On Mon, May 11, 2026 at 6:40 PM Jonathan Wakely <[email protected]> wrote:
> On Sun, 26 Apr 2026 at 19:51 -0400, Álvaro Begué wrote: > >When a Zone line specifies a numeric value as its RULES field (the > >constant DST save value for that zone line, e.g. Africa/Gaborone's > >"2 1 CAST" line), the parser stored the standard offset alone in > >ZoneInfo::m_offset. ZoneInfo::to() then returned that as > >sys_info::offset, dropping the numeric save and reporting a total > >offset that was wrong by the save amount. > > > >This was inconsistent with the two ZoneInfo constructors that take a > >sys_info, which previously stored the *total* offset (stdoff + save) in > >m_offset. As a result m_offset's semantics depended on which code path > >created the ZoneInfo, and only the parser path's lines with non-zero > >numeric save were observably broken. > > > >Fix by giving m_offset a single semantics: always the standard offset > >only. The two sys_info-taking constructors now subtract the save before > >storing, and to() adds it back when reconstructing the sys_info. > > > >The remaining .offset() callers inside _M_get_sys_info already expect > >the standard offset (they are computing rule firing times, where the > >save component is added separately from the active rule's save value), > >so no other call sites need adjustment. > > > >libstdc++-v3/ChangeLog: > > > >PR libstdc++/124851 > >* src/c++20/tzdb.cc (ZoneInfo::ZoneInfo(sys_info&&)): Store > >stdoff only in m_offset (subtract info.save). > >(ZoneInfo::ZoneInfo(const pair<sys_info, string_view>&)): > >Likewise. > >(ZoneInfo::offset()): Document new semantics. > >(ZoneInfo::to(sys_info&)): Add m_save back to offset() when > >populating sys_info::offset. > >* testsuite/std/time/time_zone/numeric_save.cc: New test. > > > >Signed-off-by: Álvaro Begué <[email protected]> > >--- > > libstdc++-v3/src/c++20/tzdb.cc | 9 +-- > > .../std/time/time_zone/numeric_save.cc | 58 +++++++++++++++++++ > > 2 files changed, 63 insertions(+), 4 deletions(-) > > create mode 100644 > >libstdc++-v3/testsuite/std/time/time_zone/numeric_save.cc > > > >diff --git a/libstdc++-v3/src/c++20/tzdb.cc > b/libstdc++-v3/src/c++20/tzdb.cc > >index b0fbfc46a..1e49bb749 100644 > >--- a/libstdc++-v3/src/c++20/tzdb.cc > >+++ b/libstdc++-v3/src/c++20/tzdb.cc > >@@ -478,11 +478,12 @@ namespace std::chrono > > > > ZoneInfo(sys_info&& info) > > : m_buf(std::move(info.abbrev)), m_expanded(true), > m_save(info.save), > >- m_offset(info.offset), m_until(info.end) > >+ m_offset(info.offset - seconds(info.save)), m_until(info.end) > > { } > > > > ZoneInfo(const pair<sys_info, string_view>& info) > >- : m_expanded(true), m_save(info.first.save), > >m_offset(info.first.offset), > >+ : m_expanded(true), m_save(info.first.save), > >+ m_offset(info.first.offset - seconds(info.first.save)), > > m_until(info.first.end) > > { > > if (info.second.size()) > >@@ -494,7 +495,7 @@ namespace std::chrono > > m_buf += info.first.abbrev; > > } > > > >- // STDOFF: Seconds from UTC during standard time. > >+ // STDOFF: Seconds from UTC during standard time (without any > save). > > seconds > > offset() const noexcept { return m_offset; } > > > >@@ -539,7 +540,7 @@ namespace std::chrono > > return false; > > > > info.end = until(); > >- info.offset = offset(); > >+ info.offset = offset() + seconds(m_save); > > info.save = minutes(m_save); > > info.abbrev = format(); > > format_abbrev_str(info); // expand %z > >diff --git a/libstdc++-v3/testsuite/std/time/time_zone/numeric_save.cc > >b/libstdc++-v3/testsuite/std/time/time_zone/numeric_save.cc > >new file mode 100644 > >index 000000000..1b5b22263 > >--- /dev/null > >+++ b/libstdc++-v3/testsuite/std/time/time_zone/numeric_save.cc > >@@ -0,0 +1,58 @@ > >+// { dg-do run { target c++20 } } > >+// { dg-require-effective-target tzdb } > >+// { dg-require-effective-target cxx11_abi } > >+// { dg-xfail-run-if "no weak override on AIX" { powerpc-ibm-aix* } } > >+ > >+// When a Zone line specifies a numeric value as its RULES field, that > >+// value is the constant DST save value for that zone line. Per > >+// [time.zone.info.sys] sys_info::offset is the total UTC offset > >+// (stdoff + save). > >+ > >+#include <chrono> > >+#include <fstream> > >+#include <testsuite_hooks.h> > >+ > >+static bool override_used = false; > >+ > >+namespace __gnu_cxx > >+{ > >+ const char* zoneinfo_dir_override() { > >+ override_used = true; > >+ return "./"; > >+ } > >+} > >+ > >+int > >+main() > >+{ > >+ using namespace std::chrono; > >+ > >+ std::ofstream("tzdata.zi") << R"(# version test_numeric_save > >+Z Test/Gaborone 2 - CAT 1943 Sep 19 2 > >+ 2 1 CAST 1944 Mar 19 2 > >+ 2 - CAT > >+)"; > >+ > >+ const auto& db = reload_tzdb(); > >+ VERIFY( override_used ); > > Please add a comment on the line above: > > VERIFY( override_used ); // If this fails then XFAIL for the target. > > OK for trunk with that change, thanks. > I also added this to the second commiit locally. > > > >+ VERIFY( db.version == "test_numeric_save" ); > >+ > >+ auto* tz = locate_zone("Test/Gaborone"); > >+ > >+ // Sample well inside the CAST (numeric-save) zone line. > >+ auto info = tz->get_info(sys_days(1943y/December/15)); > >+ VERIFY( info.offset == 3h ); // stdoff +2h + save +1h > >+ VERIFY( info.save == 60min ); > >+ VERIFY( info.abbrev == "CAST" ); > >+ > >+ // Bordering zone lines should report the standard offset with save 0. > >+ auto before = tz->get_info(sys_days(1943y/September/1)); > >+ VERIFY( before.offset == 2h ); > >+ VERIFY( before.save == 0min ); > >+ VERIFY( before.abbrev == "CAT" ); > >+ > >+ auto after = tz->get_info(sys_days(1944y/April/15)); > >+ VERIFY( after.offset == 2h ); > >+ VERIFY( after.save == 0min ); > >+ VERIFY( after.abbrev == "CAT" ); > >+} > >-- > >2.34.1 > >
