Making iCal events from TextMate

Update 1/7/10
The code in this post has been superseded by a Calendar Events bundle for TextMate, which is described here.

I’ve written in the past (here and here, at least) about using’s data detectors to quickly import several events—typically sports schedules for my kids—into iCal. The data detectors are kind of touchy, though, and often grab the wrong information for the Location field. Earlier this year, I thought I had figured out a foolproof way to format an email to get the correct Location data, but I’ve since relearned the truth of the old saying

It’s impossible to make something foolproof because fools are so damned clever.

Rather than trying again to out-clever the foolish data detectors, I decided to write a short Python script that creates an .ics file from a plain text schedule. It’s implemented as a TextMate command, so my workflow will be:

  1. Copy the schedule from a mail message or web page and paste into a TextMate document.
  2. Use TextMate’s regular expressions and/or column editing to get the schedule into the desired format.
  3. Run a command that converts the schedule into the iCalendar format and save it to disk as an .ics file.
  4. Drag the .ics file into iCal to create the events.

Before running the command, the schedule must be in a format like this example:

XC meet|09/10/08 4:00 PM|09/10/08 6:00 PM|Jefferson 
XC meet|09/15/09 4:00 PM|09/15/09 6:00 PM|Lincoln
XC meet|09/17/09 4:00 PM|09/17/09 6:00 PM|Crone
XC meet|09/21/09 4:00 PM|09/21/09 6:00 PM|Washington @ Arrowhead Park
XC meet|09/24/09 4:00 PM|09/24/09 6:00 PM|Lincoln
XC meet|09/28/09 4:00 PM|09/28/09 6:00 PM|Gregory
XC meet|10/05/09 4:00 PM|10/05/09 6:00 PM|Lincoln
XC meet|10/15/09 4:00 PM|10/15/09 6:00 PM|Gregory
XC meet|10/20/09 4:00 PM|10/20/09 6:00 PM|DuPage River Park

The fields are separated by pipe characters (|), and are in this order:

  1. Event name
  2. Starting date and time
  3. Ending date and time (optional)
  4. Location

If the ending date/time isn’t given, it will be assumed to be one hour after the starting date/time.

Of course, schedules never come in this format, they come whatever format the coach happens to prefer. But TextMate’s features, especially its column editing, usually makes the reformatting go quickly. For example, it’s a rare coach that would repeat the date portion of the ending date/time, but it’s a two-step operation to copy the column of dates and paste them just before the ending times. Column editing is also the easiest way to insert the pipe characters.

I call the command that creates the iCalendar data “Events->iCal.” Here’s what it looks like in TextMate’s Bundle Editor:

It generates a new file with the iCalendar data from either a selection or an entire document. At the moment, I have it bound to ⌃⌥⌘-E, but I’m beginning to think this won’t be used often enough to be worth a keyboard shortcut.

The source code of the command is too long to fit in the Bundle Editor window, so here it is:

 1:  #!/usr/bin/env python
 2:  import sys
 3:  from dateutil.parser import parse
 4:  from dateutil.relativedelta import *
 6:  print '''BEGIN:VCALENDAR
 7:  VERSION:2.0'''
 9:  for line in sys.stdin:
10:    parts = line.strip().split('|')
11:    if len(parts) == 3:
12:      event, sdtime, place = parts
13:      start = parse(sdtime)
14:      end = start + relativedelta(hours=+1)
15:    else:
16:      event, sdtime, edtime, place = parts
17:      start = parse(sdtime)
18:      end = parse(edtime)
19:    print '''BEGIN:VEVENT
20:  SUMMARY:%s
21:  DTSTART:%s
22:  DTEND:%s
23:  LOCATION:%s
24:  END:VEVENT''' % (event, start.strftime('%Y%m%dT%H%M00'), end.strftime('%Y%m%dT%H%M00'), place)
26:  print 'END:VCALENDAR'

It uses the dateutil library from Labix to parse the dates and times and return a datetime object. Dateutil is not a standard Python library, so if you want to use it, you’ll have to follow the link to download and install it on your system. I used it here instead of the date/time parsers in the standard library because it’s much more flexible with regard to extra spaces, leading zeros, and the like.

The script is, I think, pretty straightforward despite having no comments. The if/else on Lines 11–18 handles the optional ending time. The default duration for events without an end time is on Line 14. Most of the rest of it is just printing out the iCalendar field descriptors.

After the command is run, I have a new document, which I save to disk (usually on the Desktop, because I’ll be deleting it soon) with an .ics extension. When this document is dragged into iCal, it creates all the events.