Fix: treat equivalent timezones as a single zone - #246
Merged
Conversation
alies-dev
force-pushed
the
fix/timezone-alias-equivalence
branch
3 times, most recently
from
August 20, 2026 15:49
55872c9 to
090d148
Compare
hasDistinctTimezones() compared zone names, so UTC against Etc/UTC, or the bare Z that PHP leaves on a Z suffixed ISO string, took the two zone path and emitted identifiers the calendar services reject for an event that never crossed a zone. The zones now collapse into one when their offsets agree, each read at its own instant, and at least one of them names no place. Equal offsets alone are not enough: Europe/London and Europe/Lisbon share one all year, and both are still worth naming. A zone that names no place is identified by the offset family itself, not by a missing country in the timezone database. Every IANA backward alias reports the same `??` country as the UTC family, so reading that placeholder as "no place" would fold Japan, Singapore, US/Eastern and GB into whatever zone they were paired with and lose the destination of the flight.
alies-dev
force-pushed
the
fix/timezone-alias-equivalence
branch
from
August 20, 2026 16:13
090d148 to
704bd3a
Compare
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.
Context and Purposes
Link::hasDistinctTimezones()decided whether an event crosses a timezone by comparing the two zonenames. That is wrong whenever one zone is spelled differently from the other while meaning the same
thing, and it is easy to hit by accident:
A
Zsuffixed ISO string is worse, because PHP names its zone literallyZ:The event never leaves a single zone, yet the generators took the two zone path and emitted
identifiers the calendar services reject.
The rule this uses, and why it is not plain offset equality
The obvious fix is to compare the offsets of
$fromand$toat their own instants instead of thenames. That handles aliases and offset style zones alike, but on its own it is too eager: two
genuinely different places can share an offset. Europe/London and Europe/Lisbon are both on +00:00
the whole year, and Europe/Berlin and Africa/Lagos are both on +01:00 all winter. A flight from
London to Lisbon should still be able to name where it lands.
So
hasDistinctTimezones()now collapses a pair of zones into one only when both of these hold.instant is what lets an event running across a daylight saving change still see the two offsets it
really spans.
+00:00), an abbreviation (Z) andevery member of the UTC family (
UTC,Etc/UTC,Etc/GMT+5) are offsets wearing a label: theycarry no country in the timezone database, so there is no location to lose by describing the event
in the zone opposite them. A region zone does carry one, and two of them stay distinct even when
their offsets happen to coincide.
Name equality is still checked first as a short circuit, since identical names are the same zone by
definition, daylight saving included.
The constructor
The constructor also branches on the zone names when it normalises
$to, and that comparison is leftas it is on purpose. It answers a narrower question: not whether the two zones are worth naming
separately, but whether
$tohas to move at all. Equal names mean there is nothing to move, and forevery other pair the conversion is either meaningful or a no-op on the instant. Keeping it also keeps
the existing invariant that
$toalways carries$fromTimezone, which Yahoo and the single zonepaths of Google and ICS rely on when they render both endpoints under one zone label.
Scope
This fixes the equivalence comparison only.
ctz=Zis still not an identifier Google accepts, so aZsuffixed event is now described as single zone but not yet named in a form the servicesunderstand. That part is #234 and is handled separately.
Fixes #238