fix: preserve calendar date for whole-day events with non-UTC offsets - #91
Draft
dominik1001 wants to merge 2 commits into
Draft
fix: preserve calendar date for whole-day events with non-UTC offsets#91dominik1001 wants to merge 2 commits into
dominik1001 wants to merge 2 commits into
Conversation
new Date(iso) preserves the input's UTC instant, not its local calendar
date. ts-caldav then derives the stored VALUE=DATE via
date.toISOString().split("T")[0], which truncates any offset east of
UTC back by one day. Anchor whole-day start/end/until at UTC midnight
of the input's calendar date instead, so the truncation is a no-op.
Fixes #89
Unit tests mock the CalDAV client, so they stop short of the boundary where the #89 bug actually lived: ts-caldav's serialization and a real server's storage/parsing of VALUE=DATE. Extend scripts/smoke.ts with a create -> list -> delete round trip using a non-UTC-offset (+09:00) whole-day event, asserting the listed date matches the input calendar date rather than shifting a day earlier.
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.
Summary
Whole-day events created (or updated) with a local-midnight ISO datetime at a positive UTC offset (e.g.
2026-09-30T00:00:00+09:00) were stored one calendar day early on the server.Root cause
new Date(iso)preserves the input's UTC instant, not its local calendar date.ts-caldavthen derives the storedVALUE=DATE(forDTSTART/DTEND/RRULE UNTILon whole-day events) viadate.toISOString().split("T")[0]. For any offset east of UTC, that truncation silently rolls the calendar date back by one day — e.g.2026-09-30T00:00:00+09:00becomes the UTC instant2026-09-29T15:00:00.000Z, which truncates to2026-09-29.Fix
Added
toWholeDayDate()(src/tools/whole-day-date.ts), which takes the calendar date directly from the ISO input string and anchors it at UTC midnight, sots-caldav's latertoISOString()truncation is a no-op.create-eventandupdate-eventnow use it forstart/end/recurrenceRule.untilwhenever the event is (or is being updated to be, or already is) whole-day.update-eventfalls back to the existing event'swholeDayflag when the caller doesn't pass one explicitly, so partial updates (e.g. just changingstart) on an existing whole-day event stay date-correct too.Test plan
npm run validate(check + test + knip + docs:check + build) passes locallywhole-day-date.test.tscover UTC,+09:00,-07:00, month-end (Jan 31, Sep 30), and leap day (Feb 29) inputscreate-event.test.ts/update-event.test.tsreproduce the exact scenario from the issue (Asia/Seoul+09:00, includingrecurrenceRule.until) and assert the stored date is unchangedscripts/smoke.ts(create → list → delete with a+09:00input) so the fix is also verified end-to-end against a real CalDAV server, since the unit tests mock the client and don't exercisets-caldav's actual serialization or a real server's storage/parsing. Not run here (no live CalDAV server in this environment) — needsnpm run smokeagainst a real server to confirm.Fixes #89