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
Draft
fix(mail, calendar): speak UTC on the wire, render in the viewer's time zone#395s-aga-r wants to merge 1 commit into
s-aga-r wants to merge 1 commit into
Conversation
…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
marked this pull request as draft
July 29, 2026 13:20
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #56
Problem
Timestamps flowed through three different conventions: Stalwart speaks UTC (
...Zor 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:
...Z(second precision).normalize_utc_zconverts Stalwart's offset form and reads naive wire values as UTC. Virtual doctypes store/serve UTC; filters (before/after) listen UTC.Datetimefields keep system time —to_utc_z/from_utc_zconvert at the wire boundary.User.time_zone→ system zone (get_user_infoalso exposessystem_time_zoneso plain DB fields render correctly).fromEventZone, and edits/drags re-zone the event to the viewer at the same instant (recurring-master passthroughs keep the master's zone).Highlights
AbsoluteTrigger.whenround-trips as UTC (was written naive-local, read as UTC — alerts drifted by the viewer's offset). Recurrenceuntilis a spec-correct local datetime (the forcedZshifted the picked date a day east of UTC).VTIMEZONEcomponents are now emitted for every referenced TZID (Outlook read our invites in its own zone), all-day series emit DATE-valuedUNTIL, absolute alarm triggers are UTC, and instance CANCELs carry the occurrence'sDTSTART.parse_iso_datetimereads 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 viato_user_timezone.Tests
suite/mail/tests/test_dt.py(16 site-free unit tests over the four dt helpers — pass identically underTZ=America/New_York).frontend/src/apps/calendar/utils/datetime.test.ts+ extended mail datetime tests; full frontend suite: 1,024 tests green.UNTIL, instanceDTSTART).