-
-
Notifications
You must be signed in to change notification settings - Fork 374
Adds DST disambiguation support to the DateTime library and fixes an existing production bug with timezone conversion near DST transitions. #5275
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…ixes - Add DST disambiguation parameter with 4 strategies: "compatible", "earlier", "later", "reject" - Fix critical timezone offset calculation bug in makeZonedFromAdjusted - Update all timezone functions to support disambiguation option - Add comprehensive test coverage for DST scenarios - Maintain backward compatibility with "earlier" as default strategy Fixes timezone conversion issues where local time inputs were incorrectly converted to UTC due to improper offset calculation. Example fix: Input: 01:00 Athens time on March 30, 2025 Before: 2025-03-29T22:00:00.000Z (incorrect) After: 2025-03-29T23:00:00.000Z (correct)
🦋 Changeset detectedLatest commit: 585ec2e The changes in this PR will be included in the next version bump. This PR includes changesets to release 35 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
…est expectations. Updated test to match standard cron once mode behavior with detailed comments.
- Restore proper formatting to dateTime.ts after DST disambiguation changes - Add comprehensive DST testing suite in packages/effect/dstProof/ (can be removed after reviewers are satisfied)
- Changed disambiguation default to compatible bringing it inline with temporal and others - Refactored tests to use Effect.ts patterns - Used IllegalArgumentException for a bad argument. - Used RangeError when a disambiguation rejection occurs. - Removed various legacy comments.
@taylornz I removed the dst proof test case generator (that's a bit overkill for inclusion here). I also took a stab at streamlining the test cases a bit more. In doing so, I realised that it's likely possible to just create a huge test matrix for most of the test scenarios (they are all pretty much identical) instead of all the repetition we have in there atm. With a few exceptions of course. Lmk what you think. |
@taylornz Tim is looking into this now and will follow up. Thanks for the PR! |
Good call, reorganised the DST test suite grouping up similar scenarios into one test. |
3287c92
to
638718b
Compare
1d6c465
to
361fa5c
Compare
Thanks for the PR and heads up. I have simplified the changes and optimized for the happy path to try reduce the impact on performance. I'm not sure if the test cases cover all edge cases, so if you wanted to double check against the csv and report any issues that would be appreciated! |
Thanks Tim, looks good to me. Final Results: 338992 successes and 0 failures |
Type
Bug Summary
Along with the Feature Below this also squashes an existing production bug in Effect.ts DateTime which is failing to produce accurate local to UTC timezone conversations near DST change over moments.
The Effect DateTime library has an offset calculation bug in makeZonedFromAdjusted that causes incorrect UTC conversion when adjustForTimeZone: true is used. The bug affects both normal times near DST transition periods and DST transition periods.
Example: Europe/Athens DST transition day on 2025-03-30 ( transition at 3am ) (01:00 AM Athens EET = UTC+2, so UTC time is 23:00 previous day)
Node.js v24.2.0 on macOS (Darwin 24.5.0) ARM64
Temporal
Effect.ts DateTime
BUG: Effect DateTime is 60 minutes off
This bug is repaired as part of this feature.
Feature Summary
Adds comprehensive DST (Daylight Saving Time) disambiguation support to the DateTime API, enabling proper handling of ambiguous and gap times during timezone transitions. This implementation follows the Temporal specification and provides and maintains the Temporal default mode of 'compatible'
Problem
DST transitions create two critical scenarios when converting local times to UTC:
Fall-back transitions (Repeated Times): When clocks move backward (e.g., 02:00 -> 01:00), the same local time occurs twice
Spring-forward transitions (Gap Times): When clocks move forward (e.g., 02:00 -> 03:00), some local times don't exist
Without disambiguation, identical local timestamps during DST transitions cannot be correctly mapped to their intended unique UTC timestamps, breaking applications that parse time-series data across DST boundaries.
CSV Parsing Use Case
Why Not Just "Add 1 Hour"?
A naive approach might be to simply add/subtract 1 hour for DST disambiguation, but this fails for several critical reasons:
Not all DST transitions are exactly 1 hour:
Lord Howe Island, Australia: 30-minute DST shift
Standard: UTC+10:30, DST: UTC+11:00 (only 30 minutes!)
Chatham Islands, New Zealand: 45-minute DST shift
Standard: UTC+12:45, DST: UTC+13:45
Historical examples: Some regions had 20-minute, 40-minute, or 2-hour shifts
Even with 1-hour offsets, you need to know which direction:
Fall-back: 01:30 happens twice - which occurrence do you want?
First: 01:30 EDT = 05:30 UTC
Second: 01:30 EST = 06:30 UTC (+1 hour in UTC, but which one?)
Spring-forward: 02:30 doesn't exist - synthesize before or after gap?
Before gap: 01:30 EST vs After gap: 03:30 EDT (2-hour local time difference!)
Russia changed DST rules multiple times:
Chatham Islands gap time: 02:30 doesn't exist during spring forward
solution: Query the actual timezone database to calculate precise offset differences rather than assuming fixed increments.
Solution
Effect.ts needs disambiguation options just as the Temporal proposal library has added.
This change adds disambiguation parameter to timezone-related functions with four strategies:
export type Disambiguation = "compatible" | "earlier" | "later" | "reject"
Usage Examples
Handling Repeated Times (Fall-back)
Handling Gap Times (Spring-forward)
Implementation Details
Core Algorithm
The implementation uses a Temporal-compatible algorithm:
- 1 interpretation: Normal time (no ambiguity)
- 2 interpretations: Ambiguous time (apply strategy)
- 0 interpretations: Gap time (synthesize solution)
Variable DST Offset Support
Unlike naive "+1 hour" approaches, this correctly handles:
API Changes
Updated Function Signatures
All timezone-related functions now accept optional disambiguation parameter:
Breaking Changes: The disambiguation default has moved to 'compatible', Relatively easy to alter the disambiguation option used.
Related