Contents

Backend Development › Product Building Blocks

Recurring Events

Repeating schedules, RRULE, and their time zone edge cases.

Also known as: recurring events, recurrence, repeating events

Recurring events are things that happen repeatedly on a rule — a weekly stand-up, a monthly bill, an event “every second Tuesday” — rather than once. Modelling recurrence correctly is surprisingly tricky, because rules have exceptions, end conditions, and time-zone/DST complications.

The standard approach is a recurrence rule (the iCalendar RRULE format is the common standard): a start, a frequency (daily/weekly/monthly/yearly), an interval, days of week/month, and an end (count or until date). Store the rule, not a materialised list of every occurrence.

RRULE: FREQ=WEEKLY;BYDAY=TU;INTERVAL=2;UNTIL=20261231
  → every second Tuesday until the end of 2026

You then expand occurrences as needed (for a calendar view or to schedule the next run), applying exceptions (a cancelled instance, a moved one).

The classic mistakes:

  • Storing every occurrence instead of the rule. Materialising years of occurrences is wasteful and breaks when the rule changes. Store the rule and expand on demand (or materialise a bounded window).
  • Ignoring time zones and DST. “9:00 every day” in a user’s zone shifts across daylight-saving changes; a naive UTC-only model fires at the wrong local time (see timezone). Store the local time and zone.
  • Month-end edge cases. “Monthly on the 31st” has no 31st in February; rules must define what happens (skip, or use the last day). Undefined behaviour here causes missing or duplicate events.
  • Exceptions not modelled. Real series have cancellations and one-off modifications (“this week it’s at 10”); without an exception model, you can’t represent reality.
  • Endless recurrence. An infinite rule needs a bound when expanding (a window), or you loop forever.
  • Overlapping with reminders. Reminder notifications for an event are separate scheduled jobs; don’t confuse the recurrence rule with the notification schedule (see scheduled jobs).
  • Recomputing the rule differently in each place. If the calendar view, the reminder job and the report each interpret the rule, they disagree. Centralise expansion.

How to model it: store a recurrence rule (start, frequency, interval, end) plus exceptions, keeping the local time and zone; expand occurrences over a bounded window for display and scheduling; and centralise the expansion logic. It’s a case where the “obvious” model (a list of dates) fails as soon as rules change or time zones and DST intervene — the rule-plus-exceptions model handles reality. See cron.