iCalendar Hacking

Dave Morriss
Last Updated: 2013-07-01

The Problem

Back in 2012 Ken Fallon tried to use Google Calendar to set up an event for the recording of the monthly Community News shows on HPR. He wanted to set these on the Saturday before the first Monday of the month. Surprisingly he didn't find a way to do this and ended up deleting the attempt.

I looked at the calendaring application I use: Thunderbird with the Lightning calendar plugin, to see if I could manage it. I also couldn't find a way.

Research

I was aware that calendars like Google Calendar and many more use iCalendar to represent and communicate events, and so I thought I would try and find out more. I have often wondered how iCalendar calendaring works, so I grabbed a copy of RFC 5545 and absorbed enough to get a vague idea of how it defines recurrent entries. If you'd like to read this yourself have a look at http://www.ietf.org/rfc/rfc5545.txt

There are two primary methods of defining recurrent events within iCalendar: RRULE and RDATE. The RRULE property is the more powerful of the two and the more complex. The description of RRULE is long and involved, but in the context of this problem I could see how to define the first Monday of every month:

  RRULE:FREQ=MONTHLY;BYDAY=1MO

Most calendar applications are well able to deal with this sort of specification, and it seems to be the way in which most recurrent events are defined.

Experiment 1

However, this is not what we want. We need the Saturday before the first Monday, but the iCalendar syntax doesn't have any obvious way of subtracting 2 days to get the Saturday before, especially when it could be in the previous month.

The definition of the BYDAY rule part specifies a comma separated list of days of the week (MO, TU, WE, TH, FR, SA, SU). As we have seen these weekday specifications can also be preceded by a digit as in 1MO.

There is also a rule part BYSETPOS which modifies the BYDAY rule part. It is followed by a comma separated list of values which corresponds to the nth occurrence within the set of events specified by the rule.

This led me to believe that I could make a rule as follows:

  RRULE:FREQ=MONTHLY;BYDAY=SA,SU,1MO;BYSETPOS=1

I was rather surprised to find that this actually worked, but soon discovered that it has a fatal flaw. If the three days in BYDAY are all in the same month it works fine, but if either the Saturday or Sunday are in the previous month it can't backtrack far enough and drops the event on the wrong day.

Even if this worked, I suspect many calendar applications couldn't define it anyway. Thunderbird+Lightning cannot for certain. The user interface is just not able to specify this amount of detail.

The following is the full iCalendar entry that I plugged into Thunderbird:

  BEGIN:VCALENDAR
  PRODID:MyCal
  VERSION:2.0
  BEGIN:VEVENT
  DTSTART:20130629T190000Z
  DTEND:20130629T210000Z
  LOCATION:mumble.openspeak.cc port: 64747
  RRULE:FREQ=MONTHLY;BYDAY=SA,SU,1MO;BYSETPOS=1
  SUMMARY:HPR Community News
  END:VEVENT
  END:VCALENDAR

Experiment 2

However, I discovered there is an alternative way through the RDATE specification. With it you can define a number of events by pre-computing them. I was able to build a test calendar containing the next twelve Community News events (there's naturally a plug-in for Vim which recognises the syntax!), load it into Thunderbird and make it send out invitations.

In true Hacker style I wrote a Perl script to generate the necessary RDATE dates. The script uses the Perl module Date::Calc to perform date calculations and Data::ICal to generate iCalendar data.

Running the script in the following way:

  ./make_calendar > experiment2.ics

generates a file containing 12 appointments that can be loaded into Thunderbird (and presumably any other iCalendar-based calendar).

  BEGIN:VCALENDAR
  VERSION:2.0
  PRODID:Data::ICal 0.20
  X-WR-CALNAME:Hacker Public Radio
  X-WR-TIMEZONE:Europe/London
  BEGIN:VEVENT
  DESCRIPTION:This is a test\, building an iCalendar file and loading it into
    Thunderbird.\n-----------------------------------------\nMumble settings\
   nServer Name: Anything you like\nServer Address: mumble.openspeak.cc \nPor
   t: 64747\nName: Your name or alias is fine\n\nDon't have mumble\, setup in
   structions can be found on our wiki -\nhttp://linuxbasix.com/tiki-index.ph
   p?page=Linux+Basix+Mumble\n
  DTEND:20130803T210000Z
  DTSTART:20130803T190000Z
  LOCATION:mumble.openspeak.cc port: 64747
  RDATE;VALUE=DATE-TIME:20130803T190000Z
  RDATE;VALUE=DATE-TIME:20130831T190000Z
  RDATE;VALUE=DATE-TIME:20131005T190000Z
  RDATE;VALUE=DATE-TIME:20131102T190000Z
  RDATE;VALUE=DATE-TIME:20131130T190000Z
  RDATE;VALUE=DATE-TIME:20140104T190000Z
  RDATE;VALUE=DATE-TIME:20140201T190000Z
  RDATE;VALUE=DATE-TIME:20140301T190000Z
  RDATE;VALUE=DATE-TIME:20140405T190000Z
  RDATE;VALUE=DATE-TIME:20140503T190000Z
  RDATE;VALUE=DATE-TIME:20140531T190000Z
  RDATE;VALUE=DATE-TIME:20140705T190000Z
  SUMMARY:HPR Community News
  END:VEVENT
  END:VCALENDAR

Thunderbird's event dialog will not let you edit the sub-events, just delete them, but the idea works, albeit in a rather clunky way.

I don't have access to many other calendaring systems, except for Korganizer. It sees the multiple dates as multiple discrete events rather than a single recurring event.

Experiment 3

Other calendaring systems that do not use iCalendar can handle this problem more effectively. For many years I have used a tool called pcal (http://pcal.sourceforge.net/) that generates PostScript calendars which I print and hang on the wall. It can reliably specify the Saturday before the first Monday of each month with the expression:

  Saturday before first Monday in all    HPR Community News (19:00 - 21:00)

Another tool which can do this is Remind (http://www.roaringpenguin.com/products/remind, http://www.linuxjournal.com/article/3529). With this the following expression achieves the required result:

  REM Mon 1 --2 AT 19:00 MSG HPR Community News (19:00 - 21:00)

Remind comes with a tool which can generate iCalendar data, called rem2ics. It expects output from the remind command from which it generates data. The following example generates 12 meetings from the above reminder which is in the file .reminders.

  remind -s12 .reminders | TZ=UTC rem2ics -do >reminders.ics

The result uses the RDATE specification as discussed in Experiment 2, with the same result.

  BEGIN:VCALENDAR
  VERSION:2.0
  PRODID:http://mark.atwood.name/code/rem2ics rem2ics 0.93
  BEGIN:VEVENT
  UID:rem2ics.50a24176.3f5d.1@localhost
  SUMMARY:HPR Community News (19\:00 - 21\:00)
  DTSTART;TZID=UTC:20121103T190000
  RDATE;TZID=UTC:20121103T190000,20121201T190000,20130105T190000,20130202T
   190000,20130302T190000,20130330T190000,20130504T190000,20130601T190000,2
   0130629T190000,20130803T190000,20130831T190000,20131005T190000
  DTSTAMP:20121113Z124750Z
  COMMENT: generated by rem2ics 0.93\n http://mark.atwood.name/code/rem2ic
   s\n data[1]=|2012/11/03 * * * 1140 7\:00pm HPR Community News (19\:00 -
   21\:00)|
  END:VEVENT
  END:VCALENDAR

Conclusion

It seems that the iCalendar specification should be able to generate the appointments we need using the compact RRULE specification. However, in the (admittedly small) sample of calendaring applications checked this does not seem to have been implemented properly.

Other tools that do not use iCalendar have less difficulty representing such events but are not as widely adopted.

If anyone has any ideas about how this problem could be solved more effectively then please let me know!

Links