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