https://gcc.gnu.org/g:864f26bd7548d1a290b334b8f5375199449fcff5

commit r17-484-g864f26bd7548d1a290b334b8f5375199449fcff5
Author: Álvaro Begué <[email protected]>
Date:   Sun Apr 26 19:51:17 2026 -0400

    libstdc++: Fix numeric save offset on Zone lines [PR124851]
    
    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(0 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.
    
    Reviewed-by: Jonathan Wakely <[email protected]>
    Reviewed-by: Tomasz Kamiński <[email protected]>
    Signed-off-by: Álvaro Begué <[email protected]>

Diff:
---
 libstdc++-v3/src/c++20/tzdb.cc                     | 11 ++--
 .../testsuite/std/time/time_zone/numeric_save.cc   | 58 ++++++++++++++++++++++
 2 files changed, 64 insertions(+), 5 deletions(-)

diff --git a/libstdc++-v3/src/c++20/tzdb.cc b/libstdc++-v3/src/c++20/tzdb.cc
index 587e88bc0dd5..886d7e71d8eb 100644
--- a/libstdc++-v3/src/c++20/tzdb.cc
+++ b/libstdc++-v3/src/c++20/tzdb.cc
@@ -482,12 +482,13 @@ 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_until(info.first.end)
+      : 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())
          {
@@ -498,7 +499,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; }
 
@@ -543,7 +544,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 000000000000..687524bb355b
--- /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 ); // If this fails then XFAIL for the target.
+  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" );
+}

Reply via email to