How does a calendar platform handle recurring events (every Tuesday, except holidays) across timezones, generating occurrences on-the-fly with exception handling without storing millions of individual instances?
Core challenge: "Every Tuesday at 10am" seems simple. But: which timezone? What about DST transitions? What if one occurrence is deleted? What if the series is modified "this and future"? Storing every instance wastes space. Computing on-the-fly requires complex RRULE evaluation.
1B+
calendar events
many are recurring
RRULE
recurrence spec
RFC 5545 (iCalendar)
500+
timezones (IANA)
DST rules change yearly
On-the-fly
occurrence generation
not pre-stored
Architecture · RRULE Engine + Exception Handling
Concept
Storage
How It Works
Recurring Event
1 row: RRULE + DTSTART + TZID
RRULE:FREQ=WEEKLY;BYDAY=TU;UNTIL=20251231 · generate occurrences on query
Exception (delete one)
EXDATE list on parent
EXDATE:20240319T100000 · skip this occurrence during generation
Exception (modify one)
Separate event with RECURRENCE-ID
Override instance: different time/title but linked to parent series
"This and future"
Split into 2 series
Original gets UNTIL before split. New series starts from split point with new RRULE.
Timezone
Store as local time + TZID
"10:00 America/New_York" · compute UTC at query time using current TZ rules
DST transition
TZID handles automatically
10am EST ? 10am EDT (same local time, different UTC offset). Wall-clock time preserved.
Why not store all instances? "Every weekday forever" = infinite instances. Even "every day for 5 years" = 1,825 rows per event. With 1B recurring events, that's trillions of rows. Instead: store 1 RRULE, generate occurrences for the requested date range on-the-fly. Cache materialized windows (e.g., next 6 months).
Query pattern: "Show me events for March 2025" ? ? Fetch all non-recurring events in range ? Fetch all recurring events whose DTSTART = end AND (no UNTIL or UNTIL = start) ? Expand RRULEs within range ? Apply EXDATEs ? Merge overridden instances ? Sort by time.
Pitfalls:Storing in UTC · "10am meeting" shifts when DST changes (store local + TZID instead). Unbounded RRULE expansion · always limit to requested window. TZ rule changes · countries change DST rules; must use latest IANA tzdata. Floating time · "all-day events" have no timezone (date only).
Real-world:Google Calendar · RFC 5545 RRULE engine, stores local time + TZID. Outlook · similar, with Exchange-specific extensions. Apple Calendar · CalDAV + RRULE. Temporal · cron-based scheduling for distributed systems (different problem, same timezone challenges).
Interview Cheat Sheet
The 6 things to say for calendar/scheduling design
1.Store RRULE, not instances · "every Tuesday" = 1 row, generate occurrences on-the-fly 2.Store local time + TZID · never store UTC for recurring events (DST breaks it) 3.EXDATE for exceptions · delete one occurrence without breaking the series 4."This and future" = split series · original gets UNTIL, new series starts from split point 5.Query: expand RRULEs within requested window · never unbounded expansion 6.IANA tzdata updates · countries change DST rules; must use latest timezone database