Parsing iCal RRULE

In continuation of my last blog post for managing Calendars dynamically for the ExtJs Calendar component, I present in this blog post the logic behind parsing and serializing Recurrence rules for the Calendar component, if you have enabled recurrence for the Calendar (You get to specify Recurrence rules for an event in the Calendar component, only if you enable recurrence for it, i.e. enableRecurrence=true).

For the record, the Calendar component Recurrence support is based on iCal RRULE (details of which are available here: http://www.kanzaki.com/docs/ical/rrule.html).

Here's a sample Recurrence rule based on options selected in the Calendar Panel:

 

FREQ=MONTHLY;INTERVAL=3;BYMONTHDAY=20;COUNT=5

The above Recurrence rule is a iCal RRule produced by the Calendar Panel, which basically means that the Event recurrence is set to monthly, with the event recurring every 3 months (i.e. the event happens after a gap of 3 months), with the event occurring on 20th date of the appropriate month (i.e. 20th of every 3rd month), and the event recurs 5 times (i.e. it occurs for 5 times). You can see how an iCal RRULE can capture complex recurrence patterns in simple strings.

Parsing these recurrence strings is also not that involved as it looks on the onset. Here's a quick overview of the general properties of the recurrence rule string.

  1. A null or empty recurrence rule means the event is a one-time non-recurring event.
  2. The various parts of the recurrence rule are joined with a semi-colon.
  3. Each part of the recurrence rule is a key/value pair separated by an equals sign.

Now follows a quick description of the common parts of the rrule recurrence string, and then I would discuss properties specific for the recurrence frequency (i.e. properties specific for daily, weekly, monthly or yearly recurrence patterns). I will discuss the properties in context of the ExtJs CalendarPanel and hence only the properties supported by the CalendarPanel would be discussed.

  1. FREQ: This specifies the recurrence frequency (i.e. how often an event recurs). Supported values are DAILY, WEEKLY, MONTHLY and YEARLY with obvious meanings. This is the only required property if an event is recurring, all other properties for the recurrence rule are optional.

  2. INTERVAL: Specifies the interval between 2 occurrences of the event. e.g. FREQ=DAILY and INTERVAL=5 means that the event occurs every 5th day.
    A very important point to note is that if user selected an Interval of 1 (for any frequency), this Interval attribute would be absent from the Recurrence string. So, you can safely assume a default of 1 if Interval is absent from the string.

  3.  COUNT: The number of times the event recurs.
    e.g. FREQ=WEEKLY;INTERVAL=2;COUNT=3 means the event recurs every second week for 3 times (i.e. in total 6 weeks but every second week).

  4.  UNTIL: The end date for the event. The event recurs only until this date.
    Note that COUNT and UNTIL properties are mutually exclusive. Only one of them can be specified  (which makes sense, COUNT means the event occurs this many times, UNTIL means the event recurs until this date).

Now we have properties specific for each FREQ type.

  1. DAILY
    This frequency does not have any specific property and only supports the above properties common for all frequency types.

  2. WEEKLY
    1. BYDAY: This property specifies the days of the week the event should recur.
      e.g. FREQ=WEEKLY;INTERVAL=3;BYDAY=MO,WE;COUNT=5 means that the event recurs every 3rd week on Monday and Wednesday and it has 5 recurrences spanning over 15 weeks (notice the interval is 3 meaning every 3rd week, so the total span is 15 weeks for COUNT=5).
      If BYDAY is absent for a WEEKLY frequency, you can interpret that to mean for all days of the week. If specified, the value for this property for WEEKLY frequency would be a comma-separated list of first 2 characters for a week day corresponding to the days chosen on the UI.

  3. MONTHLY
    For this frequency, you can choose if the event recurs on a fixed day of the month or a specified day of the specified week of the month. Accordingly, you have 2 mutually exclusive properties for this frequency.
    1. BYMONTHDAY: This property means that the event recurs on a fixed day of the month.
      e.g. FREQ=MONTHLY;INTERVAL=4;BYMONTHDAY=20;COUNT=5 means that the event recurs every 4 months, on 20th of each month and recurring for 5 times (spanning over 20 months).
    2. BYDAY: This property means that he event recurs on a fixed day of the fixed week of the month.
      e.g. FREQ=MONTHLY;INTERVAL=4;BYDAY=3MO;COUNT=5 means that the event recurs on 3rd Monday of every 4th month for 5 times.
      Notice BYDAY here contains 2 parts, the first being the week of the month (between 1 and 5), and the second the day of the week (first 2 characters of the week day).
  4. YEARLY
    This frequency recurrence rule is a bit confusing containing a required and an optional property.
    The required property is BYMONTH (containing the index of the month) and the optional is BYDAY. The interpretation of BYMONTH can vary slightly by the absence or presence of the BYDAY property. Let's take an example first before discussing these 2 properties.

    i) FREQ=YEARLY;BYMONTH=12;COUNT=5
    means that the event occurs every year in the month of December (BYMONTH=12 implies the month of December). The date of the month is decided by the start date of the event. If the event start date is 12th December, then the above rule means that the event occurs on 12th December of each year for 5 years.

    ii) FREQ=YEARLY;BYMONTH=12;BYDAY=3MO;COUNT=5
    means that the event occurs on 3rd Monday in the month of December each year for 5 years.

    Here's a more official description of the above properties:
    1. BYMONTH: This property specifies the month of the year (between 1 and 12). IF BYDAY is absent, then the day of the month is decided by the start date of the event in the specified month.
    2. BYDAY: This property means that the event recurs in the fixed day of the fixed week of the month specified by BYMONTH property. It again consists of 2 parts week of the month and day of the week (e.g. 3MO meaning 3rd Monday).
      As another example, BYMONTH=5;BYDAY=2TU means the event recurs on 2nd Tueday of the month of May.

I know that above can be a bit perplexing at first, but if you take each frequency one by one, and write code for parsing the recurrence string for a frequency independently, it all becomes easy.

You can use the above description to also create the RRULE recurrence string from the description of a recurrence specified by the user through the UI of your application.

I myself have code for parsing iCal RRULE strings to Microsoft Exchange Managed API Recurrence objects, and vice versa (i.e. serializing an Exchange Managed API Recurrence object as a recurrence rule string). However, the code is non-public and hence cannot be shared.

Nevertheless, the above description should be enough for parsing and serializing iCal recurrence rules in any platform and to any server-side representation. Please let me know if you face an issue (I cannot provide the code, but can certainly advice if needed).

 

 

Comments

I am starting studying iCal specifications and I found your post very instructive.

Also the link to kanzaki was precious.

I think I put all pieces together but I must miss something.

I need to express a RRUL that will recurr every 45 days BEFORE the end of each trimester.

The following:

RRULE:FREQ=YEARLY;BYMONTH=3,6,9,12;BYMONTHDAY=-1

recurrs every end of each trimester.

The I thought it would be enough to "subtract" 45 days adding:

RRULE:FREQ=YEARLY;BYMONTH=3,6,9,12;BYMONTHDAY=-1;BYYEARDATE=-45

But such rule produces only one occurrence on 01/01/2012 (which is my DTSTART)

Where is the error and how can I set the correct RRULE for this kind of need?

Thanks

by adding BYYEARDAY=-45 (not BYYEARDATE)!

I'm trying to import a calendar event from an ics file into iCal/Calendar:

Event details

  1. SUMMARY:Class: ACCT 1001 1 Lecture(1829)
  2. Start date and time: 30/08/2012 and 3.00 PM
  3. End date: 12/12/2012 and 3.50 PM
  4. MO,WE,FR only these days need to set in the date range(doesn't need to set all day).

ics file

BEGIN:VCALENDAR
PRODID:-//bashmoblie v0.1//NONSGML iCal Writer//EN
VERSION:2.0
CALSCALE:GREGORIAN
METHOD:PUBLISH
BEGIN:VEVENT
DTSTART:20120830T150000
RRULE:FREQ=WEEKLY;UNTIL=20121212T155000;WKST=SU;BYDAY=MO,WE,FR
DTSTAMP:20091130T213238Z
     UID:1285935469767a7c7c1a9b3f0df8003a@yoursever.com
     CREATED:20091130T213238Z
SUMMARY:Class: ACCT 1001  1 Lecture(1829)
LAST-MODIFIED:20091130T213238Z
     EQUENCE:0
     STATUS:CONFIRMED
SUMMARY:Class: ACCT 1001  1 Lecture(1829)
LOCATION:Title:  Introduction to Accounting(Lecture)
TRANSP:OPAQUE
END:VEVENT
END:VCALENDAR

Problem

The problem with this is doesn't correctly import the end time (gets set to 3PM in iCal). How can I fix this?

rahul's picture

Hi Anupama, sorry I cannot help with Apple specific issues here, this blog post is for discussion of general rules for parsing iCal RRULE:
http://www.kanzaki.com/docs/ical/rrule.html 

You might want to check that you are using the correct time zone while creating the ics file and also verifying the timezone for your Apple device.

Am using normal time zone. Here the end time specified in the UNTIL of RRULE doesn't work correctly. Now it shows the end time and start time(3 PM) are same.

rahul's picture

Anupma if I recall correctly, all iCal date/times need to be in UTC. There's nothing like a normal time zone, and each Apple device has an option to set the time zone (check General Settings).

Hello 

I have changed to UTC time format but still the problem is here..

Hi there,

I got an essential question concerning RRULEs. My own parser / generator for RRULEs is finished, but only for "normal" recurrencies. Exceptions from a recurrence rule work fine (EXDATE), but what about changes (what is done by the INSTANCE in MAPI recurrence objects)? In our application you can modify one single appointment of a series, what is possible in Outlook as well (i.e. one of the ten appointments is at 4:00 pm instead of 1:00 pm). So can this be handled by RRULE strings? In the ICAL spec I didn't find anything about this... 

 

rahul's picture

Hi Andi, I am also not aware of such property for a RRULE. it might be a MAPI-specific extenstion to RRULE spec. You might want to seek help at MSDN or Exchange forums.

Thanks for your quick answer.

In the end I found it in the ICAL specs. You have to work with single ICALS for each exception using the recurrence ID. Its quite the same as the "instance thing".

http://www.ietf.org/rfc/rfc2445.txt

4.8.4.4 Recurrence ID

rahul's picture

Thanks for sharing Andi!!!

The official specification for iCal, RFC 5545, is here: http://tools.ietf.org/html/rfc5545

The RFC 2445 is obolete.

rahul's picture

Thanks for sharing Olivier...

First off, thank you for posting this.  It has been very useful but I have one concern. Can you please explain why you think they BYMONTHDAY and BYDAY rules are mutually exclusive for the MONTHLY frequency?  I can kind of see what why you might think this but this isn't explicitly mentioned in the specification and I could think of a case where having both is valid.  For example,

Every Friday the 13th, forever:

  DTSTART;TZID=US-Eastern:19970902T090000
  EXDATE;TZID=US-Eastern:19970902T090000
  RRULE:FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13

  ==> (1998 9:00 AM EST)February 13;March 13;November 13
      (1999 9:00 AM EDT)August 13
      (2000 9:00 AM EDT)October 13
  ...
Again, thanks for all the help.
rahul's picture

Hi David, that's an interesting application of these 2 parts together. The question is whether Calendar clients (Outlook, Exchange etc.) generally support this combination. I might be mistaken, but as far as I recall, when specifying such patterns in Exchange through EWS API, Exchange allows you to set only one of BYDAY or BYMONTHDAY options at a time.

If you are creating your custom parser and custom Calendar app, you are off-course free to interpret the specification as you deem fit. The question would be if other apps also interpret the rules same way in case you need stored data to be interoperable. Your best bet would be try your Rules with different clients to see if they interpret them correctly.

You are correct that most calendar applications don't allow for these more complex rules but there are many applications utilizing the ical spec.  The Friday the 13th example is directly from the specification http://www.ietf.org/rfc/rfc2445.txt and thus makes it a valid appication of these two rules does it not?

rahul's picture

David, I am really short on time to read specs, but yes I saw the Friday 13th example, and it certainly seems the spec allows using BYDAY and BYMONTHDAY together. Now my guess is if the spec allows it, most clients (inclduing Outlook) should support it (I haven't tested though).

Hi, 

 First of all thanks for the wonderful and helpful information.  I implemented rrule in my application and it is working find but

start time and end time is totally differnt from the input i have given.other things are working fine.below is the code

 

Convert.ToDateTime(strStarttime).ToUniversalTime().ToString("yyyyMMdd\\THHmmss\\Z") .where strStarttime is 3:20 PM like that.

can anyone tell me what is wrong here?

rahul's picture

It could be Convert.ToDateTime. Try using DateTime.ParseExact and specify format for strStarttime explicitly.

hi ,

   thanks for the answer .It is working fine .But i am faceing problem in updating outlook existing appointment .It is sending mail but new time is not updating in calender.

i tried with UID and sequence are same.also incrementing sequence no by 1 .

thanks
Sanchi.

rahul's picture

Hi Sanchi, I am afraid I am unable to help with topics not directly related to the topic of the blog post. I advise you seek help on Outlook forums.