[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