Skip to content

feat(events): support RRULE recurrence in create/update/list#94

Open
realitix wants to merge 1 commit into
TKasperczyk:mainfrom
realitix:feat/events-rrule
Open

feat(events): support RRULE recurrence in create/update/list#94
realitix wants to merge 1 commit into
TKasperczyk:mainfrom
realitix:feat/events-rrule

Conversation

@realitix

@realitix realitix commented May 12, 2026

Copy link
Copy Markdown

Summary

Adds RRULE recurrence support to createEvent, updateEvent, and
listEvents.

Changes

  • createEvent accepts an optional recurrence parameter taking an
    iCal RRULE string (e.g. FREQ=WEEKLY;BYDAY=MO,TU,TH,FR or
    RRULE:FREQ=DAILY;COUNT=10). The RRULE: prefix is optional and added
    automatically if missing. Invalid rules are rejected with a clear error.
  • updateEvent accepts the same recurrence parameter. Empty string
    or null clears the existing recurrence and turns the event into a
    one-shot; a non-empty string replaces the rule.
  • listEvents / formatEvent now exposes the RRULE in clear text
    (without the RRULE: prefix) under a recurrence field on the master
    event, alongside the existing isRecurring boolean.

Implementation notes

  • cal.createRecurrenceInfo() and cal.createRecurrenceRule() are not
    exposed on the cal namespace in Thunderbird's calUtils. The PR uses
    the XPCOM contracts (@mozilla.org/calendar/recurrence-info;1 and
    @mozilla.org/calendar/recurrence-rule;1) like the rest of the codebase
    already does for other calIxxx instances.
  • Two small helpers were extracted to keep createEvent / updateEvent /
    formatEvent readable:
    • setRecurrenceOnItem(item, recurrence)
    • extractRRuleFromItem(item)

Test plan

  • createEvent with recurrence: "FREQ=WEEKLY;BYDAY=MO": event appears
    every Monday in Thunderbird's calendar UI.
  • createEvent with recurrence: "RRULE:FREQ=DAILY;COUNT=5": 5
    occurrences only.
  • createEvent with recurrence: "GARBAGE": returns
    { error: "Invalid recurrence rule: ..." }.
  • updateEvent with recurrence: "": existing recurring event becomes
    a one-shot.
  • listEvents on a recurring master returns
    recurrence: "FREQ=WEEKLY;BYDAY=MO" (no RRULE: prefix) on the
    expanded occurrences.
  • Non-recurring events return recurrence: null.

- createEvent accepts an optional 'recurrence' parameter taking an iCal
  RRULE string (the 'RRULE:' prefix is optional). Builds a
  calIRecurrenceInfo via XPCOM (@mozilla.org/calendar/recurrence-info;1
  and @mozilla.org/calendar/recurrence-rule;1) and attaches it to the
  event before saving. Invalid rules are rejected with a clear error.
- updateEvent accepts the same 'recurrence' parameter. An empty string
  or null clears the existing recurrence and turns the event into a
  one-shot; a non-empty string replaces the rule.
- listEvents: formatEvent now exposes the RRULE in clear text (without
  the 'RRULE:' prefix) under a 'recurrence' field on the master event,
  alongside the existing 'isRecurring' boolean.

Implementation note: cal.createRecurrenceInfo() and
cal.createRecurrenceRule() are not exposed on the cal namespace in
Thunderbird's calUtils, so we use the XPCOM contracts directly like the
rest of the codebase already does for other calIxxx instances.
@TKasperczyk

TKasperczyk commented May 27, 2026

Copy link
Copy Markdown
Owner

Thanks @realitix. Few items before merge:

  • After feab2c7 listEvents expands recurring masters and passes occurrence proxies to formatEvent. extractRRuleFromItem(item) only reads item.recurrenceInfo, but on occurrence proxies the recurrence lives on item.parentItem.recurrenceInfo -- expanded occurrences will show recurrence: null and isRecurring: false. (item.parentItem || item).recurrenceInfo fixes it.
  • extractRRuleFromItem strips the RRULE: prefix but doesn't trim the trailing CRLF that CalRecurrenceRule.icalString sometimes appends.
  • RRULE normalization is thin -- leading/trailing whitespace, lowercase rrule: prefix (currently produces invalid RRULE:rrule:...), whitespace-only input.
  • SECONDLY / MINUTELY parse via XPCOM but generate no occurrences. Reject upfront with a clear error rather than save a silently-broken event.
  • updateEvent schema documents clearing on empty string but the implementation/PR body also accepts null. Worth aligning.
  • setRecurrenceOnItem creates a fresh recurrenceInfo, discarding existing EXDATEs / modified exceptions. Probably fine semantically (replace rule = fresh series) but worth a note in the tool description.
  • A few RRULE tests would help -- COUNT vs UNTIL, BYDAY=MO,TU, BYMONTHDAY=15, prefix/no-prefix. See test/empty-trash-compat.test.cjs for the pattern.

Holding #95 until this lands since it stacks on top.

@TKasperczyk

Copy link
Copy Markdown
Owner

Thanks @realitix. Still need the items from the review above plus a rebase onto current main (showAs/categories/onlineMeeting landed since). The one that's a real bug rather than polish:

listEvents expands recurring masters and hands occurrence proxies to formatEvent, but extractRRuleFromItem only reads item.recurrenceInfo -- on an occurrence proxy that's null, the rule lives on item.parentItem.recurrenceInfo. So recurrence/isRecurring come back null/false on every expanded occurrence, which is the feature's main path. (item.parentItem || item).recurrenceInfo fixes it.

The rest: trim the trailing CRLF off the icalString, tighten RRULE normalization (a lowercase rrule: prefix + no trim currently yields RRULE:rrule:...), reject SECONDLY/MINUTELY upfront (they parse but generate no occurrences), align null-vs-empty-string clearing between schema and impl, note that setRecurrenceOnItem discards existing EXDATEs/exceptions, and a few RRULE tests. Holding #95 until this lands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants