Skip to content

Common Use Cases

Philipp Kewisch edited this page May 5, 2024 · 2 revisions

Expanding Recurring Events

When dealing with iCalendar or jCal, we sometimes want to expand occurrences from a recurring event rule. Given the multi-layer approach of ICAL.js there are multiple ways to achieve this.

Via RecurExpansion

This is a higher layer on the model that attempts to abstract away some of the complexity of bringing together RRULEs, RDATEs and EXDATEs.

Occurrences are iterated from the first occurrence, because there are some rule parts in the spec that make it not straightforward to start somewhere in the middle. This means you'll need to skip occurrences before your range.

let expand = new ICAL.RecurExpansion({
  component: event,
  dtstart: event.getFirstPropertyValue('dtstart')
});

let rangeStart = ICAL.Time.fromString("2013-04-19T00:00:00");
let rangeEnd = ICAL.Time.fromString("2013-04-21T00:00:00");

let next = iterator.next()
for (let next = iterator.next(); next && next.compare(rangeEnd) < 0; next = iterator.next()) {
 if (next.compare(rangeStart) < 0) {
    continue;
  }
  // Do something with the date
}

Via Components and Properties

On the lower level, working with components and properties, you can iterate occurrences for a specific RRULE, you can grab alle RDATE and EXDATE properties to get their date or period. This gives you more control, but also requires you to piece together the dates and exceptions on your own.

let vcalendar = new ICAL.Component(ICAL.parse(ics));
let vevent = vcalendar.getFirstSubcomponent("vevent");

// Let's start with RRULEs
let recur = vevent.getFirstPropertyValue("rrule");

// When creating the iterator, you must use the DTSTART of the event, it is used for the basis of some calculations
let dtstart = vevent.getFirstPropertyValue("dtstart");
let iterator = recur.iterator(dtstart); 

let rangeStart = ICAL.Time.fromString("2013-04-19T00:00:00");
let rangeEnd = ICAL.Time.fromString("2013-04-21T00:00:00");

// Iterate through the start dates in the range.
let next = iterator.next()
for (let next = iterator.next(); next && next.compare(rangeEnd) < 0; next = iterator.next()) {
  if (next.compare(rangeStart) < 0) {
    continue;
  }
  // Do something with the date
}

// Now grab some RDATEs (additional occurrences) and EXDATEs (removed occurrences)
let rdates = vevent.getAllProperties("rdate").reduce((acc, prop) => acc.concat(prop.getValues()));
let exdates = vevent.getAllProperties("rdate").reduce((acc, prop) => acc.concat(prop.getValues()));
// Do somthing with the dates

For completeness, keep in mind that there can be modified occurrences (recurrence exceptions) in the series. These are events where a certain aspect of a specific occurrence has changed, e.g. this week's meeting is 30 minutes earlier. You can identify them in that they have a RECURRENCE-ID property on the VEVENT, which points to the original start date of the occurrence.

Clone this wiki locally