Tuesday, September 18, 2012

Creating iCalendar Files

Last year, we added something new to the Southwest Fox web site: links to iCalendar files on the Schedule page. The idea is that you can click the link for a session to add it to your calendar. It’s not meant as a replacement for Kokopelli, Dave Aring’s cool scheduling app, but so your calendar can remind you when, what, and where your next session is.

iCalendar files are pretty easy to create, as they’re just a text file with an ICS extension. Here’s the one for Steve Bondar’s HTML5 session:

DESCRIPTION;ENCODING=QUOTED-PRINTABLE:There is a lot of confusion and hype surrounding HTML5. (Rest of text removed for brevity.)

The DTSTART and DTEND elements show the start and end date and time in UTC, SUMMARY is the title, DESCRIPTION contains the session abstract, LOCATION has the room name, and UID is a unique ID.

The code to generate this simply uses text merge to insert the appropriate values into a template string. The only wrinkle is getting the date in UTC. Since Phoenix is +7 hours at the time Southwest Fox is held, I cheated and hard-coded the offset value, then used TTOC() to convert the starting and ending date/time values to the desired format:

lnTimeZoneOffset = 7 * 60 * 60
    && Phoenix is 7 hours behind GMT.
lcStart = ttoc(tdStart + lnTimeZoneOffset, 3) + 'Z'
lcEnd   = ttoc(tdEnd   + lnTimeZoneOffset, 3) + 'Z'

A more generic approach is to ask Windows for the current time zone information:

* Declare the time zone information API function and get the time zone
* information.
#define TIME_ZONE_SIZE  172
declare integer GetTimeZoneInformation in kernel32 ;
    string @lpTimeZoneInformation
lcTimeZone = replicate(chr(0), TIME_ZONE_SIZE)
lnID       = GetTimeZoneInformation(@lcTimeZone)

* Determine the standard and daylight time offset.
lnStandardOffset = ctobin(substr(lcTimeZone,   1, 4), '4RS')
lnDaylightOffset = ctobin(substr(lcTimeZone, 169, 4), '4RS')

* Determine the total offset based on whether the computer is on daylight time
* or not.
if lnID = 2  && daylight time
    lnTimeZoneOffset = (lnStandardOffset + lnDaylightOffset) * 60
else   && standard time
    lnTimeZoneOffset = lnStandardOffset * 60
endif lnID = 2

While this works just fine with Microsoft Outlook, I found a problem when trying to add a session to the calendar on my iPhone. I found this blog post by Joe Bradford that suggested the problem might be the wrong MIME type for .ICS files. Checking the IIS settings confirmed I had the same issue as Joe. The easiest way to change the settings for me (since I didn’t have access to the IIS settings directly) was to create a web.config file that changed the MIME setting and add it to the root folder of the web site:

   <remove fileExtension=".ics" />
     <mimeMap fileExtension=".ics" mimeType="text/calendar" />

However, that still didn’t resolve the problem; while I didn’t get an error, the session simply didn’t show up in the calendar. Just for grins, I tried to import an ICS file into Google Calendar; while the session did appear in the calendar, it was in the wrong timeslot (almost a year off). I manually created a calendar item and exported it, and noticed a difference in how the start and end date/times were formatted: the same as mine but without dashes or colons:


A quick change to the code generating the date/time values fixed that:

lcStart = chrtran(ttoc(tdStart + lnTimeZoneOffset, 3), '-:', '') + 'Z'
lcEnd   = chrtran(ttoc(tdEnd   + lnTimeZoneOffset, 3), '-:', '') + 'Z'

Now I can select the sessions I want (with Kokopelli’s help once it’s ready) and add them to my Outlook, iPhone, or Google Calendar so I’m reminded about each session.

No comments: