Skip to content

fix(caldav): consistent UIDs and VTIMEZONE in iCalendar output#28115

Merged
anikdhabal merged 5 commits intocalcom:mainfrom
yuvrajangadsingh:fix/caldav-uid-vtimezone
Feb 21, 2026
Merged

fix(caldav): consistent UIDs and VTIMEZONE in iCalendar output#28115
anikdhabal merged 5 commits intocalcom:mainfrom
yuvrajangadsingh:fix/caldav-uid-vtimezone

Conversation

@yuvrajangadsingh
Copy link
Copy Markdown
Contributor

Follow-up to #22434 — addresses the two remaining CalDAV interop issues flagged in #28112.

What changed

1. UID consistency

createEvent was always generating a fresh uuidv4(). CalDAV servers use the UID as the canonical event identifier, so when cal.com's email invitation carried a different UID than the CalDAV object, recipients ended up with duplicate entries. Now we use event.uid || uuidv4() so the booking's UID flows through to the .ics file.

2. VTIMEZONE injection

The ics library outputs UTC times (DTSTART:20240115T140000Z) with no VTIMEZONE block. RFC 5545 §3.6.5 requires a VTIMEZONE component when DTSTART uses TZID. Without it, CalDAV servers like Fastmail fall back to UTC and send scheduling emails showing the wrong local time.

We now:

  • Rewrite DTSTART/DTEND from UTC to DTSTART;TZID=America/Chicago:20240115T080000 (organizer's timezone)
  • Build and inject a proper VTIMEZONE with STANDARD/DAYLIGHT components
  • Binary-search actual DST transitions for the event's year (not 1970), so zones that changed rules post-2007 get correct BYDAY/BYMONTH
  • Handle Southern Hemisphere timezones (Australia/Sydney) where DST polarity is reversed

Both fixes apply to createEvent and updateEvent.

Tests

10 new tests covering:

  • UID: uses event.uid when provided, falls back to uuidv4(), filename matches UID
  • VTIMEZONE: block present, placed before VEVENT, DTSTART uses TZID, correct UTC→local conversion, valid 8-digit dates in RRULE, Southern Hemisphere DST, updateEvent parity

All 24 tests pass (TZ=UTC yarn vitest run packages/lib/CalendarService.test.ts).

Fixes #9485

…tput

Two remaining CalDAV interop issues from calcom#9485:

1. UID consistency: use the booking's canonical UID (event.uid) instead
   of always generating a new random UUID. CalDAV servers use UID as the
   event identifier — different UIDs cause duplicate calendar entries.

2. VTIMEZONE injection: the ics library generates UTC times with no
   VTIMEZONE block. CalDAV servers like Fastmail read this as UTC and
   send scheduling emails with wrong times. Per RFC 5545 §3.6.5,
   DTSTART with TZID requires a matching VTIMEZONE component. We now
   build a proper VTIMEZONE using binary-searched DST transitions for
   the event's year, handling Northern/Southern hemisphere correctly.
@github-actions github-actions bot added $500 caldav area: caldav, fastmail, Baïkal, Kerio, mailbox, nextcloud Low priority Created by Linear-GitHub Sync 🐛 bug Something isn't working 💎 Bounty A bounty on Algora.io labels Feb 21, 2026
@yuvrajangadsingh yuvrajangadsingh marked this pull request as ready for review February 21, 2026 08:31
@graphite-app graphite-app bot added the community Created by Linear-GitHub Sync label Feb 21, 2026
@yuvrajangadsingh
Copy link
Copy Markdown
Contributor Author

@anikdhabal here's the follow-up you asked for on #22434. 2 files changed, all 24 tests passing with TZ=UTC.

for the UID fix — just event.uid || uuidv4() so the booking's canonical UID flows through to the .ics file. prevents the duplicate calendar entries fastmail/nextcloud users were seeing.

for VTIMEZONE — instead of hardcoding US-specific DST rules, we binary-search the actual transition moments for the event's year using dayjs timezone data. this handles southern hemisphere zones (australia/sydney where DST polarity is reversed) and zones that changed rules post-2007 (US moved from 1st sunday in april to 2nd sunday in march). the VTIMEZONE block gets injected before BEGIN:VEVENT, and DTSTART/DTEND get rewritten from UTC to TZID-qualified local times.

both fixes apply to createEvent and updateEvent. used #28112 as reference.

@graphite-app
Copy link
Copy Markdown

graphite-app bot commented Feb 21, 2026

Graphite Automations

"Send notification to Community team when bounty PR opened" took an action on this PR • (02/21/26)

2 teammates were notified to this PR based on Keith Williams's automation.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/lib/CalendarService.ts">

<violation number="1" location="packages/lib/CalendarService.ts:202">
P2: VTIMEZONE DTSTART is computed using the post-transition offset, so the generated onset time is shifted by the DST change (e.g., 03:00 instead of 02:00). RFC 5545 requires the DTSTART local time of the onset (interpreted with the pre-transition offset), so this produces incorrect DST boundaries in CalDAV clients.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

yuvrajangadsingh and others added 3 commits February 21, 2026 14:24
The DTSTART in VTIMEZONE components must represent the local time
interpreted with the pre-transition offset (TZOFFSETFROM), not the
post-transition offset. For example, US Eastern spring forward DTSTART
should be 02:00 (EST), not 03:00 (EDT).
Removed comments about UID handling for calendar events.
Update injectVTimezone function documentation to clarify UTC handling.
Copy link
Copy Markdown
Contributor

@anikdhabal anikdhabal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@anikdhabal anikdhabal added the run-ci Approve CI to run for external contributors label Feb 21, 2026
@anikdhabal anikdhabal enabled auto-merge (squash) February 21, 2026 14:31
@anikdhabal anikdhabal merged commit f1e8e3f into calcom:main Feb 21, 2026
63 of 68 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💎 Bounty A bounty on Algora.io 🐛 bug Something isn't working caldav area: caldav, fastmail, Baïkal, Kerio, mailbox, nextcloud community Created by Linear-GitHub Sync Low priority Created by Linear-GitHub Sync ready-for-e2e run-ci Approve CI to run for external contributors size/XL $500

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CalDAV integration with Fastmail is generating duplicate, erroneous invitation emails.

2 participants