Skip to content

VTIMEZONE + RRULE emission in iCal output (currently floating time + expanded instances) #338

@Salem874

Description

@Salem874

ELI5

Our calendar feed sends event times without saying what timezone they're in, and writes out every single occurrence of a repeating event one by one. Modern calendar apps expect a timezone block at the top and a "repeat weekly" rule for series, so subscribers in other timezones get the right local time and don't see 52 cluttering entries for a weekly meeting.

Detailed proposal

Extend web/_core/Ical.php (shipped via #271) to emit standards-compliant VTIMEZONE and RRULE blocks instead of the current floating-time, fully-expanded approach.

Concretely:

  1. Walk the result set, collect the distinct TZID values from tblEvents.timezone (column landed with Dynamic per-event/per-calendar timezone support with auto-detection from location #238's dynamic detection work) and prepend one VTIMEZONE component per distinct zone before any VEVENT — generated from PHP's DateTimeZone::getTransitions() so DST offsets are correct.
  2. For events bound to a series via tblEventSeries + tblEventRecurrence, emit a single VEVENT carrying DTSTART;TZID=..., RRULE:FREQ=...;INTERVAL=...;BYDAY=...;COUNT=... / UNTIL=... mapped from tblEventRecurrence columns, rather than one VEVENT per occurrence.
  3. Reserve an EXDATE;TZID=... line for skipped instances — wired up later when per-occurrence overrides (the sibling gap issue) land.
  4. Add a Ical::emitVTimezone(string $tzid): string helper and a Ical::emitRecurrenceRule(array $rrule): string helper; keep the current per-row path as a fallback for one-off events.
  5. No schema additions needed (columns exist); no new routes; no UI surface — feed consumers benefit transparently. Brand-aware: feed title/PRODID still pulled from Site::branding() as today.

Pros / Cons

Pros

  • Correct local time for subscribers in any timezone (Apple Calendar / Outlook / Google all honour VTIMEZONE).
  • Feed payload shrinks dramatically — a weekly year-long series goes from 52 VEVENTs to 1.
  • Brings us to parity with TEC's iCal output, the reference implementation in the WordPress space.
  • Foundation for EXDATE once per-occurrence overrides land.

Cons

  • VTIMEZONE generation from DateTimeZone::getTransitions() is fiddly to get byte-exact across DST boundaries.
  • RRULE mapping has edge cases (BYSETPOS, monthly-by-weekday, COUNT vs UNTIL precedence).
  • Existing subscribers may see one-off re-sync churn the first time the feed format changes.

How necessary?

Medium — feed users (cross-timezone speakers, congregants subscribing on iOS/Android) hit the bug today but workarounds exist.

Acceptance criteria

  • VTIMEZONE block emitted once per distinct TZID in the feed, with correct standard/daylight transitions.
  • Series-bound events emit a single VEVENT + RRULE mapped from tblEventRecurrence.
  • DTSTART / DTEND carry TZID=... (no more floating time).
  • One-off events continue to emit as today (regression-safe).
  • Round-trip test: subscribe in Apple Calendar from a non-UK timezone, confirm local time renders correctly and series shows as recurring.
  • EXDATE emission point stubbed for the per-occurrence-override follow-up.

Estimated effort

~6-8 hours focused work (VTIMEZONE helper is the bulk; RRULE mapping is mechanical; existing Ical.php is small).


Filed during The Events Calendar competitive analysis on 2026-06-16; decision pending.

Metadata

Metadata

Assignees

No one assigned

    Labels

    for considerationIdea parked for an owner decision before active workpriority: mediumNormal prioritytype: enhancementImprovement to existing functionality

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions