Skip to content

Use the parsed offset in ZonedDateTime::parse() when a region is present - #140

Merged
BenMorel merged 2 commits into
brick:mainfrom
gnutix:fix/zoned-date-time-parse-offset
Oct 10, 2026
Merged

BenMorel merged 2 commits into
brick:mainfrom
gnutix:fix/zoned-date-time-parse-offset

Conversation

@gnutix

@gnutix gnutix commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Item 1 of #137, with the stricter behaviour suggested there.

Problem

ZonedDateTime::from() parsed the offset, then discarded it and resolved the local date-time against the region again:

$z = ZonedDateTime::ofInstant(Instant::of(1729989000), TimeZone::parse('Europe/Zurich'));
(string) $z;                                      // 2024-10-27T02:30:00+02:00[Europe/Zurich]
ZonedDateTime::parse((string) $z)->isEqualTo($z); // false: +01:00, one hour later

ZonedDateTime::parse('2024-06-01T12:00Z[Europe/Zurich]'); // 12:00+02:00, a different instant

Change

  • When a region is present, the instant is computed from the local date-time and the parsed offset, then converted to the region.
  • If the resulting offset differs from the parsed one, a DateTimeException is thrown:
    • The offset Z is not valid for 2024-06-01T12:00 in Europe/Zurich. when the region doesn't use that offset at that time;
    • The local date-time 2024-03-31T02:30 does not exist in Europe/Zurich. when the local date-time falls in a DST gap.
  • Both occurrences of an overlap now parse to the right instant, so toString() → parse() round-trips.

This is a behaviour change: input with an offset that doesn't match the region used to be accepted silently, and now throws.

One existing fixture (ZonedDateTimeTest, testPlusPeriod/testMinusPeriod data set #11) used -07:00 for America/Los_Angeles in February. It only passed because the offset was ignored, and now uses -08:00; the expected result is unchanged.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Fm9bvgKFasEGzpK48bQrxA

from() discarded the parsed offset and resolved the local date-time against
the region again. In a DST overlap, the first occurrence therefore parsed
as the second one, so parse((string) $zdt) did not round-trip, and an offset
that the region does not use at that time was silently replaced:

    ZonedDateTime::parse('2024-10-27T02:30+02:00[Europe/Zurich]'); // +01:00, one hour later
    ZonedDateTime::parse('2024-06-01T12:00Z[Europe/Zurich]');      // 12:00+02:00, another instant

The instant is now computed from the local date-time and the offset, and a
DateTimeException is thrown when that offset is not valid for the region.

A test fixture used -07:00 for America/Los_Angeles in February, an offset
that only passed because it was ignored; it now uses -08:00.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fm9bvgKFasEGzpK48bQrxA
@codecov

codecov Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.24%. Comparing base (44f8c01) to head (84e568d).
⚠️ Report is 5 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##               main     #140   +/-   ##
=========================================
  Coverage     99.24%   99.24%           
- Complexity     1141     1143    +2     
=========================================
  Files            48       48           
  Lines          2501     2517   +16     
=========================================
+ Hits           2482     2498   +16     
  Misses           19       19           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread tests/ZonedDateTimeTest.php Outdated
@gnutix
gnutix requested a review from BenMorel October 10, 2026 11:41
@BenMorel
BenMorel merged commit 4c432a2 into brick:main Oct 10, 2026
10 checks passed
@BenMorel

Copy link
Copy Markdown
Member

Thank you, @gnutix!

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.

2 participants