Seriously, RFC2445 is on crack. No wonder that few apps handle recurrence rules properly.

Let me give you some examples straight from the RFC:

RRULE:FREQ=YEARLY;INTERVAL=2;BYMONTH=1; BYDAY=SU;BYHOUR=8,9;BYMINUTE=30

These are tons of events: at 8:30 and 9:30 every other year, every sunday in january.

That’s not precisely what I’d call a yearly frequency… I mean, we’re talking about every sunday in januar. I’d more call that like… weekly.

And yes, this is the official solution:

“every Sunday in January at 8:30 AM and 9:30 AM, every other year”

Note that you can not do 8:30 AM and 9:00 AM. You could do 8:00 AM, 8:30 AM, 9:00 AM and 9:30 AM (using BYMINUTE=0,30). Pure crack.

Everyday in January, for 3 years: DTSTART;TZID=US-Eastern:19980101T090000 RRULE:FREQ=YEARLY;UNTIL=20000131T090000Z; BYMONTH=1;BYDAY=SU,MO,TU,WE,TH,FR,SA or RRULE:FREQ=DAILY;UNTIL=20000131T090000Z;BYMONTH=1

Isn’t that totally on crack? Using FREQ=YEARLY for everyday in january?

Notice how the semantics of “BYMONTH” change? In the first case, it’s creating multiple events (BYMONTH=1,2 would name january and february independently); in the second case it’s used to filter out individual days. Let me explain that a bit more:

FREQ=YEARLY;BYDAY=MO,WE means every Monday and Wednesday. See how the modifier makes about 104 recurrences out of a single year?

FREQ=HOURLY;BYDAY=MO,WE means every hour, but only on Monday or Wednesday. In this case, the modifier reduces the hourly events by throwing out all that are on other weekdays, reducing the number of recurrences.

Again, this is not my weird reading, but the RFC says:

BYxxx rule parts for a period of time which is the same or greater than the frequency generally reduce or limit the number of occurrences of the recurrence generated. […] BYxxx rule parts for a period of time less than the frequency generally increase or expand the number of occurrences of the recurrence.

(Note that I didn’t find any details on if they consider a week to be be “less” or “more” than a month, since there is no obvious subset relationship, but a week can overlap two months, and a month usually overlaps five weeks… So if I have FREQ=WEEKLY;BYMONTH=1;BYDAY=WE, is that Monday in every week overlapping January? Every week completely contained in January? If I’d want every Monday in January, I’d use FREQ=DAILY;BYMONTH=1;BYDAY=MO…)

Intersecting or joining two RRULE specifications is usually impossible (try to write one rule that is 8:00 and 9:30 every day for convincing yourself that a join won’t work, and try to intersect FREQ=DAILY;INTERVAL=3 and FREQ=YEARLY;INTERVAL=2…)

Anyway, I’m truly not suprised that my mobile and probably many other applications don’t handle recurring events completely (that is when importing from iCal they often just lose the repetitions).

My own implementation is doing okay. There are still a few missing features (BYSETPOS e.g. which can be used to say “the second to last tuesday or thursday each month”; should be trivial to add however, just picking the appropriate indexes out of a list), and some odd corner cases to dig in the specs and resolve (e.g. “every other week on friday, starting saturday march 3” - are we talking in business weeks, or intervals of 7 days here? When talking in weeks, is it friday in the week containing march 3, or following march 3, since that day itself is not a friday!)

Some of these details probably vary heavily from iCalendar-supporting application to another… e.g. FREQ=DAILY;BYHOUR=8;UNTIL=20070215T000000Z does this include a recurrence on 2007/02/15 at 8:00 (which is past the UNTIL time, however the hour of the UNTIL field is not used in presence of BYHOUR).

If you ever design a language for specifying recurrences, please:

  • Allow interval repetitions or repetition patterns (e.g. Monday, Thursday) for any and multiple fields (iCalendar only has interval for one)
  • Allow arbitray union and intersection
  • Think of people not using Gregorian calendars, we have i18n/l10n…

Consider something like (intersect (repeat daily interval=3) (union (repeat weekly monday) (repeat monthly 1) ) )

(i.e. every 3 days, if they fall on the first of the month or on a monday)

Also spend some time thinking about alignment: “Every monday in a week containing the 15th of a month.” - months and weeks are not aligned; assuming a week being defined as Monday-Sunday, this can be any day between the 9th and 15th of the month (in fact you could specify this in iCalendar, but it’s probably easy to come up with an example that is too difficult to represent in iCalendar RRULEs, albeit trivial to calculate.)