The previous patches for PR 116110 left one case unresolved: a Zone
line with a wall-time UNTIL whose RULES field is a named rule set.
The save value used to convert the wall UNTIL to UTC depends on which
rule of the set was active at the UNTIL instant, but at parse time
the rule records have not all been loaded, so the active-rule lookup
can't yet be performed.

The remaining FIXME in operator>>(istream&, ZoneInfo&) caused zones
like Africa/Algiers (around 1977-10-21) to place their zone-line
boundary one save-period off from the canonical zic interpretation,
producing brief incorrect sys_info windows during DST transitions.

This commit defers the save adjustment to the end chrono::reload_tzdb
function, after rule data is available.

The m_expanded member is replaced with four state _M_state setting:
Expanded, and three separte state used for rule based zones:
* SaveKnown - m_save contains save value at time m_until
* SavePending - m_save is defualted to zero, m_until is correct
* UntilPending - as above, but also m_until time need to adjusted
The parser set the UntilPending state when it sees a wall-time
UNTIL on a named-rule line, and SaveKnown if not UNTIL date is
specified.

The fixup is performed in bulk for all time_zones in chrono::reload_tzdb
using newly calc_save function, guaranteeing proper value of UNTIL during
the query. The save at boundary for previous/current ZoneInfo is required
to implement proper zone merging, so it is always computed.

The active-rule lookup either reuses the existing find_active_rule
overload accepting sys_seconds (if UNTIL is not affected by save),
or newly introduced overload accepting local_seconds (local time).
The local_seconds versions, follows the same logic for finding
rule transitions (extracted to find_surrounding_transitions)
surrounding the sys_time. The final active rule, is then determined
after converting the transition times to local time, including the
running save.

The test_apia case in 116110.cc had a hardcoded `+11h` workaround
for the unfixed bug; with this fix in place the workaround is removed
and the value becomes the canonical `+10h`.

libstdc++-v3/ChangeLog:

        PR libstdc++/116110
        * src/c++20/tzdb.cc (ZoneInfo::calc_save, ZoneInfo::State): Define.
        (ZoneInfo::m_expanded): Replaced with m_state.
        (ZoneInfo::m_pos): Reduce the bit with to 14.
        (ZoneInfo::m_state): Expands m_expanded with four state enum.
        (ZoneInfo::ZoneInfo, ZoneInfo::expanded, ZoneInfo::set_abbrev):
        Replace m_expanded usage with m_state and State::Expanded.
        (Transitions, find_surrounding_transitions): Extracted from
        find_active_rule.
        (find_active_rule): Define overload for local_seconds. Implement
        both in terms of find_surrounding_transitions.
        (chrono::reload_tzdb): Calculate save (invoke calc_save) for
        all infos on all zones.
        (time_zone::_M_get_sys_info): Define rules as span<const Rules>,
        and remove the comment.
        (operator>>(istream&, ZoneInfo&)): Set m_state for UntilPending
        for UNTIL time using wall time, and SaveKnown for lines without
        end date. For clarity, set m_save to 0 if daylight is not used.
        * testsuite/std/time/time_zone/116110.cc (test_apia): Use correct
        value of the the total offset.
        testsuite/std/time/time_zone/pr116110_named.cc: New test.

Reviewed-by: Jonathan Wakely <[email protected]>
Co-authored-by: Álvaro Begué <[email protected]>
Signed-off-by: Álvaro Begué <[email protected]>
Signed-off-by: Tomasz Kamiński <[email protected]>
        (cherry picked from commits 020e02fcf281bd24065c8663219a22aba9d0a47d)
        (4426e08a4fd0d35643d16a14c9e9b81488823a23)
        (TODO xxxxxxxx)
---
This incorportes also 
https://gcc.gnu.org/pipermail/libstdc++/2026-July/067366.html
(TODO xxxxx) should be replaced with the commit one merged.
I have updated the description accordingly.

I think that increase from 1.9s to 2.1s for time of loading tzdb is
acceptable, and makes this commit much safer to backport. For full
commit results look at the linked test.

Tested on powerpc64le and x86_64-linux. Validated that all differences
against date are addressed. OK for GCC-16, after landing linked
patch to 16.

 libstdc++-v3/src/c++20/tzdb.cc                | 210 +++++++++++++-----
 .../testsuite/std/time/time_zone/116110.cc    |   4 +-
 .../std/time/time_zone/pr116110_named.cc      |  74 ++++++
 .../std/time/time_zone/wall_cascade.cc        |  73 ++++++
 4 files changed, 308 insertions(+), 53 deletions(-)
 create mode 100644 libstdc++-v3/testsuite/std/time/time_zone/pr116110_named.cc

diff --git a/libstdc++-v3/src/c++20/tzdb.cc b/libstdc++-v3/src/c++20/tzdb.cc
index 6af5768a58e..93a31e55683 100644
--- a/libstdc++-v3/src/c++20/tzdb.cc
+++ b/libstdc++-v3/src/c++20/tzdb.cc
@@ -478,6 +478,8 @@ namespace std::chrono
        select_std_or_dst_abbrev(info.abbrev, info.save);
     }
 
+    struct Rule;
+
     // A time zone information record.
     // Zone  NAME        STDOFF  RULES   FORMAT  [UNTIL]
     // Zone  Asia/Amman  2:00    Jordan  EE%sT   2017 Oct 27 01:00
@@ -487,12 +489,12 @@ namespace std::chrono
       ZoneInfo() = default;
 
       ZoneInfo(sys_info&& info)
-      : m_buf(std::move(info.abbrev)), m_expanded(true), m_save(info.save),
+      : m_buf(std::move(info.abbrev)), m_state(Expanded), m_save(info.save),
        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_state(Expanded), m_save(info.first.save),
        m_offset(info.first.offset - seconds(info.first.save)),
                m_until(info.first.end)
       {
@@ -528,17 +530,23 @@ namespace std::chrono
       sys_seconds
       until() const noexcept { return m_until; }
 
+      // For non-expanded zone (using rules) computes the value
+      // of 'save' at the time of transitions, caches it in m_save.
+      // Returns true if until value was changed in the process.
+      bool
+      calc_save(span<const Rule> all_rules) noexcept;
+
       friend istream& operator>>(istream&, ZoneInfo&);
 
       bool
       expanded() const noexcept
-      { return m_expanded; }
+      { return m_state == Expanded; }
 
       // For an expanded ZoneInfo this returns the LETTERS that apply to the
       // **next** sys_info after this one.
       string_view
       next_letters() const noexcept
-      { return m_expanded ? rules() : string_view{}; }
+      { return (m_state == Expanded) ? rules() : string_view{}; }
 
 
       bool
@@ -546,7 +554,7 @@ namespace std::chrono
       {
        // If this object references a named Rule then we can't populate
        // a sys_info yet.
-       if (!m_expanded)
+       if (m_state != Expanded)
          return false;
 
        info.end = until();
@@ -560,12 +568,16 @@ namespace std::chrono
     private:
       friend class time_zone;
 
+      enum class State : uint_least16_t
+      { Expanded, SaveKnown, SavePending, UntilPending };
+      using enum State;
+
       void
       set_abbrev(string abbrev)
       {
        m_buf = std::move(abbrev);
        m_pos = 0;
-       m_expanded = true;
+       m_state = Expanded;
       }
 
       void
@@ -581,8 +593,8 @@ namespace std::chrono
       }
 
       string m_buf;     // rules() + ' ' + format() OR letters + ' ' + format()
-      uint_least16_t m_pos : 15 = 0; // offset of format() in m_buf
-      uint_least16_t m_expanded : 1 = 0;
+      uint_least16_t m_pos : 14 = 0; // offset of format() in m_buf
+      State m_state : 2 = SavePending;
       duration<int_least16_t, ratio<60>> m_save{};
       sec32_t m_offset{};
       sys_seconds m_until{};
@@ -690,14 +702,27 @@ namespace std::chrono
 #endif
     };
 
-    const Rule*
-    find_active_rule(span<const Rule> rules, sys_seconds t, seconds std_offset)
+    struct Transitions
     {
-      struct Transition {
+      struct Entry
+      {
        const Rule* rule;
        sys_seconds when;
       };
 
+      Entry prev{nullptr, sys_seconds::min()};
+      Entry curr{nullptr, sys_seconds::min()};
+      Entry next{nullptr, sys_seconds::max()};
+    };
+
+    // Collect transitions of rules surrounding specified time t:
+    // * .curr - rule active directly before or at t,
+    // * .prev - rule transition before trans.curr
+    // * .next - rule transition directly after t
+    Transitions
+    find_surrounding_transitions(span<const Rule> rules, sys_seconds t,
+                                seconds std_offset)
+    {
       const year_month_day date(chrono::floor<days>(t));
       // Expand the search window for a time near the end
       // of the year which can be pushed to next/previous
@@ -711,16 +736,7 @@ namespace std::chrono
        if (date.month() == January && date.day() == day(1))
          --first_year;
 
-      // Rule specifying start time as Wall time, should apply
-      // running 'save' accumulated by earlier rules. To handle
-      // that we firstly collect transitions surrounding specified
-      // time t, ignoring the 'save':
-      // * curr_tran - rule active directly before or at t,
-      // * prev_tran - rule transition before curr_tran
-      // * next_tran - rule transition directly after t
-      Transition prev_tran{nullptr, sys_seconds::min()};
-      Transition curr_tran{nullptr, sys_seconds::min()};
-      Transition next_tran{nullptr, sys_seconds::max()};
+      Transitions trans;
       for (const auto& rule : rules)
        {
          if (last_year < rule.from) // Rule doesn't apply yet at time t.
@@ -756,7 +772,7 @@ namespace std::chrono
 
          if (first_year > rule.to)
            // Rule no longer applies at time t, record last transition
-            start_before = rule.start_time(rule.to, offset);
+           start_before = rule.start_time(rule.to, offset);
          else if (first_year == last_year)
            for_year(first_year);
          else
@@ -768,24 +784,35 @@ namespace std::chrono
               for_year(last_year);
           }
 
-         if (curr_tran.when < start_before)
+         if (trans.curr.when < start_before)
            {
-             prev_tran = curr_tran;
-             curr_tran = {&rule, start_before};
+             trans.prev = trans.curr;
+             trans.curr = {&rule, start_before};
            }
-         else if (prev_tran.when < start_before)
-           prev_tran = {&rule, start_before};
+         else if (trans.prev.when < start_before)
+           trans.prev = {&rule, start_before};
 
-         if (start_after < next_tran.when)
-           next_tran = {&rule, start_after};
+         if (start_after < trans.next.when)
+           trans.next = {&rule, start_after};
        }
+      return trans;
+    }
+
+    const Rule*
+    find_active_rule(span<const Rule> rules, sys_seconds t, seconds std_offset)
+    {
+      // Rule specifying start time as Wall time, should apply
+      // running 'save' accumulated by earlier rules. To handle
+      // that we firstly collect transitions surrounding specified
+      // time t:
+      auto trans = find_surrounding_transitions(rules, t, std_offset);
 
       // No rule was active at the time of t, running 'save'
       // cannot change this output, as we have no save to apply.
-      if (!curr_tran.rule)
+      if (!trans.curr.rule)
        return nullptr;
 
-      auto cascade_save = [](const Rule* from, Transition& to)
+      auto cascade_save = [](const Rule* from, Transitions::Entry& to)
       {
        if (!from || from->save == seconds(0))
          return false;
@@ -795,19 +822,53 @@ namespace std::chrono
        return true;
       };
 
-      if (cascade_save(curr_tran.rule, next_tran))
-       // Running save moved what we considered next_tran to time
-       // before or at t, in that case next_tran is active rule.
-       if (next_tran.when <= t)
-         return next_tran.rule;
+      if (cascade_save(trans.curr.rule, trans.next))
+       // Running save moved what we considered trans.next to time
+       // before or at t, in that case trans.next is active rule.
+       if (trans.next.when <= t)
+         return trans.next.rule;
+
+      if (cascade_save(trans.prev.rule, trans.curr))
+       // Running save moved what we consider trans.curr to
+       // time after t, in that case trans.prev is active rule.
+       if (trans.curr.when > t)
+         return trans.prev.rule;
+
+      return trans.curr.rule;
+    }
+
+    const Rule*
+    find_active_rule(span<const Rule> rules, local_seconds t, seconds 
std_offset)
+    {
+      // UNTIL or START time in local time should take a 'save' from
+      // active rule. To handle tapproximate system time at, and
+      // collect transitions surrounding it:
+      const sys_seconds at(t.time_since_epoch() - std_offset);
+      const auto trans = find_surrounding_transitions(rules, at, std_offset);
 
-      if (cascade_save(prev_tran.rule, curr_tran))
-       // Running save moved what we consider curr_tran to
-       // time after t, in that case prev_tran is active rule.
-       if (curr_tran.when > t)
-         return prev_tran.rule;
+      auto to_local = [&](const Transitions::Entry& tran, const Rule* before)
+      {
+       local_seconds ls(tran.when.time_since_epoch());
+       if (!tran.rule)
+         // 'when' is either min() or max(), and applying std_offset overflows.
+         return ls;
+
+       ls += std_offset;
+       if (!before || tran.rule->when.indicator == at_time::Wall)
+         // no active rule or 'when' was converted from local time without
+         // considering running 'save' in first place
+         return ls;
+
+       return ls + before->save;
+      };
 
-      return curr_tran.rule;
+      // Translate the transitions time to local time, taking 'save'
+      // into consideration, and recheck active rule.
+      if (to_local(trans.next, trans.curr.rule) <= t)
+       return trans.next.rule;
+      if (to_local(trans.curr, trans.prev.rule) > t)
+       return trans.prev.rule;
+      return trans.curr.rule;
     }
 
     const Rule*
@@ -830,6 +891,43 @@ namespace std::chrono
        }
       return first;
     }
+
+    bool
+    ZoneInfo::calc_save(span<const Rule> all_rules) noexcept
+    {
+      // Expanded rule, or the save value was already computed.
+      if (m_state < SavePending)
+       return false;
+
+      // Find the rules named by rules()
+      span<const Rule> sel_rules
+       = ranges::equal_range(all_rules, rules(), ranges::less{}, &Rule::name);
+      if (sel_rules.empty())
+       __throw_runtime_error("std::chrono::time_zone::get_info: invalid data");
+
+      if (m_state == UntilPending)
+       {
+         // UNTIL was specified in Wall time, so it is affected by 'save'.
+         // Convert it back to local time, and find active rule using that.
+         const local_seconds lu(m_until.time_since_epoch() + m_offset);
+         if (const Rule* rule = find_active_rule(sel_rules, lu - seconds(1), 
m_offset))
+           if (rule->save.count() != 0)
+             {
+               m_state = SaveKnown;
+               m_save = duration_cast<minutes>(rule->save);
+               m_until -= rule->save;
+               return true;
+             }
+        }
+      else
+       // UNTIL was either specified in Universal time, or Standard
+       // time (known total offset). m_until is corresponding sys_time point.
+       if (const Rule* rule = find_active_rule(sel_rules, m_until - 
seconds(1), m_offset))
+         m_save = duration_cast<minutes>(rule->save);
+
+      m_state = SaveKnown;
+      return false;
+    }
   } // namespace
 #endif // TZDB_DISABLED
 
@@ -966,7 +1064,7 @@ namespace std::chrono
        if (infos.empty())
          __throw_runtime_error("std::chrono::time_zone::get_info: invalid 
data");
        tp = (--i)->until();
-    }
+      }
 
     sys_info info;
 
@@ -985,10 +1083,10 @@ namespace std::chrono
     const ZoneInfo& ri = *i;
 
     // Find the rules named by ri.rules()
-    auto rules = ranges::equal_range(node->rules, ri.rules(),
-                                    ranges::less{}, &Rule::name);
+    span<const Rule> rules = ranges::equal_range(node->rules, ri.rules(),
+                                                ranges::less{}, &Rule::name);
 
-    if (ranges::empty(rules))
+    if (rules.empty())
       __throw_runtime_error("std::chrono::time_zone::get_info: invalid data");
 
     vector<pair<sys_info, string_view>> new_infos;
@@ -1007,8 +1105,6 @@ namespace std::chrono
     // rule changes always occur between periods of standard time.
     info.offset = ri.offset();
     info.save = 0min;
-    // XXX The ri.until() time point should be
-    // "interpreted using the rules in effect just before the transition"
     info.end = ri.until();
     info.abbrev = ri.format();
 
@@ -1925,6 +2021,11 @@ constinit tzdb_list::_Node::NumLeapSeconds 
tzdb_list::_Node::num_leap_seconds;
       return lhs.save < rhs.save;
     });
 
+    // Calculate the SAVE value at UNTIL, and adjust it if necessary.
+    for (time_zone& tz : node->db.zones)
+      for (ZoneInfo& info : tz._M_impl->infos)
+       info.calc_save(node->rules);
+
     return Node::_S_replace_head(std::move(head), std::move(node));
 #else
     __throw_disabled();
@@ -2630,6 +2731,7 @@ constinit tzdb_list::_Node::NumLeapSeconds 
tzdb_list::_Node::num_leap_seconds;
          if (rules == "-")
            {
              // Standard time always applies, no DST.
+             inf.m_save = minutes(0);
            }
          else
            {
@@ -2663,14 +2765,20 @@ constinit tzdb_list::_Node::NumLeapSeconds 
tzdb_list::_Node::num_leap_seconds;
              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.
+                 if (inf.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 // else Named Rule, SAVE is unknown, mark as pending
+                   inf.m_state = ZoneInfo::UntilPending;
                }
            }
        }
       else
-       inf.m_until = sys_days(year::max()/December/31);
+       {
+         inf.m_until = sys_days(year::max()/December/31);
+         // The line does not define UNTIL, so it is not affected by save
+         if (!inf.expanded())
+           inf.m_state = ZoneInfo::SaveKnown;
+       }
 
       in.clear(in.rdstate() & ios::eofbit);
       in.exceptions(ex);
diff --git a/libstdc++-v3/testsuite/std/time/time_zone/116110.cc 
b/libstdc++-v3/testsuite/std/time/time_zone/116110.cc
index 3751c5df910..45daed8c050 100644
--- a/libstdc++-v3/testsuite/std/time/time_zone/116110.cc
+++ b/libstdc++-v3/testsuite/std/time/time_zone/116110.cc
@@ -65,8 +65,8 @@ test_apia()
   auto* tz = locate_zone("Pacific/Apia");
   local_seconds t = local_days(2011y/December/29) + 24h;
 
-  // FIXME: this should be + 10h but we do not account for DST yet, so + 11h.
-  sys_seconds ut(t.time_since_epoch() + 11h );
+  // The wall UNTIL is interpreted in the prior offset (-11h + 1h)
+  sys_seconds ut(t.time_since_epoch() + 10h);
   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/pr116110_named.cc 
b/libstdc++-v3/testsuite/std/time/time_zone/pr116110_named.cc
new file mode 100644
index 00000000000..b3bf4eb1aa9
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/time/time_zone/pr116110_named.cc
@@ -0,0 +1,74 @@
+// { 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* } }
+
+// Africa/Algiers 1977-10-21: a Zone line whose RULES references a
+// named Rule and whose UNTIL is a wall-time expression.  The wall
+// UNTIL is interpreted using the SAVE value in force just before the
+// boundary (the May-6 rule's save=1, not the Oct-21 rule's save=0
+// even though the Oct-21 rule fires at the same wall instant).
+//
+//   Rule d 1977 May  6 0:00 wall  save=1
+//   Rule d 1977 Oct 21 0:00 wall  save=0
+//   Z A    0 d WE%sT 1977 O 21
+//          1 d CE%sT
+
+#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_pr116110_named
+R d 1977 o - May  6 0 1 S
+R d 1977 o - O   21 0 0 -
+Z Test/Algiers 0 d WE%sT 1977 O 21
+               1 d CE%sT
+)";
+
+  const auto& db = reload_tzdb();
+  VERIFY( override_used );
+  VERIFY( db.version == "test_pr116110_named" );
+
+  auto* tz = locate_zone("Test/Algiers");
+
+  // Just before the boundary: still in the first Zone line under
+  // the May-6 rule (save=1, WEST, total +1).
+  auto pre = tz->get_info(sys_days{1977y/October/20} + 22h);
+  VERIFY( pre.offset == 1h );
+  VERIFY( pre.save == 1h );
+  VERIFY( pre.abbrev == "WEST" );
+
+  // The boundary is Oct 20 23:00 UTC (= wall 00:00 - stdoff(0) - save(1)).
+  // At and after the boundary we are in the second line (CET).
+  auto at = tz->get_info(sys_days{1977y/October/20} + 23h);
+  VERIFY( at.offset == 1h );    // stdoff 1 + save 0 (CET, second line)
+  VERIFY( at.save == 0min );
+  VERIFY( at.abbrev == "CET" );
+
+  // A second query inside the second line, well clear of the boundary.
+  auto after = tz->get_info(sys_days{1977y/October/21} + 12h);
+  VERIFY( after.offset == 1h );
+  VERIFY( after.save == 0min );
+  VERIFY( after.abbrev == "CET" );
+
+  // A query inside the [Oct 20 23:00, Oct 21 00:00] UTC window must be
+  // in the second line, not in a leftover stretch from the first line.
+  auto window = tz->get_info(sys_days{1977y/October/20} + 23h + 30min);
+  VERIFY( window.offset == 1h );
+  VERIFY( window.abbrev == "CET" );
+}
diff --git a/libstdc++-v3/testsuite/std/time/time_zone/wall_cascade.cc 
b/libstdc++-v3/testsuite/std/time/time_zone/wall_cascade.cc
index 3529c2de852..f9167b79612 100644
--- a/libstdc++-v3/testsuite/std/time/time_zone/wall_cascade.cc
+++ b/libstdc++-v3/testsuite/std/time/time_zone/wall_cascade.cc
@@ -261,6 +261,78 @@ Z Europe/Lisbon -0:36:45 - LMT 1884
   VERIFY( at_boundary.abbrev == "WET" );
 }
 
+void
+test_last_transition()
+{
+   std::ofstream("tzdata.zi") << R"(# version test_bishkek
+R R 1984 1995 - S lastSu 2s 0 -
+R R 1985 2010 - Mar lastSu 2s 1 S
+R R 1996 2010 - O lastSu 2s 0 -
+R KG 1992 1996 - Ap Su>=7 0s 1 -
+R KG 1992 1996 - S lastSu 0 0 -
+R KG 1997 2005 - Mar lastSu 2:30 1 -
+R KG 1997 2004 - O lastSu 2:30 0 -
+Z Test/Bishkek 4:58:24 - LMT 1924 May 2
+5 R %z 1991 Au 31 2
+5 KG %z 2005 Au 12
+   )";
+
+  const auto& db = reload_tzdb();
+  VERIFY( override_used ); // If this fails then XFAIL for the target.
+  VERIFY( db.version == "test_bishkek" );
+
+  auto* tz = locate_zone("Test/Bishkek");
+
+  sys_seconds transitions[]{
+    sys_seconds{sys_days{1991y/August/30}} + 20h, // from 5 R %z 1991 Au 31 2
+    sys_seconds{sys_days{1992y/April/11}} + 19h,
+    sys_seconds{sys_days{1992y/September/26}} + 18h,
+    sys_seconds{sys_days{1993y/April/10}} + 19h,
+    sys_seconds{sys_days{1993y/September/25}} + 18h,
+    sys_seconds{sys_days{1994y/April/9}} + 19h,
+    sys_seconds{sys_days{1994y/September/24}} + 18h,
+    sys_seconds{sys_days{1995y/April/8}} + 19h,
+    sys_seconds{sys_days{1995y/September/23}} + 18h,
+    sys_seconds{sys_days{1996y/April/6}} + 19h,
+    sys_seconds{sys_days{1996y/September/28}} + 18h,
+    sys_seconds{sys_days{1997y/March/29}} + 21h + 30min,
+    sys_seconds{sys_days{1997y/October/25}} + 20h + 30min,
+    sys_seconds{sys_days{1998y/March/28}} + 21h + 30min,
+    sys_seconds{sys_days{1998y/October/24}} + 20h + 30min,
+    sys_seconds{sys_days{1999y/March/27}} + 21h + 30min,
+    sys_seconds{sys_days{1999y/October/30}} + 20h + 30min,
+    sys_seconds{sys_days{2000y/March/25}} + 21h + 30min,
+    sys_seconds{sys_days{2000y/October/28}} + 20h + 30min,
+    sys_seconds{sys_days{2001y/March/24}} + 21h + 30min,
+    sys_seconds{sys_days{2001y/October/27}} + 20h + 30min,
+    sys_seconds{sys_days{2002y/March/30}} + 21h + 30min,
+    sys_seconds{sys_days{2002y/October/26}} + 20h + 30min,
+    sys_seconds{sys_days{2003y/March/29}} + 21h + 30min,
+    sys_seconds{sys_days{2003y/October/25}} + 20h + 30min,
+    sys_seconds{sys_days{2004y/March/27}} + 21h + 30min,
+    sys_seconds{sys_days{2004y/October/30}} + 20h + 30min,
+    sys_seconds{sys_days{2005y/March/26}} + 21h + 30min,
+    sys_seconds{sys_days{2005y/August/11}} + 18h, // 5 KG %z 2005 Au 12 
+  };
+
+  for (size_t i = 1; i < std::size(transitions); ++i)
+    {
+      const minutes save = (i % 2) ? 0h : 1h;
+      const sys_seconds t = transitions[i-1];
+      const sys_info info = tz->get_info(t);
+      VERIFY( info.begin == t );
+      VERIFY( info.offset == 5h + save );
+      VERIFY( info.save == save );
+      VERIFY( info.end == transitions[i] );
+    }
+
+  // The transition 2005 Au 12 has total offset 6 (5h + 1h).
+  sys_seconds t = transitions[std::size(transitions)-1];
+  const sys_info info = tz->get_info(t);
+  VERIFY( info.offset == 6h );
+  VERIFY( info.save == 1h );
+}
+
 int
 main()
 {
@@ -270,4 +342,5 @@ main()
   test_prev_year();
   test_earlier_year();
   test_at_boundary();
+  test_last_transition();
 }
-- 
2.55.0

Reply via email to