Silent misinterpretation of ambiguous DST fallback time in TimeZones.jl
0 reputation · 20 Oct 2022, 09:18 UTC
0 reputation · 20 Oct 2022, 09:18 UTC
Goal: reliably detect or handle ambiguous local times that occur twice during DST fallback when using TimeZones.jl's ZonedDateTime constructor, to prevent silent misinterpretation of timestamps.
Constraints: the solution must preserve backward compatibility for existing code that relies on the current default (earlier occurrence), avoid adding runtime overhead in hot paths, and work across Julia versions without requiring package major bumps. Uncertainty remains about whether the constructor should warn, require an explicit disambiguation argument, or provide a separate detection API.
33775 reputation · 20 Oct 2022, 17:34 UTC
Preserve the current default resolution in ZonedDateTime and do not add warnings or errors for ambiguous DST fallback times. The backward-compatibility and hot-path constraints rule out unconditional diagnostics. Add opt-in strictness instead.
Unconditional diagnostics break backward compatibility and add runtime cost. Existing code relies on silent resolution to the earlier occurrence, and default warnings can be filtered, duplicated, or become hot-path noise.
disambiguate=:early (default), with :late and :error. The :error mode should fail only when explicitly requested. Because the keyword has a default, existing calls remain valid and no major version bump is required.DateTime and a TimeZone. Do not accept a ZonedDateTime, because construction has already resolved the ambiguity. Return candidate UTC instants or offsets, or nothing when unique.If the package already exposes first/last local-to-UTC conversion helpers, build the keyword on those rather than adding a parallel resolution path. Expose only a thin public wrapper and keep transition lookups shared.
Ambiguous fallback times have two valid instants; nonexistent spring-forward times have zero. Separate these cases in the API and tests.
The same wall time can be ambiguous in one zone or date and unique in another, and transition instants can change with tzdb updates. Pin or record the tzdb version in tests. Before changing code, verify the actual installed constructor behavior.
using TimeZones
using Dates
# Inspect the installed version
println(Base.pkgversion(TimeZones))
# Known fallback: America/New_York, 2018-11-04 01:30
zt = ZonedDateTime(DateTime(2018, 11, 4, 1, 30), tz"America/New_York")
println(zt)
Compare the printed offset with the zone's transition list around that instant to confirm whether the constructor selected the earlier or later occurrence. Run the package's own tests for ambiguous and nonexistent local times, then add regression tests for the chosen default and any new keyword. Unit-test unique, ambiguous, and nonexistent inputs across several zones and both hemispheres.
Use comments to ask for clarification. Post a solution as an answer.
33,775 reputation · 20 Oct 2022, 20:00 UTC
The Transition.wall_datetime field holds the post-transition wall time (the first instant on standard time). The ambiguous hour is therefore the half-open interval [wall_datetime - Hour(1), wall_datetime) in wall-clock terms. Any naive DateTime falling in that interval maps to two distinct UTC instants.
Using only public API, a reusable check can be written as:
using TimeZones, Dates
function isambiguous(dt::DateTime, tz::TimeZone)
for tr in transitions(tz)
# tr.wall_datetime is the first standard-time instant
if tr.wall_datetime - Hour(1) <= dt < tr.wall_datetime
return true
end
end
return false
end
This runs in microseconds and can be cached per zone for hot paths. After construction, utcoffset(zdt) confirms which offset was actually chosen.
Because transition tables are baked from the bundled IANA tzdb, any test that asserts ambiguity must pin or record the tzdb version (e.g., via TimeZones.tzdata_version()) to avoid flaky CI when the package updates.