Skip to content

fix(mail, calendar): speak UTC on the wire, render in the viewer's time zone - #395

Draft
s-aga-r wants to merge 1 commit into
frappe:developfrom
s-aga-r:mail-calendar-utc
Draft

fix(mail, calendar): speak UTC on the wire, render in the viewer's time zone#395
s-aga-r wants to merge 1 commit into
frappe:developfrom
s-aga-r:mail-calendar-utc

Conversation

@s-aga-r

@s-aga-r s-aga-r commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Closes #56

Problem

Timestamps flowed through three different conventions: Stalwart speaks UTC (...Z or offset forms like -05:00), the virtual doctypes converted that into naive system-timezone strings, and the mail/calendar UIs then parsed those strings in the browser's zone. Whenever browser ≠ system zone, every displayed time was wrong — and calendar events pinned themselves to their stored wall clock (a 2–4 PM Asia/Kolkata event still showed 2–4 PM from Vienna).

Approach

One contract everywhere:

  • APIs speak and listen UTC ...Z (second precision). normalize_utc_z converts Stalwart's offset form and reads naive wire values as UTC. Virtual doctypes store/serve UTC; filters (before/after) listen UTC.
  • MariaDB-backed Datetime fields keep system timeto_utc_z/from_utc_z convert at the wire boundary.
  • UIs convert for display: browser zone → User.time_zone → system zone (get_user_info also exposes system_time_zone so plain DB fields render correctly).
  • JSCalendar stays local + IANA zone per RFC 8984; the UI converts events into the viewer's zone via fromEventZone, and edits/drags re-zone the event to the viewer at the same instant (recurring-master passthroughs keep the master's zone).

Highlights

  • Mail messages, push subscriptions, vacation responses, contact cards, calendar events/notifications and the admin API's raw Stalwart pass-throughs are all normalized; the inbound sync API loses its system-tz round trip.
  • Calendar alert AbsoluteTrigger.when round-trips as UTC (was written naive-local, read as UTC — alerts drifted by the viewer's offset). Recurrence until is a spec-correct local datetime (the forced Z shifted the picked date a day east of UTC).
  • ICS exports: VTIMEZONE components are now emitted for every referenced TZID (Outlook read our invites in its own zone), all-day series emit DATE-valued UNTIL, absolute alarm triggers are UTC, and instance CANCELs carry the occurrence's DTSTART.
  • Search/export date filters resolve day bounds in the user's zone instead of UTC midnight (IST users silently lost the first 5½ hours of a searched day).
  • parse_iso_datetime reads naive values as UTC instead of the OS process zone; RSVP expiry no longer depends on the container's zone; server-rendered text (forward/quoted headers, MIME dialog) converts via to_user_timezone.

Tests

  • New suite/mail/tests/test_dt.py (16 site-free unit tests over the four dt helpers — pass identically under TZ=America/New_York).
  • New frontend/src/apps/calendar/utils/datetime.test.ts + extended mail datetime tests; full frontend suite: 1,024 tests green.
  • ICS output verified against icalendar 6.1.3 (VTIMEZONE presence, UTC/DATE UNTIL, instance DTSTART).

…me zone

Virtual doctypes and APIs now store, serve and accept UTC ...Z timestamps
(normalize_utc_z), completing the migration started for the admin API.
Stalwart's offset form (e.g. -05:00) is normalized at every boundary, and
the UIs convert for display: browser zone -> User.time_zone ->
SystemSettings.time_zone.

Mail: format_message serves UTC; filters listen UTC; raw Stalwart
pass-throughs in the admin API are normalized; the inbound sync API drops
its system-tz round trip; server-rendered text (forward headers, MIME
dialog) converts via to_user_timezone; search/export date filters resolve
day bounds in the user's zone; the screener list and exchange views render
system-zone DB fields correctly; the invite-edit modal converts on both
sides instead of writing the browser's wall clock into a system-time field.

Calendar: events render in the viewer's zone (a 2 PM Asia/Kolkata event
shows at 10:30 AM in Vienna) and edits/drags re-zone to the viewer while
recurring-master passthroughs keep their zone; alert AbsoluteTrigger.when
round-trips as UTC; recurrence until is a spec-correct local datetime;
ICS exports gain the required VTIMEZONE, DATE-valued UNTIL for all-day
series, UTC alarm triggers and per-occurrence DTSTART on instance CANCELs;
RSVP expiry no longer depends on the OS zone.

parse_iso_datetime now reads naive wire values as UTC instead of the OS
zone; utcnow() emits the canonical second-precision Z form. Covered by new
backend (suite/mail/tests/test_dt.py) and frontend datetime tests.
@s-aga-r
s-aga-r requested a review from krantheman as a code owner July 29, 2026 12:54
@s-aga-r
s-aga-r marked this pull request as draft July 29, 2026 13:20
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.

Issue: Timestamp/Timezone wrong

1 participant