Skip to content

fix/plugin(tz/utc) broken on dst change #3008

Open
JasonLiu-1214 wants to merge 2 commits intoiamkun:devfrom
JasonLiu-1214:fix/plugin-dst-transition
Open

fix/plugin(tz/utc) broken on dst change #3008
JasonLiu-1214 wants to merge 2 commits intoiamkun:devfrom
JasonLiu-1214:fix/plugin-dst-transition

Conversation

@JasonLiu-1214
Copy link

Problem

1. DST-related date normalization issue

During daylight saving time (DST) transitions, date normalization could produce incorrect results when using startOf('day') in timezone conversions.

Example:

dayjs('2025-11-02T09:00:00Z').tz('utc').startOf('day').toISOString()

Unexpected output:

2025-11-01T23:00:00.000Z

Root cause:

  • The implementation relied on new Date(target), which uses environment-dependent native parsing.
  • Around DST boundaries, native parsing may implicitly apply timezone shifts, leading to inconsistent normalization results.

2. DST transition offset recalculation issue in timezone plugin

During DST transitions, timezone normalization could produce incorrect results after offset adjustments.

Example:

dayjs.tz('2012-03-11 02:59:59', 'America/New_York').format()

Expected output:

2012-03-11T03:59:59-04:00

Actual output:

2012-03-11T04:59:59-04:00

Root cause:

  • After applying timezone offset adjustments, DST transitions may change the effective offset.
  • The previous logic did not recalculate the offset after add(), causing an extra one-hour shift around DST boundaries.

Solution

1. Deterministic parsing for DST-safe normalization

  • Replaced new Date(target) with parseUSLocaleDate to avoid environment-dependent native parsing.
  • Switched to deterministic string-based parsing to prevent implicit timezone conversion.
  • Ensured consistent startOf('day') behavior when normalizing dates in UTC.

2. Recalculate offset after DST-sensitive adjustments

  • Updated timezone normalization logic to re-evaluate the timezone offset after offset adjustments.
  • Added an additional offset check after add() to account for DST boundary changes.
  • Prevented accidental extra-hour shifts during DST transitions.

Tests

Added new test cases covering DST edge scenarios:

  • Correct normalization around DST boundaries when using startOf('day') in UTC.
  • Offset transitions during DST changeover in the timezone plugin.
  • Conversion correctness across DST-sensitive timestamps.
  • Regression tests ensuring no extra-hour shift occurs after offset adjustments.

Jason Liu added 2 commits February 15, 2026 06:43
Problem:
During daylight saving time (DST) transitions, date normalization could produce incorrect results.

Example:
dayjs('2025-11-02T09:00:00Z').tz('utc').startOf('day').toISOString()

Unexpected output:
2025-11-01T23:00:00.000Z

Root cause:
Using new Date(target) relies on environment-dependent native Date parsing, which may shift values around DST boundaries.

Solution:
Replace new Date(target) with parseUSLocaleDate for deterministic string-based parsing
Avoid native Date parsing to prevent implicit timezone conversion
Ensure consistent startOf('day') calculation in UTC
Problem:
During daylight saving time (DST) transitions, timezone normalization could produce incorrect results after offset adjustments.

Example:
dayjs.tz('2012-03-11 02:59:59', 'America/New_York').format()

Expected output:
2012-03-11T03:59:59-04:00

Actual output:
2012-03-11T04:59:59-04:00

Root cause:
After applying timezone offset adjustments, DST transitions can change the effective offset.
The previous logic did not recalculate the offset after add(), causing an extra hour shift during DST boundaries.
@JasonLiu-1214
Copy link
Author

tz Issue Explanation

The tz plugin converts a date to another timezone. The root cause of the issue is that the timezone of new Date(target) and the date object may not match. If target and date cross a DST (daylight saving time) boundary, incorrect results occur.

proto.tz = function (timezone = defaultTimezone, keepLocalTime) {
  ...
  const target = date.toLocaleString('en-US', { timeZone: timezone })
  const diff = Math.round((date - new Date(target)) / 1000 / 60)
  ...
}

On 2025-11-02 01:00, when the transition from DST to standard time occurs:
If date is before 01:00 and target is after 01:00, the calculation will fail.
The time difference between target and date is in [-23, +23] hours, determined by the difference between the target timezone and the current timezone.
There are 24 possible timezones [UTC-12, UTC+12], so the difference ranges from -23 to +23 hours.

date    UTC-8         target    UTC-7
2025-11-02 00:00     2025-11-02 07:00
        |---- -23h ~ +23h ----|
                2025-11-02 01:00

@JasonLiu-1214 JasonLiu-1214 changed the title Fix/plugin dst transition fix/plugin(tz/utc) dst transition Feb 15, 2026
@JasonLiu-1214 JasonLiu-1214 changed the title fix/plugin(tz/utc) dst transition fix/plugin(tz/utc) broken on dst change Feb 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant