https://gcc.gnu.org/g:e39c7121bc643ded1304ea73248390ee8ed5eac6

commit r15-11158-ge39c7121bc643ded1304ea73248390ee8ed5eac6
Author: Jonathan Wakely <[email protected]>
Date:   Mon Mar 16 21:48:30 2026 +0000

    libstdc++: Partial fix for interpretation of non-UTC UNTIL times in 
tzdata.zi [PR116110]
    
    This is a partial fix for PR 116110, so that zone changes that occur at
    a time specified as standard time will be parsed correctly. Previously
    all UNTIL times were assumed to be UTC, which is incorrect according to
    the spec. Only times with a 'u' suffix (or the equivalent 'g' or 'z')
    are UTC times. An 's' suffix means standard time (i.e. the zone's usual
    offset from UTC, without any DST adjustment) and a 'w' suffix (or no
    suffix) means wall time, so the offset and any DST adjustment. This
    commit fixes the handling of 's' suffixes, so that the ZoneInfo::m_until
    member is set correctly. It also fixes the handling of some UNTIL times
    using wall time, specifically those where the DST adjustment is known
    during parsing of the tzdata.zi file.
    
    This is not a complete fix, because as noted in PR 116110 a Zone line
    that refers to a named Rule and uses wall time cannot be determined
    while parsing tzdata.zi. We need to remember that the m_until member is
    not correct, and then adjust it later when we find the rule that applies
    at a given time point. That requires more work.
    
    Some existing tests need to be adjusted due to the fixes in this commit.
    The std/time/time_zone/get_info_sys.cc and std/time/zoned_time/1.cc
    tests are corrected to check that the changes happens at midnight local
    time, not midnight UTC as previously assumed.
    
    One of the FIXME comments in std/time/time_zone/116110.cc can be removed
    now, because the UNTIL time is correctly interpreted as midnight local
    time. The other FIXME needs to be changed to midnight at the local
    standard time, which is still wrong but we don't currently adjust it for
    the DST save time of one hour. It also still needs the incorrect +24h
    due to Bug 124513.
    
    libstdc++-v3/ChangeLog:
    
            PR libstdc++/116110
            * src/c++20/tzdb.cc (operator>>(istream&, ZoneInfo&)): Adjust
            inf.m_until according to indicator suffix on AT time in UNTIL.
            * testsuite/std/time/time_zone/116110.cc (test_kiritimati):
            Remove FIXME now that the UNTIL time is adjusted for STDOFF.
            (test_apia): Adjust FIXME now that UNTIL time is adusted for
            STDOFF.
            * testsuite/std/time/time_zone/get_info_sys.cc: Adjust expected
            results to account for corrected logic.
            * testsuite/std/time/zoned_time/1.cc: Likewise.
    
    Reviewed-by: Tomasz KamiƄski <[email protected]>
    (cherry picked from commit cddf4111c4c4383f8d686ab822a9f3fc3ed6db44)

Diff:
---
 libstdc++-v3/src/c++20/tzdb.cc                     | 15 +++++++++++----
 .../testsuite/std/time/time_zone/116110.cc         |  6 ++++--
 .../testsuite/std/time/time_zone/get_info_sys.cc   | 22 +++++++++++++---------
 libstdc++-v3/testsuite/std/time/zoned_time/1.cc    | 20 ++++++++++++--------
 4 files changed, 40 insertions(+), 23 deletions(-)

diff --git a/libstdc++-v3/src/c++20/tzdb.cc b/libstdc++-v3/src/c++20/tzdb.cc
index 5c54606a9761..367f056b12e1 100644
--- a/libstdc++-v3/src/c++20/tzdb.cc
+++ b/libstdc++-v3/src/c++20/tzdb.cc
@@ -2157,11 +2157,18 @@ namespace std::chrono
          at_time t{};
          // XXX DAY should support ON format, e.g. lastSun or Sun>=8
          in >> m >> d >> t;
-         // XXX UNTIL field should be interpreted
-         // "using the rules in effect just before the transition"
-         // so might need to store as year_month_day and hh_mm_ss and only
-         // convert to a sys_time once we know the offset in effect.
          inf.m_until = sys_days(year(y)/m.m/day(d)) + seconds(t.time);
+         if (t.indicator != at_time::Universal)
+           { // UNTIL uses "the rules in effect just before the transition"
+             // adjust by STDOFF
+             inf.m_until -= seconds(inf.m_offset);
+             if (t.indicator != at_time::Standard)
+               {
+                 if (inf.m_expanded) // Not a named Rule, SAVE is known now.
+                   inf.m_until -= inf.m_save;
+                 // else Named Rule, SAVE is unknown. FIXME: PR 116110
+               }
+           }
        }
       else
        inf.m_until = sys_days(year::max()/December/31);
diff --git a/libstdc++-v3/testsuite/std/time/time_zone/116110.cc 
b/libstdc++-v3/testsuite/std/time/time_zone/116110.cc
index d86011cf5739..0f3e09690e0e 100644
--- a/libstdc++-v3/testsuite/std/time/time_zone/116110.cc
+++ b/libstdc++-v3/testsuite/std/time/time_zone/116110.cc
@@ -39,7 +39,7 @@ test_kiritimati()
   auto* tz = locate_zone("Pacific/Kiritimati");
   local_seconds t = local_days(1994y/December/31);
 
-  sys_seconds ut(t.time_since_epoch() /* FIXME: should be + 10h */);
+  sys_seconds ut(t.time_since_epoch() + 10h);
   sys_info info;
   info = tz->get_info(ut - 1s);
   VERIFY( info.offset == -10h );
@@ -65,7 +65,9 @@ test_apia()
   auto* tz = locate_zone("Pacific/Apia");
   local_seconds t = local_days(2011y/December/29) + 24h;
 
-  sys_seconds ut(t.time_since_epoch() /* FIXME: should be + 10h */ - 24h );
+  // FIXME: this should be + 10h but we do not account for DST yet, so + 11h.
+  // The 24h is because we don't parse the "24" in the Zone line (PR 124513).
+  sys_seconds ut(t.time_since_epoch() + 11h - 24h );
   sys_info info;
   info = tz->get_info(ut - 1s);
   VERIFY( info.offset == (-11h + info.save) );
diff --git a/libstdc++-v3/testsuite/std/time/time_zone/get_info_sys.cc 
b/libstdc++-v3/testsuite/std/time/time_zone/get_info_sys.cc
index 769c7744017b..88f16b76ff48 100644
--- a/libstdc++-v3/testsuite/std/time/time_zone/get_info_sys.cc
+++ b/libstdc++-v3/testsuite/std/time/time_zone/get_info_sys.cc
@@ -12,33 +12,37 @@ test_zurich()
   const time_zone* const tz = locate_zone("Europe/Zurich");
 
   {
-    sys_days d = 1853y/July/16;
+    sys_days d = 1853y/July/16; // On this date ...
+    auto offset = 34min + 8s;   // ... local time is this far ahead of UTC,
+    auto t = d - offset;        // so LMT to BMT transition is at this time.
 
-    auto info = tz->get_info(d - 1s);
-    VERIFY( info.offset == (34min + 8s) );
+    auto info = tz->get_info(t - 1s);
+    VERIFY( info.offset == offset );
     VERIFY( info.abbrev == "LMT" );
 
-    info = tz->get_info(d);
+    info = tz->get_info(t);
     VERIFY( info.offset == (29min + 46s) );
     VERIFY( info.abbrev == "BMT" );
 
-    info = tz->get_info(d + 1s);
+    info = tz->get_info(t + 1s);
     VERIFY( info.offset == (29min + 46s) );
     VERIFY( info.abbrev == "BMT" );
 
-    info = tz->get_info(d + 0.001s);
+    info = tz->get_info(t + 0.001s);
     VERIFY( info.offset == (29min + 46s) );
     VERIFY( info.abbrev == "BMT" );
   }
 
   {
     sys_days d = 1894y/June/1;
+    auto offset = 29min + 46s;
+    auto t = d - offset;
 
-    auto info = tz->get_info(d - 1s);
-    VERIFY( info.offset == (29min + 46s) );
+    auto info = tz->get_info(t - 1s);
+    VERIFY( info.offset == offset );
     VERIFY( info.abbrev == "BMT" );
 
-    info = tz->get_info(d);
+    info = tz->get_info(t);
     VERIFY( info.offset == 1h );
     VERIFY( info.abbrev == "CET" );
   }
diff --git a/libstdc++-v3/testsuite/std/time/zoned_time/1.cc 
b/libstdc++-v3/testsuite/std/time/zoned_time/1.cc
index 1623aca1c7a8..a77cd99dd550 100644
--- a/libstdc++-v3/testsuite/std/time/zoned_time/1.cc
+++ b/libstdc++-v3/testsuite/std/time/zoned_time/1.cc
@@ -48,24 +48,26 @@ test_zurich()
   const time_zone* const zurich = locate_zone("Europe/Zurich");
 
   {
-    sys_days d = 1853y/July/16;
+    sys_days d = 1853y/July/16; // On this date ...
+    auto offset = 34min + 8s;   // ... local time is this far ahead of UTC,
+    auto t = d - offset;        // so LMT to BMT transition is at this time.
 
-    auto z = zoned_seconds(zurich, sys_seconds(d) - 1s);
+    auto z = zoned_seconds(zurich, t - 1s);
     auto info = z.get_info();
-    VERIFY( info.offset == (34min + 8s) );
+    VERIFY( info.offset == offset );
     VERIFY( info.abbrev == "LMT" );
 
-    z = zoned_seconds(zurich, d);
+    z = zoned_seconds(zurich, t);
     info = z.get_info();
     VERIFY( info.offset == (29min + 46s) );
     VERIFY( info.abbrev == "BMT" );
 
-    z = zoned_seconds(zurich, d + 1s);
+    z = zoned_seconds(zurich, t + 1s);
     info = z.get_info();
     VERIFY( info.offset == (29min + 46s) );
     VERIFY( info.abbrev == "BMT" );
 
-    auto z2 = zoned_time(zurich, d + 0.001s);
+    auto z2 = zoned_time(zurich, t + 0.001s);
     info = z2.get_info();
     VERIFY( info.offset == (29min + 46s) );
     VERIFY( info.abbrev == "BMT" );
@@ -73,13 +75,15 @@ test_zurich()
 
   {
     sys_days d = 1894y/June/1;
+    auto offset = 29min + 46s;
+    auto t = d - offset;
 
-    auto z = zoned_seconds(zurich, sys_seconds(d) - 1s);
+    auto z = zoned_seconds(zurich, t - 1s);
     auto info = z.get_info();
     VERIFY( info.offset == (29min + 46s) );
     VERIFY( info.abbrev == "BMT" );
 
-    z = zoned_seconds(zurich, d);
+    z = zoned_seconds(zurich, t);
     info = z.get_info();
     VERIFY( info.offset == 1h );
     VERIFY( info.abbrev == "CET" );

Reply via email to