fix(diff-schedule): resolve DST offset bug#120
Conversation
localToUtc sampled the timezone offset once at the naive UTC guess, which can be the wrong side of a DST transition when the wall time falls in a spring-forward or fall-back window. Resample the offset at the corrected instant and use it when it disagrees with the first guess. Fixes #43
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Deploy →
|
|
discuss: why don't we use date-fns in teh server? |
There was a problem hiding this comment.
Pull request overview
This PR fixes a DST edge-case in localToUtc used by the diff-schedule Supabase function, ensuring wall-clock times near DST transitions in a target IANA timezone convert to the intended UTC instants.
Changes:
- Reworked
localToUtcto sample the timezone offset twice (naive guess + corrected instant) and re-correct when the offsets disagree across DST transitions. - Refactored the offset calculation into a helper (
offsetMsAt) returning an offset in milliseconds. - Added Deno tests covering Lisbon spring-forward (gap) and fall-back (fold) transition cases.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| supabase/functions/diff-schedule/helpers.ts | Updates localToUtc DST handling by re-sampling offset after correction and introduces offsetMsAt. |
| supabase/functions/diff-schedule/helpers.test.ts | Adds regression tests for DST gap/fold behavior and updates imports for additional assertions. |
Replace the hand-rolled two-pass Intl.DateTimeFormat offset resolver with date-fns-tz's fromZonedTime, matching the timezone-handling library already used client-side (src/lib/timeUtils.ts) and importable via npm: specifiers in Deno edge functions (same pattern as commit-schedule's supabase-js import). This also removes the locale-string date parsing Copilot flagged as fragile, since that code path no longer exists. Update the spring-forward gap test to match date-fns-tz's documented behavior (resolves using the post-transition/DST offset) and spell out that resolution policy in the test comment per Copilot's second review comment. The fall-back and summer/winter/midnight cases are unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iiisPt8nyYMGcYfh9p5yo
|
Switched The hand-rolled two-pass One behavioral note: Verified with the real Generated by Claude Code |
) date-fns-tz's fromZonedTime does a single-pass offset lookup, which reintroduced the exact spring-forward off-by-one-hour bug #43 was about (verified independently and flagged by Copilot review). Switch to @date-fns/tz's TZDate, which resolves ambiguous/skipped DST wall times correctly, keeping localToUtc's date-fns-based per PR feedback without regressing the gap case.
|
Good catch — thanks @copilot. The previous commit switched to Fixed by switching to Generated by Claude Code |
Fixes #43
localToUtcsampled the timezone offset once at the naive UTC guess, which can land on the wrong side of a DST transition; it now resamples the offset at the corrected instant and corrects itself when the two disagree, so wall times inside a DST transition window convert correctly.Verification
01:30on 2026-03-29 (Lisbon spring-forward gap, skipped hour); result is2026-03-29T01:30:00.000Z, not the old off-by-one-hour2026-03-29T00:30:00.000Z.01:30on 2026-10-25 (Lisbon fall-back fold, repeated hour); result is2026-10-25T01:30:00.000Zand round-trips consistently.cd supabase/functions/diff-schedule && deno test --allow-env --allow-net --allow-readpasses, including the two new DST-transition cases inhelpers.test.ts.Generated by Claude Code