Overview
Goals and Objectives
Recurrence is a common calendaring feature allowing the same conceptual event to occur at different dates and times. In 0.6, we will extend the calendar feature set to include the importing/export as well as creation and modification of recurring events. This documents specifies the client-side requirements and workflows that should be supported. We will also detail the implementation design and specifics in order to support this functionality.
- Sheila Mooney
- Spec owner and contributor
- Jeffrey Harris
- Spec contributor
- Alec Flett
- Spec contributor
- Bryan Stearns
- Spec contributor
- Mimi Yin
- Spec contributor
Background
We currently do not have any support for recurrence in 0.5. If an imported calendar has recurring events on it, we simply create separate items for the first 10 instances of the recurring event.
High Level Use Cases
These are the high-level most basic use cases we will focus on supporting for 0.6:
- A user imports a calendar into Chandler that has recurring
events:
- The calendar has been exported from Chandler.
- The calendar has been exported from another client ie: Apple iCal
- A user exports a calendar from Chandler that has recurring
events:
- The recurring events may have been created or modified in Chandler.
- The recurring events could have been created in another client ie: Apple iCal and re-exported.
- Publish and subscribe to a calendar that has recurring events.
- Create a new event in Chandler and specify simple recurrence parameters.
- Delete a recurring event.
- Modify a recurring event.
- Modify the recurrence rule for a recurring event, part-way through the event series.
- Make exceptions to the recurrence rule.
Definitions
Requirements
Interoperability: The iCalendar semantics will be used for recurrence rules for import/export as well as sharing. This allows recurrence events to be shared with other calendar clients and on CALDAV servers.
Display: The Chandler UI will only present a subset of all the complex recurrence rules that can be specified using iCalendar. If a calendar contains an event with recurrence that is more complex than available in Chandler, we need to support a mechanism to indicate this to the user and preserve the recurrence rules under the hood.
Modifications: If we are modifying a recurring event in Chandler, we need to support a way to modify a single event, the entire series or all future events.
Deletions: If we are deleting a recurring event in Chandler, we need to be able to delete a single event, the entire series or all future events.
Performance: Event related queries, such as find all events overlapping a given week, month, or day, must not cause visual delay in the CPIA layer when hundreds of recurring events are defined.
Assumptions
- For 0.6 there will be affordances in Chandler for both specifying infinite recurrence and recurrence that ends on a specific date. We have decided that this is too basic not to have in 0.6.
High-level Decisions
Chandler will model recurring events on iCalendar and iTIP. Using iCalendar's semantics for recurrence rules will allow recurring events to be smoothly shared on CalDAV servers.
To provide interoperability with non-Chandler clients, almost all iCalendar recurrence features will eventually be made available at the repository level. Most of these features will be available in 0.6. See the cuts section for omitted features and the permanent cuts section for areas where Chandler will diverge from iCalendar.
While most iCalendar recurrence rules will function in the repository in 0.6, only a few of these rules will be exposed to the user. It's possible that some infrequently used rules will never be exposed to the user.
Terminology
- rrule
- A valid iCalendar recurrence rule. The rule must have at least one date and time. It may repeat indefinitely, terminate on a particular date, or repeat N times, where N is a positive integer. Example: Every Monday at 9PM (UTC).
- rruleset
- A recurrence rule set. Multiple rrules can apply to the same event, the full collection of rules for a particular event is the event's rruleset. Example: Every Monday and every other Thursday at 9PM (UTC).
- exrule
- An exclusion rule. An event's rruleset can include both rrules and exrules. Exclusion takes precedence over inclusion. Example: Every Monday at 9PM (UTC) excluding December and June.
- occurrence
- An occurrence is an event with exactly one date and time.
- icalUID
- The iCalendar UID, distinct from a Chandler item's UUID, is shared by every occurrence in a recurring event's rruleset.
- recurrenceID
- A datetime specifying which instance of a rruleset is referred to.
- master
- A recurring event which occurrences and modifications are derived from.
- modification
- Master events can have a set of modifications. A modification to a master event shares its icalUID with the master and specifies recurrenceID and whether the modification applies to one event, or all future events. Example: Every Monday at 9PM (UTC), except Monday, January 3rd, 2005's meeting should be on January 4, 2005 with description "Office New Years Party!".
Modifications
There are (at least) two major ways users might look at modifications to recurring events.
- Treat exceptions to rules as entirely new events, coupled loosely, if at all, to the original rule
- Treat exceptions to rules as part of the original rule, so actions like delete, when applied to the whole event, apply to modifications
The second approach doesn't preclude the first approach, a
particular occurrence of a recurring event can be deleted and an
unrelated event can be created in its place, admittedly, with a little
more effort. In 0.6, Chandler will take the first approach. Once an
occurrence in a series has been edited, it is considered detached from
the series. All THISANDFUTURE changes to the event series do not affect
occurrences that have been detached from the series.
Issue:
[Mimi] For events that have been
detached, can we put the text (detached event) next to the recurrence
rule drop-down (a la iCal).
User Interface/Interactions
Importing a calendar
The existing 0.5 workflow will be used for importing a calendar with recurring events. Each event in the recurring series should display on the calendar. For 0.6 we will not provide visual feedback in the calendar view indicating that an event recurs (other than the event appearing multiple times in the calendar). Any indication that an event recurs will be provided in the detail view only.
When we click on a recurring event and bring up the detail view, we should try to map the recurrence rule on the imported event to the available recurrence parameters in Chandler (Daily, Weekly, Monthly, Yearly). If the event was created using a more complex recurrence algorithm than those available in Chandler, the Custom option will be selected and a some static text describing the details of the recurrence rule will be displayed below the "occurs" drop down field. This static text field has no label.
If an imported event has a custom recurrence algorithm, we will only allow the user to modify the recurrence rule insofar as they can change (from the "occurs" pulldown) the recurrence rule to one of Chandler's basic recurrence rules. We will warn user when they attempt to change custom recurrence rules in order to let them know that the more complex custom recurrence rule will be lost when they make a change.
The following diagram displays a detail view for an imported event that has a custom recurrence algorithm. If this example is the first one in the series, it occurs on Wed, Thurs, Fri 3:00-4:00pm for 5 weeks starting 11/02/05. The user can change the occurs drop down but CANNOT reselect the custom option once they have changed it to something else.

Exporting a calendar
The existing 0.5 user workflow will be used for exporting a calendar with recurring events.
Publishing and subscribing to a calendar
See the Sharing spec for details regarding publishing and subscribing to a shared calendar. Since we are planning to publish event information in iCalendar format, we will be using the same rules for import/export, to map recurrence information. We don't expect recurrence to affect the publishing and subscribing workflows.
Creating a recurring event
A new event is created in Chandler using the same workflow as in 0.5. The recurrence parameters for an event can be set in the detail view. A new occurs pulldown will be added to the detail view with the following options.
- Once (default when we create an event)
- Daily
- Weekly (every week, on this day of the week)
- Biweekly (every other week, on this day of the week)
- Monthly (every month, on this date - if a month doesn't have this date ie: the 31st, then there is no recurrence in that month)
- Yearly (every year, on this month and date - if we pick Feb 29th, the event will only appear every 4 years)
- Custom (selected if the event was imported and has a more complex recurrence rule). This option is always greyed out.
If the Once option is selected, no more recurrence fields show up below the "occurs" drop down..
If the user selects, Daily, Weekly, Biweekly, Monthly or Yearly a text field labelled ends appears below the drop down with the grey hint text MM/DD/YY. If the user enters a valid date ie: 05/06/06, the recurrence ends on the specified date (including the end date). If the user DOES NOT enter a valid date, the grey hint text returns once the user tabs or clicks out of the "ends" field. Leaving the ends field populated with the hint text is equivalent to having infinite recurrence. If this fields previously contained a date, we can delete the text to change to infinite recurrence in which case the ends field displays MM/DD/YY.
The user can never explicitly select the Custom option in Chandler. The option is selected automatically if the event has been imported and we cannot map the recurrence algorithm to Daily, Weekly, Montly or Yearly (Chandler options). If the user tries to select the Custom option, the pulldown defaults back to "Once".
The above description handles the behavior of the occurs, ends and static rule text (custom) fields only. Please refer to the Calendar Spec for a detailed description of the defaults for all 4 date time fields when we create a new event.
The screenshot below displays the recurrence fields for an event created in Chandler. Let's assume this is the first event in the series. In the example below the event occurs weekly from 3:00pm-4:00pm starting 11/02/05 and ends on 11/30/05.

Displaying recurring events in the summary table view
Regardless of whether we import or subscribe to a calendar with recurring events or create a new recurring event, we need to be able to display recurring events in the summary table view. In 0.6 the summary table view will display recurring events as a single item.
Deleting a recurring event
Deleting a recurring event can be done by using the menus or the delete key (scenarios outlined in the item and deletion spec). The user should be prompted with a dialog asking if they want to delete this single occurence, the selected and future events or all occurrences in the series.

- Deleting just this event causes an exclusion date to be added to the recurrence rule
- Deleting all future events deletes any future detached events and set the recurrence rule to last until the previous occurrence
Modifying a recurring event
An existing recurring event can be modified using a number of different scenarios.
- Making a change to the event information by editing the detail view directly.
- Making a change to the event on the calendar view (ie: moving the event to a different day/time).
- Stamping an event as a message or task.
- Unstamping an event as a message, task or event.
Any of the above scenarios can apply to recurring events created using Chandler or recurring events imported into Chandler that have basic or custom recurrence.
Modifications in the detail view
A user can modify any event information in the detail view (unless it's a read only event). The user can modify the stamps, privacy setting, email fields, title, location, all-dayness, date/time information, event status, recurrence parameters, alarm settings and notes field of an event. According to the detail view spec, the "modification" occurs when you click, tab out, select something else or hit enter on the field that has changed. Bryan Sterns can provide more detail here. The types of changes to recurring events fall into 2 categories.
If the user changes any of the following fields: title, location,
all-dayness, date/time information, event status, alarm settings and
notes, they will be prompted by a dialog asking if they want to apply
this change to the current event or the current and all future events.
In this case, either a single event or the current and future will be
modified. It should not matter whether the event has a custom
recurrence rule or a Chandler recurrence rule. The
current/current+future dialog should only pop up once per detail view
editing session, but should pop up for every modification in the
calendar view.
For 0.6, we will be giving the user the ability to modify the selected single event or the selected event and all future in the series. We will not provide an option for modifying the entire series.
For 0.6 we don't see a pressing need to provide visual feedback in the recurrence rule pulldown that exceptions to the recurrence rule have been made. This is true of both the detail view for the excepted event and the detail view of the other events in the event series.
A user can also modify the recurrence rule directly by changing the recurrence parameters in the "occurs" dropdown. In 0.6, doing so does not trigger a confirmation dialog. The change applies to this and future events in the series:
- If the modified event is NOT the first event in the series, the previous events in the series KEEP the old recurrence rule and are assigned a new end date that corresponds to the last recurrence of the event BEFORE the modified event.
- The current event and all future events in the series are assigned the new recurrence rule.
- For example: An event series has a recurrence rule of occurs -Weekly, ends -
Never. The user modifies the recurrence rule on the 3rd recurrence to
become, occurs: Monthly, ends 06/06/07. The 1st and 2nd events in the
event series should still display a recurrence rule of, occurs: Weekly,
but with a new end date of the date of the second event in the series
. The 3rd and all subsequent events in the event series should display a recurrence rule of, occurs: Monthly, ends: 06/06/07. - If any occurrence of an event in a recurring event series has
it's recurrence rule changed to None, then all occurrences of that
event in the event series are deleted except for the first occurrence
without warning.
If the event had custom recurrence, Chandler will lose all custom recurrence parameters and the user cannot go back to their imported custom recurrence rule. We will warn the user accordingly about this change.
Modifications in the calendar view
Changes to events in the calendar view should behave in the same manner as the user editing the date time information in the detail view.
Unstamping a recurring event
If we are displaying the detail view for a recurring event and we unstamp it as an event, eventually this should remove this event from the calendar using the same workflow as for modifications.

In 0.6, the All events and All future events options are greyed out, so the only options are Just this event and Cancel.
Stamping a recurring event
In Chandler, a user can stamp any type of event item to be a message and/or task as well. The event could be a recurring event. In 0.6, the only option for stamping recurring events is to stamp a single occurrence.
Drag and drop recurring events
Dragging and dropping to add events to other calendars will be
available in 0.6. To keep this very simple for 0.6, if we drag and drop
any event that is part of a recurring series onto another calendar, we
should automatically create the entire series on the second calendar.
This means that we can see the series from more than one calendar and
making changes in one, automatically updates the other calendar since
they are the same events.
Ideally, Mimi would like to be able to have a single event in a
series dragged to another calendar (remaining attached) and have it's
modification update in the other calendar. The workflow here is: there
is a recurring weekly services team meeting, I never attend but I want
to attend next week to talk about performance. I drag this single
instance to my calendar. Later, Lisa changes the time of the meeting
and I would want the instance that I have to update automatically.
Sharing calendars that have recurring events
There are no special UI behaviors for recurring events on a shared calendar. When viewing and modifying these events in Chandler, the behavior should be the same as with non-shared calendars.
Architecture
Overview
There are three connected pieces that make up a complete recurring event: master, modifications, and occurrences. There are various ways these pieces could be modeled. For 0.6, all three pieces are modeled using the same mixin Kind, CalendarEventMixin. The distinction between the three is determined by the modifications <-> modificationFor and occurrences <-> occurrenceFor bidirectional references linking CalendarEventMixins together.
Occurrences
Any CalendarEventMixins can act as an occurrences, including a master event.
Occurrences beyond the master are automatically generated by calendar code for the relevant range, if they don't already exist. These occurrences may become invalid, so outdated generated occurrences are automatically deleted when overriding modifications are made.
Changing Events
CalendarEventMixins can be modified just like other Chandler items. However, when an attribute on a recurring item is modified, that modification may be intended to apply to just that occurrence, or all future occurrences. The changeThis or changeThisAndFuture methods should be used to change attributes on recurring events. If an attribute on a recurring event is changed directly, it will cause the changeThis method to be called.
UI code needs to get user feedback to determine whether future occurrences should be changed. To enable this, UI code manipulates Python proxies around items, using normal attribute setting. If the item recurs, the proxy buffers change attribute setting calls until the user clicks a range option on a dialog. If the item doesn't recur, the proxy passes attribute changes through to the item transparently.
Changes to one item which should change other items in the recurrence set are immediately propagated by the onValueChanged hook.
Modifications
In a slight divergence from the iCalendar model, for 0.6 at least, modifications are modeled as complete items. In iCalendar, a modification that changes one attribute wouldn't need to list any attributes that were unchanged, other attributes would be inherited. In Chandler, modifications need to be viewable in the detail view, so modification items duplicate all unmodified attributes from the occurrence they're based on.
Because Chandler doesn't track per attribute modifications, in 0.6, THISANDFUTURE changes do not apply to items which have already been modified
In 0.6, THISANDFUTURE modifications create entirely new items, with different icalUIDs from the original series. This is way Apple's iCal treats THISANDFUTURE modifications, and it seems adequate for most use cases.
Recurrence Data Model
RecurrenceRule (Kind)
Represents one iCalendar RRULE or EXRULE.
RecurrenceRuleSet (Kind)
Aggregates RRULEs/EXRULEs (items of kind RecurrenceRule), and RDATEs/EXDATEs (datetimes).
Instance methods:
- createDateUtilFromRule(startTime) -> create an appropriate dateutil.rrule.rruleset instance, which provides a (possibly infinite) iterable for all occurrence startTimes and a between(start, end) method returning occurrence startTimes, if any, for the given range. Note that startTime is a required argument, startTime isn't an attribute of a RecurrenceRuleSet
- setRuleFromDateUtil(rruleset) -> modify the RRuleSet item given a dateutil.rrule.rruleset object
FrequencyEnum (Enum)
secondly, minutely, hourly, daily, weekly, monthly, yearly. Only the last four are offered as options in 0.6 UI.
CalendarEventMixin (Kind)
Key attributes:
- modifications - set of modifications or None
- icalUID - unicode, identical for all CalendarEvents in the same rule
- rruleset - RecurrenceRuleSet or None
- startTime - DateTime for the first occurrence, may be different from recurrenceID, may or may not have a timezone
- duration - DateTimeDelta or None
- occurrences - links to self (the first occurrence) and additional occurrences not overridden by modifications
- occurrenceFor - inverseAttribute to occurrences, often links to self, may not exist if the first occurrence is modified
- modificationFor - Master or modification rule this event modifies, None if this is a master
- recurrenceID - DateTime for the first occurrence overridden by this modification, often equivalent to startTime
Instance methods:
- changeThis(attribute, value) -> make a modification that applies only to this occurrence
- changeThisAndFuture(attribute, value) -> create a new recurrence series starting with the current event, existing modifications are unchanged
- createDateUtilFromRule() -> convenience method, returning self.rruleset.createDateUtilFromRule(self.dtstart) or if self.rruleset is None, return None
- setRuleFromDateUtil(rule) -> create an appropriate RecurrenceRuleSet from a dateutil rrule or rruleset, set it to self.rruleset
- getMaster() -> convenience method, returning self if modificationFor is None, or self.modificationFor.getMaster()
- deleteAll() -> delete all modifications and occurrences for this event, delete self
- deleteThis() -> remove this item, exclude its recurrenceID from the parent rule
- deleteThisAndFuture() -> remove this item, delete future occurrences and modifications, modify master's rule to end before this occurrence
- getOccurrencesBetween(start, end) -> check for virtual events that end after start and start before end, create any that don't already exist, return an iterable of events ordered by startTime
- isCustomRule() -> return boolean depending on whether the rule must be represented as custom in the UI
- getCustomDescription() -> return a string describing the recurrence rule, like "TuTh every second week for 5 weeks", or "complex" if no description is available for the rule
- getNextOccurrence(after=None, before=None) -> find, or if necessary create, the next occurrence after self.startTime, or in the given range
Note that duration is relevant for getOccurrencesBetween, it returns events that begin before start, but end after start, not just events that are completely contained within the given range.
Reminders
See the Reminder spec for updated reminder semantics.
When a new occurrence in a recurring series is generated, its reminders should be checked to see if it's already in the past. Any reminders in the past should be automatically set to the expiredReminders attribute instead of the reminders attribute.
Sample code
Create an event.
>>> eventPath =
'//parcels/osaf/contentmodel/calendar/CalendarEvent'
>>> eventKind = view.findPath(eventPath)
>>> A = eventKind.newItem()
>>> A.displayName = "start title"
>>> A.startTime = datetime.datetime(2005, 6, 18, 9)
>>> A.duration = datetime.timedelta(hours=6)

Add a recurrence rule.
>>> A.setRuleFromDateUtil(dateutil.rrule.rrule(dateutil.rrule.DAILY, count=5))

Now generate a few occurrences by calling getOccurrencesBetween.
>>> someJuneOccurrences = A.getOccurrencesBetween(datetime(2005, 6, 18, 14), datetime(2005, 6, 20, 14))
>>> [i.startTime for i in someJuneOccurrences]
[datetime.datetime(2005, 6, 18, 9, 0, 0), datetime.datetime(2005, 6, 19, 9, 0, 0), datetime.datetime(2005, 6,
20, 9, 0, 0)]

getOccurrencesBetween returns the initial occurrence, even thought it starts before 2PM on June 19, but not all 5 occurrences. Now lets modify a generated occurrence.
>>> A.getNextOccurrence().changeThis('displayName', 'changed')
>>> [i.displayName for i in A.getOccurrencesBetween(datetime(2005, 6, 18, 14), datetime(2005, 6, 20, 14))]
['start title', 'changed', 'start title']

Cuts
THISANDFUTURE changes are not persisted in 0.6, instead, they create new recurrence series.
In 0.6, the following iCalendar sharing-related attributes are not implemented:
- sequence
- The iCalendar sequence of an event, defaults to 0. Any time a change is made to an event its sequence should be incremented.
- organizer
- In iTIP, the single user allowed to make changes and approve suggested changes to a group event.
Finally, imported or shared recurring events with modifications having range THISANDPRIOR probably won't be supported in 0.6. Events with THISANDPRIOR modifications can be represented as events with range THISANDFUTURE, but the conversion routine to do this is a low priority, because THISANDPRIOR is almost never seen in the wild.
Permanent cuts
iCalendar allows events to have either a starting time and and ending time or a starting time and a duration. For single events, either method of describing duration is equivalent. For recurring events, the two techniques diverge if an occurrence spans daylight savings time (10PM-4AM occasionally lasts 5 or 7 hours). Chandler should always use duration.
History
| Author | Edit date | Description |
|---|---|---|
| Sheila Mooney | April 15, 2005 | First Draft |
| Sheila Mooney | April 19, 2005 | Second Draft after review with apps team |
| Sheila Mooney | May 2, 2005 | Edits to second draft based on feedback from apps review. |
| Sheila Mooney | May 2, 2005 | Modification to summary table view display for recurring events. |
| Jeffrey Harris | May 6, 2005 | Added details about recurrence framework choices. Removed simplifying assumption that modifications to future events wouldn't be supported. |
| Sheila Mooney | May 11, 2005 | Some modifications based on feedback and clarification from Jeffrey. |
| Sheila Mooney | May 13, 2005 | Updates from Mimi. |
| Jeffrey Harris | May 24, 2005 | Modified issues, updated architecture section |
| Jeffrey Harris | June 18, 2005 | Updated architecture section, reminders, added sample code |
| Jeffrey Harris | June 22, 2005 | Tweaked the API based on Alec's suggestions |
| Jeffrey Harris | June 28, 2005 | Added API calls |
| Sheila Mooney | July 17, 2005 | Clarified behavior of the recurrence ends field and added
some comments to outstanding questions from Jeffrey |
| Mimi Yin |
August 08, 2005 | Clhanged behavior of detached items with respect to
THISANDFUTURE modifications. See Modifications sections for more
details. |
| Jeffrey Harris | December 07, 2005 | Overhauled the spec to reflect how recurrence actually works in 0.6 |