Skip to content

Fix: create_calendar_event silently fails for timezone offsets#45

Closed
JonathanGodley wants to merge 1 commit into
MadLlama25:mainfrom
JonathanGodley:fix/caldav-ical-encoding
Closed

Fix: create_calendar_event silently fails for timezone offsets#45
JonathanGodley wants to merge 1 commit into
MadLlama25:mainfrom
JonathanGodley:fix/caldav-ical-encoding

Conversation

@JonathanGodley

Copy link
Copy Markdown
Contributor

Summary

create_calendar_event returns a success response with a UID, but the event is never persisted on the CalDAV server when the input datetime includes a timezone offset (e.g. +10:00). No error is thrown.

Root cause: The naive event.start.replace(/[-:]/g, '') strips colons from timezone offsets, producing DTSTART:20260407T184500+1000 — which is not a valid iCalendar datetime form (RFC 5545 §3.3.5 requires UTC with Z, floating, or TZID parameter). The server silently rejects the PUT while tsdav does not throw.

This PR also fixes two related iCalendar encoding issues:

  • No line folding: Long SUMMARY/DESCRIPTION/LOCATION values exceeded 75 octets per line, violating RFC 5545 §3.1
  • unescapeICalText ordering bug: The sequential .replace() chain incorrectly decodes \n (escaped backslash + letter n) as a newline instead of \n. This is an interop issue — it corrupts text when parsing events created by other CalDAV clients (Apple Calendar, Thunderbird, etc.) that correctly escape literal backslashes per RFC 5545 §3.3.11

Changes

  • Add toICalUTC() — converts ISO 8601 offset datetimes to UTC, preserves floating times
  • Add foldICalLine() — folds content lines at 75 UTF-8 octets with surrogate pair safety
  • Fix unescapeICalText() — single-pass regex avoids ordering ambiguity between \ and \n
  • Update createCalendarEvent to use toICalUTC and foldICalLine

Test plan

  • 6 tests for toICalUTC (positive/negative offsets, UTC, floating, invalid input, boundary crossing)
  • 5 tests for foldICalLine (short passthrough, basic fold, multi-segment, 75-octet enforcement, multi-byte emoji)
  • 7 tests for unescapeICalText (basic unescaping, round-trip with escapeICalText, \n and \, edge cases)
  • 1 end-to-end round-trip test (create iCal → parse back → verify all fields survive)
  • All 48 CalDAV tests pass
  • Functionally tested against live Fastmail CalDAV server — event persists and reads back correctly

The CalDAV PUT is silently rejected because timezone offsets are mangled
by the naive replace(/[-:]/g, '') — e.g. +10:00 becomes +1000, which is
not a valid iCal datetime form. Events appear to create successfully but
are never persisted.

Add toICalUTC() that converts offset datetimes to UTC and preserves
floating times. Add foldICalLine() that folds at 75 UTF-8 octets per
RFC 5545 §3.1, with surrogate pair safety. Fix unescapeICalText()
ordering bug by switching to single-pass regex (sequential .replace()
incorrectly decoded \n as newline instead of literal backslash + n).
@MadLlama25

Copy link
Copy Markdown
Owner

Closing as superseded: the timezone-offset silent failure was fixed in v1.9.3 (validateAndFormatICalDate normalizes offsets to UTC), the unescapeICalText ordering bug landed via PR #81 in v1.9.5, and RFC 5545 §3.1 line folding shipped with the PR #53 calendar overhaul in v1.11.0. All three of this PR's fixes are now on main — thanks for the thorough diagnosis, it drove each of them.

@MadLlama25 MadLlama25 closed this Jul 6, 2026
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