Advanced Search
Apple Developer Connection
Member Login Log In | Not a Member? Contact ADC

Internet Developer IconInternet Developer Mac OS X Logo
HTML Fonts CSS Java JavaScript Web Services
Working with Javascript
 
Web Development & Mac OS X

iCalendar Files on Mac OS X
Apple's new calendar application, iCal, is available for Mac OS 10.2. iCal makes it easy to publish and share your calendar data online, either through .Mac or third-party services. Perhaps of more interest to developers, iCal stores its data in the standard iCalendar (.ics) file format, which is used by other calendar programs, like Mozilla Calendar. This means you can take advantage of existing libraries to develop your own applications for sharing and publishing iCal files.

In this article, I'll go over some options for publishing your iCal data through outside services. I'll then show you how you can start working with code that will let you display your calendars on your own OS X server. I'll start with the basics of the iCalendar file format, introduce you to some ways of dealing with iCal files in Perl and PHP, and finally demonstrate a PHP-based WML calendar viewer for cellular phones and other mobile devices.

Publishing Your iCal Data
The simplest way publish your iCal calendars is with .Mac. Create a calendar, then choose "Publish" under the Calendar menu. You'll be prompted to enter your .Mac user information into your internet control panel if you haven't already done so. (Free trial accounts are available from Apple, and permanent accounts are available for a fee.) Once you've published a calendar, it will be available online at http://ical.mac.com/.mac/username/calendarname. Other iCal users can subscribe to your calendar at webcal://ical.mac.com/username/calendarname.

Similarly, iCal Exchange lets you publish your calendars to their server from within iCal. This is currently a free service. To publish to iCal Exchange, select "Publish" from the Calendar menu, and then select "Publish on a web server." If you've published a general-interest calendar, you can use a directory site like iCalShare to let people know that it exists.

Hosting Your Own Calendars
The rest of this article will focus on helping you publish your calendar data on your own OS X server, either via a custom application, or by installing a complete calendar viewer like PHP iCalendar. I'll start with an overview of the .ics file format.

The iCalendar File Format
The full specification can for the iCalendar file format be found at the Internet Engineering Task Force site. Here's a simple example:

BEGIN:VCALENDAR CALSCALE:GREGORIAN X-WR-TIMEZONE;VALUE=TEXT:US/Pacific METHOD:PUBLISH PRODID:-//Apple Computer\, Inc//iCal 1.0//EN X-WR-CALNAME;VALUE=TEXT:Example VERSION:2.0 BEGIN:VEVENT SEQUENCE:5 DTSTART;TZID=US/Pacific:20021028T140000 DTSTAMP:20021028T011706Z SUMMARY:Coffee with Jason UID:EC9439B1-FF65-11D6-9973-003065F99D04 DTEND;TZID=US/Pacific:20021028T150000 BEGIN:VALARM TRIGGER;VALUE=DURATION:-P1D ACTION:DISPLAY DESCRIPTION:Event reminder END:VALARM END:VEVENT END:VCALENDAR

Each line (called a "content line") consists of two parts, separated by a colon. The first part is called a property, and it can be combined with one or more parameters. A property is separated from its parameters by a semicolon, and multiple parameters can be separated by commas. The part after the colon is called the property's value. For example, the DTSTART property above (the starting date and time of the event) has a parameter to indicate the relevant time zone, TZID, whose value is "US/Pacific": DTSTART;TZID=US/Pacific. The property's value is a date/time string which consists of the date in YYYYMMDD format, followed by the letter "T," and a time string in HHMMSS format.

Note that lines end Windows-style, with a carriage return and line feed (CRLF). You can "fold" long lines by inserting a CRLF followed by one or more whitespace characters.

Calendars can contain various components, such as events, alarms, to-do list items, and others. The most common component is an event. You can see an event described above between the lines BEGIN:VEVENT and END:VEVENT. Event times can be indicated by the DTSTART and DTEND properties, or by combinations of the DTSTART, DURATION, and RRULE properties.

The RRULE property defines recurrence rules for a given object, and the syntax can get fairly complicated. RRULEs allow an iCalendar file to efficiently contain an event that indefinitely repeats, say, the second Tuesday of each month, but doesn't happen in November and June. Here's an example RRULE for a recurring Monday-to-Thursday event that lasts until December 14, 2002:

RRULE:FREQ=WEEKLY;UNTIL=20021214T055959;INTERVAL=1;BYDAY=MO,TU,WE,TH

Parsing with Perl
Developers in the Reefknot Project have made inroads toward a Perl toolkit for parsing iCal files. The two major modules under development are Net::ICal and Date::ICal. Net::ICal, which aims to be a more comprehensive iCalendar parser, is in a very early alpha release, and unfortunately does not seem to be under active development—so it's not even ready for lightweight use. There is a mailing list for developers and potential developers, however, and the module authors have put out a call for support.

Date::ICal, a smaller module for parsing iCalendar-style dates, is more stable. It could be useful if you'd like to write your own parser in Perl. To install Date::ICal from CPAN, simply type the command below in a Terminal window, then follow the onscreen prompts:

sudo perl -MCPAN -e 'install Date::ICal' <followed by your password>

The sample Perl/CGI script below is capable of parsing simple (non-repeating) events and displaying them as a flat list. It uses Date::ICal to handle date/time strings. Since only a few key properties are recognized, most content lines end up being silently ignored:

#!/usr/bin/perl use Date::ICal; use strict; use CGI; use CGI::Carp qw(fatalsToBrowser); my $c = new CGI(); print $c->header(); print $c->start_html('Simple Calendar Parser'); print "This basic iCalendar file parser can display simple non-repeating events<br><br>"; #### either install example.ics in your cgi-bin directory, or provide a full path below open(FILE,'example.ics') or die "Cannot open sample .ics file 'example.ics'"; my $showfile = ''; while(<FILE>) { $showfile .= $_; chomp(); my ($prop,$val) = split(':',$_); dispatch($prop,$val); } print "<br><br>The raw .ics file:<br><br><pre>$showfile</pre><br>"; print $c->end_html(); ## handle the DTSTART property sub dtstart { my ($dtstring,@pp) = @_; my $tzone = ''; # check the parameter list for a time zone (we're just going to display it) for my $p (@pp) { if ($p =~ s/TZID=(.*)/$1/i) { $tzone = " ($p time)"; last; } } return "Starts: " . pretty_dt($dtstring) . "$tzone"; } ## handle the BEGIN property (only recognize VEVENTs) sub begin { my ($d,@pp) = @_; # only deal with VEVENT my $ret = $d eq 'VEVENT' ? "<strong>Event</strong>" : undef; return $ret; } ## handle the SUMMARY property sub summary { my $s = shift; return "Summary: $s"; } sub dispatch { # set up a hash of references to functions to call for a few selected properties # anything else will be ignored my %funcs = ( 'BEGIN' => \&begin , 'SUMMARY' => \&summary , 'DTSTART' => \&dtstart ); my ($prop_params,$data) = @_; # properties can be followed by optional parameters, separated by a semicolon my @prop_params = split(';',$prop_params); if ( $funcs{$prop_params[0]} ) { my $output = &{ $funcs{$prop_params[0]} }($data,@prop_params); print "$output <br>\n" if $output; } } ## use Date::ICal to parse a date-time string sub pretty_dt { my $dstring = shift; my $ical = Date::ICal->new( ical => $dstring ); $ical->offset(0); # no time zone math (we're displaying the timezone, if supplied) my $pdate = my $day = $ical->month . '/' . $ical->day . '/' . $ical->year; $pdate .= ' ' . $ical->hour . ':' . ($ical->min > 10 ? $ical->min : '0' . $ical->min); return $pdate; }

If you've installed the Perl script above, you can run it against a small example .ics file, like this one. If you do, you'll see the following output:

Perl sciprt output screen shot.

Parsing with PHP
While the iCalendar libraries available for Perl are currently incomplete at best, there's at least one solid tool available for PHP. PHP iCalendar is an open-source iCal file parser that generates a number of attractive, customizable calendar views. A sample calendar can be found here.

To install PHP iCalendar, first make sure your version of Apache is configured to support PHP. If it isn't, please see this article for instructions. You should note that PHP iCalendar assumes that request variables will be available as globals, and will alter your php.ini file to enable gpc_globals, if necessary.

At the time of this writing, the most current version of PHP iCalendar is 0.8.1, available for download here. Here are the steps I took to download and extract the files into my server's document root directory. Note that you may wish to download from a different mirror.

liz> cd /Library/WebServer/Documents liz> curl -O http://telia.dl.sourceforge.net/sourceforge/phpicalendar/phpicalendar-0.8.1.tgz liz> tar -xzvf phpicalendar-0.8.1.tgz

That's it. Because the code comes with sample calendars, you can now test your installation by going to http://localhost/phpicalendar-0.8.1/index.php. Using the default code to display your own calendars is as easy as copying your .ics files to the "calendars" directory of your PHP iCalendar installation. If you'd like your online calendars to be updated as soon as you change them with iCal, you can create symbolic links from your iCal files to your web calendar directory. For this to work, you'll need to make your ~/Library directory world-executable before you make the symbolic link:

liz@mail> chmod go+x ~/Library liz@mail:~> cd /Library/WebServer/Documents/phpicalendar-0.8.1/calendars liz@mail:calendars> ln -s ~/Library/Calendars/Home.ics ./Home.ics

Another option is to use a cron job to copy calendars from ~/Library/Calendars to /Library/WebServer/Documents/phpicalendar-0.8.1/calendars on a regular basis. To use the cron scheduler to copy calendars to a web directory nightly, add these four lines to your crontab file.

# use /bin/sh to run commands, no matter what /etc/passwd says SHELL=/bin/sh # run five minutes after midnight, every day 5 0 * * * cp /Users/liz/Library/Calendars/*.ics /Library/WebServer/Documents/phpicalendar-0.8.1/calendars > /dev/null 2>&1

For a short tutorial on using cron with OS X, see this article at macosxhints.

Extending PHP iCalendar: A WML Calendar
Although PHP iCalendar works just fine as a stand-alone application, it also provides a number of utility functions that you can use to extend its functionality or write your own iCal viewer. For example, you can build an application that uses the PHP iCalendar internals to simplify the process of creating a WML calendar viewer for cell phones and other wireless devices.

To use this application, first add a few lines to your httpd.conf file (in OS X, this file usually lives in /etc/httpd/):

AddType text/vnd.wap.wml .wml Addhandler application/x-httpd-php .wml

The first line adds a new MIME type for .wml files. The second tells Apache to parse .wml files as PHP.

If you're new to WML, you might want to vist a WML tutorial like this one at W3Schools. In general, WML is very similar to HTML but with a smaller set of tags. WML files must be made up of valid XML, and can be arranged into "cards," which can then be linked to one another. Although the files below are separated into one card per file, you can combine two or more cards in a single WML file.

The first of the three files in this application is named cal.wml, and it shows a month-at-a-time view of the calendar. A day will appear hyperlinked if it has any events, and the user can then select the link to view that day's calendar. Since cell phone screens are tiny, the output is as minimal as possible:

<?php header ("Content-type: text/vnd.wap.wml"); // set this to the location of phpicalendar on your system define('BASE', './phpicalendar-0.8.1/'); // the phpicalendar files assume that request variables will be // available as globals. you may need to edit your php.ini file to enable // gpc_globals include_once(BASE.'functions/ical_parser.php'); // set up some date variables ereg ("([0-9]{4})([0-9]{2})([0-9]{2})", $getdate, $day_array); $this_day = $day_array[3]; $this_month = $day_array[2]; $this_year = $day_array[1]; $date = mktime(0,0,0,"$this_month","$this_day","$this_year"); $long_month = date("F", $date); $iday = strtotime($this_year.$this_month."01"); $i = 1; $lastday = date('j', mktime(0,0,0,$mon+1,0,$year)); $today = date( "Ymd", time() ); // start the xml page print '<?xml version="1.0"?> <!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/wml_1.1.xml"> <wml>'; ?> <card id="main" title="Calendar"> <p> <strong><?= $long_month ?> <?= $this_year ?></strong> </p> <p> <a href="newmonth.wml?getdate=<?= $getdate ?>">Change Month</a> </p> <p> <a href="day.wml?getdate=<?= $today ?>">Today</a> </p> <p> <?php // loop through the days in the month, display with // a link if there are any events in the calendar while ($i < $lastday) { $day = date ('d', $iday); $daylink = date ("Ymd", $iday); $pday = $i < 10 ? "0$i" : $i; $tstar = strcmp("$this_year$this_month$pday",$today) ? '' : '*'; if (isset($master_array[("$daylink")])) { print "<a href=\"day.wml?getdate=$this_year$this_month$pday\">$day"; $ecount = count($master_array[("$daylink")]); print " ($ecount) $tstar</a>"; } else { print "$day $tstar"; } print '<br/>'; $iday = strtotime("+1 day", $iday); $i++; } ?> </p> </card> </wml>

Next is a card called newmonth.wml which allows the user to switch months. If you're unfamilar with WML, you'll notice that the form elements are similar to HTML form elements, although the code to define the form's action is different:

<?php header ("Content-type: text/vnd.wap.wml"); print '<?xml version="1.0"?> <!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/wml_1.1.xml"> <wml>'; ?> <card id="mlist" title="Select Month"> <p> <strong>Month</strong> <select name="m"> <option value="01">January</option> <option value="02">February</option> <option value="03">March</option> <option value="04">April</option> <option value="05">May</option> <option value="06">June</option> <option value="07">July</option> <option value="08">August</option> <option value="09">September</option> <option value="10">October</option> <option value="11">November</option> <option value="12">December</option> </select> <strong>Year</strong> <select name="y"> <option value="2002">2002</option> <option value="2003">2003</option> </select> <do type="accept" label="Choose"><go href="cal.wml?getdate=$(y)$(m)01"/></do> </p> </card> </wml>

Finally, here's a card that displays a one-day view of calendar events. The other files in this set assume it will be named day.wml:

<?php header ("Content-type: text/vnd.wap.wml"); print '<?xml version="1.0"?> <!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/wml_1.1.xml"> <wml>'; // set this to the location of phpicalendar on your system define('BASE', '/usr/local/apache/htdocs/wap/phpicalendar-0.8.1/'); // the phpicalendar files assume that request variables will be // available as globals. you may need to edit your php.ini file to enable // gpc_globals include_once(BASE.'functions/ical_parser.php'); ereg ("([0-9]{4})([0-9]{2})([0-9]{2})", $getdate, $day_array); $this_day = $day_array[3]; $this_month = $day_array[2]; $this_year = $day_array[1]; $date = mktime(0,0,0,"$this_month","$this_day","$this_year"); $showday = date('M j, Y D', $date); ?> <card id="day" title="<?= $showday ?>"> <p><a href="cal.wml?getdate=<?= $getdate ?>">Month View</a></p> <?php print "<p><strong>$showday</strong></p>"; $daylink = date ("Ymd", $date); if (isset($master_array[("$daylink")])) { foreach ($master_array[("$daylink")] as $event_times) { foreach ($event_times as $val) { $num_of_events++; $event_text = stripslashes(urldecode($val["event_text"])); $event_text = strip_tags($event_text, '<b><i><u>'); $event_text = htmlentities($event_text,ENT_NOQUOTES); if ($event_text != "") { $event_start = @$val["event_start"]; $event_end = @$val["event_end"]; $event_start = date ($timeFormat, @strtotime ("$event_start")); $event_start2 = date ($timeFormat_small, @strtotime ("$event_start")); $event_end = date ($timeFormat, @strtotime ("$event_end")); if (!isset($val["event_start"])) { $event_start = ''; $event_end = ''; print "<p><i>$event_text</i></p>"; } else { print "<p>$event_start2 $event_text</p>"; } } } } } else { print "<p>No Events Scheduled</p>"; } ?> </card> </wml>

This application was written to display a single calendar. If you want to display a calendar of your own, simply place it (or create a symbolic link to it, as shown above) in the calendars directory of your PHP iCalendar installation. Then edit config.inc.php and change the value of $default_cal. You can also acheive the same result by moving or deleting all files in the calendars directory except for yours. If PHP iCalendar sees a single .ics file in that directory, it will automatically use that file as the default.

To test these files, you can use a web-enabled cell phone or a WAP browser simulator like the one available from mobone.com. If you've got everything configured correctly, you'll see something like the screenshots below. (Note that the mobone.com browser simulates a WAP browser by means of a CGI script, which means that your web server must be visible to other machines on the internet.)

Month View (cal.wml)

WAP screen shot

Day View (day.wml)

WAP screen shot

Choose a New Month (newmonth.wml)

WAP screen shot

Conclusion
The tools available for parsing iCal files are still young, as is the iCal application itself. Still, I hope I've shown you some promising methods for exploring online shared calendars. If your interest has been sparked by this topic, I encourage you to look into contributing to one of the several worthy open-source development efforts currently underway.

 
Get information on Apple products.
Visit the Apple Store online or at retail locations.
1-800-MY-APPLE

Copyright © 2003 Apple Computer, Inc.
All rights reserved. | Terms of use | Privacy Notice