Files
Anand HegdeandGitHub b3e9580e23 fix(caldav): parse Z-suffixed timestamps as UTC (#3883)
Credit to @jnsvgt for the root-cause analysis and suggested fix in
#3871. This PR implements that fix and adds regression tests.

`caldavTimeToTimestamp()` used the `Z` suffix only to pick the layout,
where it matches as a literal character. The value was then parsed in
the server timezone. With a non-UTC `service.timezone`, every UTC value
without a `TZID` was shifted by the server's offset. Absolute VALARM
triggers are always UTC, so reminders were stored hours early.

Fix: when the value ends in `Z`, parse it in UTC and ignore any `TZID`
(RFC 5545 doesn't allow combining the two). Floating values and `TZID`
values work as before. DTSTART, DUE, DTEND, DTSTAMP and COMPLETED all go
through the same helper, so they get the fix too.

Tests, with the server timezone set to Europe/Berlin:
- `TestCaldavTimeToTimestamp_NonUTCServerTimezone`: covers UTC,
floating, TZID, and UTC combined with TZID
- `TestParseTaskFromVTODO_UTCAlarmNonUTCServerTimezone`: the VTODO from
the issue; the reminder should match the due date

Before the fix, the UTC cases and the alarm test fail (`05:00 CEST`
instead of `05:00 UTC`). After the fix, they pass. `go test
./pkg/caldav`, `mage test:caldav` and `mage lint` also pass.

I wrote this with help from Claude Code, then reviewed and tested it
myself.

Note on #1068: that change made floating times (no `Z`, no `TZID`) use
the configured timezone, which this PR keeps. It also routed
`Z`-suffixed UTC times through the same path; this PR only restores UTC
parsing for those.

Fixes #3871
2026-09-15 16:25:31 +00:00
..