[PATCH 0/5] libstdc++: chrono tzdb correctness fixes
Alvaro Begue
alvaro.begue@gmail.com
Sat Apr 11 13:33:04 GMT 2026
This series fixes five distinct correctness issues in libstdc++'s
<chrono> tzdb implementation that cause time-zone conversions to
disagree with zic.c / libc localtime_r for specific real-world zones.
The motivation was a brute-force comparison harness that walks every
IANA zone hourly across 1850-2150 (~563k transition samples) and
compares chrono::time_zone::to_local() against libc's localtime_r().
Against current master HEAD that harness reports thousands of
mismatches across ~190 zones; with this series applied it reports
zero mismatches across all 447 zones. An abbreviation-only sweep
(169k samples) likewise reports zero offset and zero abbrev diffs.
The series builds on Jonathan Wakely's recent PR 116110 / PR 124513
work (commits 663e5ade1, cddf4111c, fbc5d2b1a). Patch 3 in
particular resolves the "FIXME: PR 116110" left in operator>>(istream&,
ZoneInfo&) for the named-rule wall-UNTIL case.
The five commits are deliberately small and independently testable;
each adds a hermetic regression test using the same
__gnu_cxx::zoneinfo_dir_override pattern as the existing 124513.cc.
1. Fix numeric save offset on Zone lines [PR 124851].
ZoneInfo::m_offset had inconsistent semantics: the parser path
stored stdoff alone, but the two sys_info-taking constructors
stored the total (stdoff + save). ZoneInfo::to() returned
m_offset as sys_info::offset, dropping the numeric save for
parser-path lines like Africa/Gaborone's "2 1 CAST" middle line.
Normalize m_offset to stdoff alone everywhere; to() adds save
back when reconstructing.
2. Support ON-format DAY in Zone UNTIL field [PR 124852].
The UNTIL parser only accepted a plain integer as the DAY,
silently misparsing tzdata.zi entries like Europe/Simferopol's
"1997 Mar lastSu 1u". Reuse the on_day machinery and the
parse_on_day_body helper.
3. Resolve named-rule UNTIL save adjustment [PR 116110].
The remaining FIXME in operator>>(istream&, ZoneInfo&) for
wall-time UNTILs on named-rule zone lines. At parse time the
active rule cannot be evaluated (rule records aren't all
loaded yet), so the parser leaves the SAVE adjustment pending
and a new fixup pass in reload_tzdb walks every pending
ZoneInfo and applies the adjustment using a new
find_pre_until_rule helper with iterative-boundary cascade
semantics.
A new bit (m_until_save_pending, stolen from m_pos) marks
pending entries. The seeding code is also updated to use
`t = info.begin + 1s` so a rule firing at exactly info.begin
is included.
Removes the +11h workaround from test_apia in 116110.cc.
4. Cascade wall-time saves in lazy expansion seeding [PR 124853].
The seeding code in _M_get_sys_info interpreted each rule in
isolation against ri.offset() (the line's standard offset
alone), ignoring the running save accumulated by earlier rules
in the same year. For zones whose rule set has wall-time
rules whose effective firing time depends on a prior rule's
save (Europe/Paris around 1945), this gives a wrong answer.
Replace with a chronological cascade walker (matching zic.c's
outzone() logic) that maintains a running save and interprets
each Wall-time rule's at_time relative to the cascaded state.
5. Implement zic writezone merge optimization [PR 124854].
Two distinct fixes that together let lazy expansion match
zic.c's writezone output for zones with rule firings near
zone-line boundaries:
a. Always seed info.offset and info.save from find_active_rule,
not just when letters is empty. Previously, partial-
expansion re-entry left info.offset/save at their
(ri.offset(), 0) init values because the seeding was
skipped when letters was already populated from
i[-1].next_letters(). This caused zones like Europe/Berlin
around 1947-06-29 to emit a 2-hour CEST sys_info with
offset=3600 / save=0 — observably wrong.
b. Add the writezone merge optimization itself: when adjacent
zone lines have different total offsets and the new line's
rule set has a rule firing within |jump| of the boundary
(where jump is a backward local-time jump), fold that rule
into the boundary transition. Canonical cases handled:
America/Argentina/Buenos_Aires 1999-10-03 and Europe/Berlin
1945-05-24.
Test plan:
* make check-target-libstdc++-v3 RUNTESTFLAGS="conformance.exp=std/time/*"
-- all existing chrono tests pass, plus the five new ones added
by this series.
* The comparison harness reports zero mismatches across all 447
zones (563600 samples).
* The abbrev sweep reports zero offset and zero abbrev diffs
(169080 samples).
Alvaro Begue (5):
libstdc++: Fix numeric save offset on Zone lines [PR 124851]
libstdc++: Support ON-format DAY in Zone UNTIL field [PR 124852]
libstdc++: Resolve named-rule UNTIL save adjustment [PR116110]
libstdc++: Cascade wall-time saves in lazy expansion seeding [PR
124853]
libstdc++: Implement zic writezone merge optimization [PR 124854]
libstdc++-v3/src/c++20/tzdb.cc | 464 ++++++++++++++----
.../testsuite/std/time/time_zone/116110.cc | 7 +-
.../std/time/time_zone/numeric_save.cc | 66 +++
.../std/time/time_zone/pr116110_named.cc | 106 ++++
.../std/time/time_zone/until_day_on.cc | 177 +++++++
.../std/time/time_zone/wall_cascade.cc | 87 ++++
.../std/time/time_zone/zone_merge.cc | 101 ++++
7 files changed, 920 insertions(+), 88 deletions(-)
create mode 100644 libstdc++-v3/testsuite/std/time/time_zone/numeric_save.cc
create mode 100644 libstdc++-v3/testsuite/std/time/time_zone/pr116110_named.cc
create mode 100644 libstdc++-v3/testsuite/std/time/time_zone/until_day_on.cc
create mode 100644 libstdc++-v3/testsuite/std/time/time_zone/wall_cascade.cc
create mode 100644 libstdc++-v3/testsuite/std/time/time_zone/zone_merge.cc
--
2.34.1
More information about the Libstdc++
mailing list