[16][PATCH] libstdc++: Cascade wall-time saves in lazy expansion seeding [PR124853]

Tomasz Kaminski tkaminsk@redhat.com
Thu Jul 23 11:30:14 GMT 2026


On Thu, Jul 23, 2026 at 1:28 PM Tomasz Kamiński <tkaminsk@redhat.com> wrote:

> When _M_get_sys_info seeds a Zone line by looking up the active rule
> just before info.begin, the previous code interpreted each rule in
> isolation against ri.offset() (the line's standard offset alone),
> ignoring the running save accumulated by earlier rules in the same
> year. For most zones this gives the right answer because the search
> only matters when no rule has fired yet, but for zones whose rule
> set has wall-time rules whose effective firing time depends on a
> prior rule's save it produces wrong answers.
>
> Canonical case: Europe/Paris around 1945.  France's rules
>
>   R Fr 1945 o - Apr 2  2 2 M
>   R Fr 1945 o - Sep 16 3 0 -
>
> both use plain wall time. In Paris's stdoff=1 frame, the September
> rule's at_time of 03:00 wall translates to UT Sep 16 02:00 if no
> prior save is applied, but to UT Sep 16 00:00 once the running save
> of 2h from the April rule is taken into account.  When seeding a
> sys_info whose info.begin falls between those two values, the simple
> search picks the April rule (save=2 → CEMT, total offset 3h) when
> the correct answer is the September rule (save=0 → CET, total offset
> 1h). libstdc++ reports this as a sustained CEMT stretch where zic
> and libc agree on CET.
>
> To address above we now consider rules triggering at info.begin (not
> only before), and the find algorithm is expanded to collect three
> rule transitions around specified time t, while continuing to ignore
> the save:
> * curr_tran: transition happening before or at time t,
> * prev_tran: transition preceding above transition,
> * next_tran: transition happening after time t.
>
> This collects sufficient information to adjust the start_time (if
> Wall time is used) for curr_tran (save of prev_tran) and next_tran
> (save of curr_tran). Assuming that applying save value does not
> change order of transition (cascading save would be ill-defined
> otherwise), after the adjustment the actual active rule is:
>  * next_tran.rule: if the adjustment pushed next_tran.when to
>    time before or at t, which happen for positive save (see
>    test_positive),
>  * prev_tran.rule: if adjustment pushed curr_tran.when to time
>    after time t, which happens for negative save (see test_negative),
>  * curr_tran.rule.
>
> For the time at the start (Jan/1) or end of the year (December/31),
> for each rule, in addition to transition in year of t, we check
> transitions in previous or next year respectively (years in range
> [first_year, last_year]). This handle rules whose firing (specified
> in local time) crosses a year boundary due to a large stdoff or save.
> One example is Pacific/Auckland's 1946 Jan 1 rule, in stdoff=12h,
> fires at 1945-12-31 11:30 UT, see test_next_year.
>
> The fallback "earliest STD rule" logic is preserved for the case
> where no rule has fired yet, but is extracted to separate function.
> This lookup is optimized, by searching the rules by name, from, and
> save in that order, grouping std rules in given year together.
>
>         PR libstdc++/124853
>
> libstdc++-v3/ChangeLog:
>
>         * src/c++20/tzdb.cc
>         (time_zone::_M_get_sys_info): Extract code blocks to
>         separate functions, and invoke them.
>         (<unnamed>::find_active_rule): Modify algorithm to
>         handle cascading saves.
>         (<unnamed>::find_first_std): Simplified implementation
>         benefiting from reordering of rules.
>         (chrono::reload_tzdb): Sort rules by name, from and save.
>         * testsuite/std/time/time_zone/wall_cascade.cc: New test.
>
> Reviewed-by: Tomasz Kamiński <tkaminsk@redhat.com>
> Co-authored-by: Álvaro Begué <alvaro.begue@gmail.com>
> Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>
> Signed-off-by: Álvaro Begué <alvaro.begue@gmail.com>
>
>         (cherry picked from commits
> f7cde200320e0800e4fcc5cea399e9faef50f8d4)
>         (610134a51fa3d0bcd3e0980c6574557172dc88c0)
> ---
> This squashes "- seconds(1)" from the follow up (r17-2462-g610134a51fa3d0),
> and I have modified the first sentece of paragraph of commit description
> that starts with:
>  To address above we now consider rules triggering at info.begin...
>
> As mentioned in the follow up patch, this is now pure improvment in
> consistency with date library and reduces number of differences to
> 271 from 422 (after parsing/stop condition patches) and 635 (16.1)
> respectively.
>
> I have also tried to measure the performance impact on this change,
> but the difference is affecting only first query. So I have tried
> to measure the time it takes to iterate each Zone by sys_info
> (query end next) from year -5000 to 2000. This is not perfect
> as we expand follow up zones, but the best think I could come up
> with. The percante difference between state before/this patch
> as follows (PXX% is XX percentile):
> MIN      MED    P90%    P95%    P99%    MAX       AVG
> -60,56%  0.55%  46.42%  56.35%  80.4%%  175.46%   0.43%
>
I have attached the csv file that lists the results collected for all
zones.

>
> It's hard to make anything from this numbers, and we cannot really
> do repeated measurments. The Avarate is certainly acceptable, and
> that does not affect any following calls.
>
> (I believe the reduction come from the fact that finding first DST
> rule is now much quicker).
>
> As the original patch is on trunk since Jun 30, and the follow up
> is really small change, I think given above this is fine to backport.
>
> Tested on x86_64-linux locally and powerpc64le. OK for GCC-16?
>
>  libstdc++-v3/src/c++20/tzdb.cc                | 209 ++++++++++----
>  .../std/time/time_zone/wall_cascade.cc        | 273 ++++++++++++++++++
>  2 files changed, 425 insertions(+), 57 deletions(-)
>  create mode 100644
> libstdc++-v3/testsuite/std/time/time_zone/wall_cascade.cc
>
> diff --git a/libstdc++-v3/src/c++20/tzdb.cc
> b/libstdc++-v3/src/c++20/tzdb.cc
> index a1148355303..6af5768a58e 100644
> --- a/libstdc++-v3/src/c++20/tzdb.cc
> +++ b/libstdc++-v3/src/c++20/tzdb.cc
> @@ -689,6 +689,147 @@ namespace std::chrono
>        }
>  #endif
>      };
> +
> +    const Rule*
> +    find_active_rule(span<const Rule> rules, sys_seconds t, seconds
> std_offset)
> +    {
> +      struct Transition {
> +       const Rule* rule;
> +       sys_seconds when;
> +      };
> +
> +      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
> +      // year by 'offset'. This assumes that 'offset' is
> +      // never greater than 24h.
> +      year first_year = date.year(), last_year = date.year();
> +      if (std_offset.count() > 0)
> +       if (date.month() == December && date.day() == day(31))
> +         ++last_year;
> +      if (std_offset.count() < 0)
> +       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()};
> +      for (const auto& rule : rules)
> +       {
> +         if (last_year < rule.from) // Rule doesn't apply yet at time t.
> +           break; // Rules are ordered by 'from' in ascending order.
> +
> +         seconds offset{}; // appropriate for at_time::Universal
> +         if (rule.when.indicator == at_time::Wall
> +             || rule.when.indicator == at_time::Standard)
> +           offset = std_offset;
> +
> +         // Times at which rule takes affect before (or equal) t,
> +         // and after t, respectively.
> +         sys_seconds start_before = sys_seconds::min();
> +         sys_seconds start_after = sys_seconds::max();
> +         auto for_year = [&](year y)
> +         {
> +           // Time the rule takes effect on year y:
> +           const sys_seconds rule_start = rule.start_time(y, offset);
> +           if (rule_start <= t)
> +             {
> +               start_before = rule_start;
> +               if (y == last_year && rule.to > y)
> +                 start_after = rule.start_time(++y, offset);
> +             }
> +           // Do not override start_after set for first_year
> +           else if (rule_start < start_after)
> +             {
> +               start_after = rule_start;
> +               if (y == first_year && rule.from < y)
> +                 start_before = rule.start_time(--y, offset);
> +             }
> +         };
> +
> +         if (first_year > rule.to)
> +           // Rule no longer applies at time t, record last transition
> +            start_before = rule.start_time(rule.to, offset);
> +         else if (first_year == last_year)
> +           for_year(first_year);
> +         else
> +          {
> +            // Local time may of t may be in prev/next year.
> +            if (first_year >= rule.from)
> +              for_year(first_year);
> +            if (last_year <= rule.to)
> +              for_year(last_year);
> +          }
> +
> +         if (curr_tran.when < start_before)
> +           {
> +             prev_tran = curr_tran;
> +             curr_tran = {&rule, start_before};
> +           }
> +         else if (prev_tran.when < start_before)
> +           prev_tran = {&rule, start_before};
> +
> +         if (start_after < next_tran.when)
> +           next_tran = {&rule, start_after};
> +       }
> +
> +      // 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)
> +       return nullptr;
> +
> +      auto cascade_save = [](const Rule* from, Transition& to)
> +      {
> +       if (!from || from->save == seconds(0))
> +         return false;
> +       if (!to.rule || to.rule->when.indicator != at_time::Wall)
> +          return false;
> +       to.when -= from->save;
> +       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(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;
> +
> +      return curr_tran.rule;
> +    }
> +
> +    const Rule*
> +    find_first_std(span<const Rule> rules)
> +    {
> +      auto is_std = [](const Rule& rule) { return !rule.save.count(); };
> +      // Rules with same name are sorted by 'from' and then 'save' in
> ascending order.
> +      auto it = ranges::find_if(rules, is_std);
> +      if (it == rules.end())
> +       return nullptr;
> +
> +      const Rule* first = &*it;
> +      const year y = first->from;
> +      for (const Rule& next : span<const Rule>(++it, rules.end()))
> +       {
> +         if (!is_std(next) || next.from > y)
> +           break;
> +         if (next.start_time(y, {}) < first->start_time(y, {}))
> +           first = &next;
> +       }
> +      return first;
> +    }
>    } // namespace
>  #endif // TZDB_DISABLED
>
> @@ -877,70 +1018,17 @@ namespace std::chrono
>
>      if (letters.empty())
>        {
> -       sys_seconds t = info.begin - seconds(1);
> -       const year_month_day date(chrono::floor<days>(t));
> -
>         // Try to find a Rule active before this time, to get initial
>         // SAVE and LETTERS values. There may not be a Rule for the period
>         // before the first DST transition, so find the earliest DST->STD
>         // transition and use the LETTERS from that.
> -       const Rule* active_rule = nullptr;
> -       sys_seconds active_rule_start = sys_seconds::min();
> -       const Rule* first_std = nullptr;
> -       for (const auto& rule : rules)
> -         {
> -           if (rule.save == minutes(0))
> -             {
> -               if (!first_std)
> -                 first_std = &rule;
> -               else if (rule.from < first_std->from)
> -                 first_std = &rule;
> -               else if (rule.from == first_std->from)
> -                 {
> -                   if (rule.start_time(rule.from, {})
> -                         < first_std->start_time(first_std->from, {}))
> -                     first_std = &rule;
> -                 }
> -             }
> -
> -           year y = date.year();
> -
> -           if (y > rule.to) // rule no longer applies at time t
> -             continue;
> -           if (y < rule.from) // rule doesn't apply yet at time t
> -             continue;
> -
> -           sys_seconds rule_start;
> -
> -           seconds offset{}; // appropriate for at_time::Universal
> -           if (rule.when.indicator == at_time::Wall)
> -             offset = info.offset;
> -           else if (rule.when.indicator == at_time::Standard)
> -             offset = ri.offset();
> -
> -           // Time the rule takes effect this year:
> -           rule_start = rule.start_time(y, offset);
> -
> -           if (rule_start >= t && rule.from < y)
> -             {
> -               // Try this rule in the previous year.
> -               rule_start = rule.start_time(--y, offset);
> -             }
> -
> -           if (active_rule_start < rule_start && rule_start < t)
> -             {
> -               active_rule_start = rule_start;
> -               active_rule = &rule;
> -             }
> -         }
> -
> -       if (active_rule)
> +       if (const Rule* active_rule = find_active_rule(rules, info.begin,
> ri.offset()))
>           {
>             info.offset = ri.offset() + active_rule->save;
>             info.save = chrono::duration_cast<minutes>(active_rule->save);
>             letters = active_rule->letters;
>           }
> -       else if (first_std)
> +       else if (const Rule* first_std = find_first_std(rules))
>           letters = first_std->letters;
>        }
>
> @@ -1828,7 +1916,14 @@ constinit tzdb_list::_Node::NumLeapSeconds
> tzdb_list::_Node::num_leap_seconds;
>
>      ranges::sort(node->db.zones, {}, &time_zone::name);
>      ranges::sort(node->db.links, {}, &time_zone_link::name);
> -    ranges::stable_sort(node->rules, {}, &Rule::name);
> +    ranges::sort(node->rules, [](const Rule& lhs, const Rule& rhs)
> +    {
> +      if (auto result = lhs.name <=> rhs.name; result != 0)
> +       return result < 0;
> +      if (auto result = lhs.from <=> rhs.from; result != 0)
> +       return result < 0;
> +      return lhs.save < rhs.save;
> +    });
>
>      return Node::_S_replace_head(std::move(head), std::move(node));
>  #else
> @@ -2441,7 +2536,7 @@ constinit tzdb_list::_Node::NumLeapSeconds
> tzdb_list::_Node::num_leap_seconds;
>             {
>               if (c = in.get(); c == '<' || c == '>')
>                 if (in.get() == '=')
> -                 if (unsigned d; (in >> d) && (d <= 31)) [[likely]]
> +                 if (unsigned d; (in >> d) && (d <= 31)) [[likely]]
>                     {
>                       on.kind = c == '<' ? LessEq : GreaterEq;
>                       on.day_of_week = w.wd.c_encoding();
> diff --git a/libstdc++-v3/testsuite/std/time/time_zone/wall_cascade.cc
> b/libstdc++-v3/testsuite/std/time/time_zone/wall_cascade.cc
> new file mode 100644
> index 00000000000..3529c2de852
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/std/time/time_zone/wall_cascade.cc
> @@ -0,0 +1,273 @@
> +// { 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* } }
> +
> +// Wall-time rules in the same rule set whose effective firing time
> +// depends on a prior rule's save (Europe/Paris 1945):
> +//   1945 Apr 2  02:00 wall  save=2  M
> +//   1945 Sep 16 03:00 wall  save=0  -
> +// In the (stdoff=1, save=2) frame the September rule fires at
> +// Sep 16 00:00 UT, not Sep 16 02:00 UT.
> +
> +#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 "./";
> +  }
> +}
> +
> +using namespace std::chrono;
> +
> +void
> +test_positive()
> +{
> +  // Line 1 ends at "1945 Sep 16 1u" (Universal time, no save
> shenanigans),
> +  // so info.begin for line 2 is exactly 1945-09-16 01:00 UT.
> +  //
> +  // Two-line zone whose second line begins at 1945 Sep 16 01:00 UT,
> +  // between the cascaded firing time (Sep 16 00:00 UT) and the
> +  // non-cascaded firing time (Sep 16 02:00 UT) of the September rule.
> +  // The seeding must pick the September rule (save=0, CET) at info.begin.
> +  std::ofstream("tzdata.zi") << R"(# version test_wall_cascade
> +R Fr 1945 o - Apr 2  2 2 M
> +R Fr 1945 o - Sep 16 3 0 -
> +Z Test/Paris 0  -  X     1945 Sep 16 1u
> +             1  Fr CE%sT
> +)";
> +
> +  const auto& db = reload_tzdb();
> +  VERIFY( override_used ); // If this fails then XFAIL for the target.
> +  VERIFY( db.version == "test_wall_cascade" );
> +
> +  auto* tz = locate_zone("Test/Paris");
> +
> +  // Line 2 begins at exactly 1945-09-16 01:00 UT.  Sample one second
> +  // after the boundary, well inside line 2's first sys_info.
> +  auto info = tz->get_info(sys_seconds{
> +      sys_days(1945y/September/16) + 1h + 1s});
> +  VERIFY( info.offset == 1h );
> +  VERIFY( info.save == 0min );
> +  VERIFY( info.abbrev == "CET" );
> +
> +  // The boundary instant itself is in the new line.
> +  auto at_boundary
> +    = tz->get_info(sys_seconds{sys_days(1945y/September/16) + 1h});
> +  VERIFY( at_boundary.offset == 1h );
> +  VERIFY( at_boundary.save == 0min );
> +
> +  // Sample later still in line 2 (winter): unchanged.
> +  auto winter = tz->get_info(sys_days(1945y/December/1));
> +  VERIFY( winter.offset == 1h );
> +  VERIFY( winter.save == 0min );
> +}
> +
> +void
> +test_negative()
> +{
> +  // This is synthetic version of above example, with negative
> +  // running save.
> +  //
> +  // Two-line zone whose second line begins at 1945 Sep 16 01:00 UT,
> +  // before the cascaded firing time (Sep 16 02:00 UT), but after
> +  // non-cascaded firing time (Sep 16 00:00 UT) of the September rule.
> +  // The seeding must pick the April rule (save=-2, CEST) at info.begin.
> +  std::ofstream("tzdata.zi") << R"(# version test_negative_cascade
> +R Fr 1945 o - Apr 2  2 -3 M
> +R Fr 1945 o - Sep 16 0 0 -
> +Z Test/Negative 0  -  X     1945 Sep 16 1u
> +             1  Fr CE%sT
> +)";
> +
> +  const auto& db = reload_tzdb();
> +  VERIFY( override_used ); // If this fails then XFAIL for the target.
> +  VERIFY( db.version == "test_negative_cascade" );
> +
> +  auto* tz = locate_zone("Test/Negative");
> +
> +  // Line 2 begins at exactly 1945-09-16 01:00 UT, sample
> +  // one second after.
> +  auto info = tz->get_info(sys_seconds{
> +      sys_days(1945y/September/16) + 1h + 1s});
> +  VERIFY( info.offset == -2h );
> +  VERIFY( info.save == -3h );
> +  VERIFY( info.abbrev == "CEMT" );
> +
> +  // The boundary instant.
> +  auto at_boundary
> +    = tz->get_info(sys_seconds{sys_days(1945y/September/16) + 1h});
> +  VERIFY( at_boundary.offset == -2h );
> +  VERIFY( at_boundary.save == -3h );
> +
> +  // Test the firing of Sep 16 rule
> +  auto at_sep_rule = tz->get_info(sys_seconds{
> +    sys_days(1945y/September/16) + 2h});
> +  // The transition_window < 1d condition triggers, and
> +  // transition is ignored.
> +  // VERIFY( at_sep_rule.offset == 1h );
> +  // VERIFY( at_sep_rule.save == 0h );
> +  // VERIFY( at_sep_rule.abbrev == "CET" );
> +}
> +
> +void
> +test_next_year()
> +{
> +  // The NZ 1946 rule triggers at 1946 Jan 1 00:00:00
> +  // local time, which correspond to 1946 Dec 13 12:00:00 UT.
> +  std::ofstream("tzdata.zi") << R"(# version test_next_year
> +R NZ 1934 1940 - Ap lastSu 2 0 M
> +R NZ 1934 1940 - S lastSu 2 0:30 S
> +R NZ 1946 o - Ja 1 0 0 S
> +Z Pacific/Auckland 11:39:4 - LMT 1868 N 2
> +11:30 NZ NZ%sT 1946
> +12 NZ NZ%sT
> +Z Pacific/AucklandUT 11:39:4 - LMT 1868 N 2
> +11:30 NZ NZ%sT 1945 Dec 31 13u
> +12 NZ NZ%sT
> +)";
> +
> +  const auto& db = reload_tzdb();
> +  VERIFY( override_used ); // If this fails then XFAIL for the target.
> +  VERIFY( db.version == "test_next_year" );
> +
> +  // Pacific/Auckland requires both PR124854 and PR116110 to work
> +  // correctly. TODO test it once implemented.
> +  // The UT version uses 1945-12-31 13:00:00 UT after
> +  // the rule application change.
> +  auto* utz = locate_zone("Pacific/AucklandUT");
> +
> +  // Before the change
> +  auto before_utboundary
> +   = utz->get_info(sys_seconds{sys_days(1945y/December/31) + 11h});
> +  VERIFY( before_utboundary.offset == 12h );
> +  VERIFY( before_utboundary.save == 30min );
> +  VERIFY( before_utboundary.abbrev == "NZST" );
> +
> +  auto at_utboundary
> +    = utz->get_info(sys_seconds{sys_days(1945y/December/31) + 13h});
> +  VERIFY( at_utboundary.offset == 12h );
> +  VERIFY( at_utboundary.save == 0h );
> +  VERIFY( at_utboundary.abbrev == "NZST" );
> +
> +  auto after_utboundary
> +    = utz->get_info(sys_seconds{sys_days(1945y/December/31) + 14h});
> +  VERIFY( after_utboundary.offset == 12h );
> +  VERIFY( after_utboundary.save == 0h );
> +  VERIFY( after_utboundary.abbrev == "NZST" );
> +}
> +
> +void
> +test_prev_year()
> +{
> +  // The synthetic version of above, where the local
> +  // time for rule is moved to previous year.
> +  // The NZ 1946 rule triggers at 1946 Dec 31 22:00:00
> +  // local time, which correspond to 1947 Jan 1 10:00:00 UT.
> +  std::ofstream("tzdata.zi") << R"(# version test_prev_year
> +R PY 1934 1940 - Ap lastSu 2 0 M
> +R PY 1934 1940 - S lastSu 2 0:30 S
> +R PY 1946 o - D 31 22 0 S
> +Z Test/PrevYear -11:39:4 - LMT 1868 N 2
> +-12:30 PY PY%sT 1947 Jan 1 11u
> +-12 PY PY%sT
> +)";
> +
> +  const auto& db = reload_tzdb();
> +  VERIFY( override_used ); // If this fails then XFAIL for the target.
> +  VERIFY( db.version == "test_prev_year" );
> +
> +  // The UT version uses 1945-12-31 13:00:00 UT after
> +  // the rule application change.
> +  auto* tz = locate_zone("Test/PrevYear");
> +
> +  // Before the change
> +  auto before_boundary
> +   = tz->get_info(sys_seconds{sys_days(1947y/January/1) + 9h});
> +  VERIFY( before_boundary.offset == -12h );
> +  VERIFY( before_boundary.save == 30min );
> +  VERIFY( before_boundary.abbrev == "PYST" );
> +
> +  auto at_boundary
> +    = tz->get_info(sys_seconds{sys_days(1947y/January/1) + 11h});
> +  VERIFY( at_boundary.offset == -12h );
> +  VERIFY( at_boundary.save == 0h );
> +  VERIFY( at_boundary.abbrev == "PYST" );
> +
> +  auto after_boundary
> +    = tz->get_info(sys_seconds{sys_days(1947y/January/1) + 12h});
> +  VERIFY( after_boundary.offset == -12h );
> +  VERIFY( after_boundary.save == 0h );
> +  VERIFY( after_boundary.abbrev == "PYST" );
> +}
> +
> +void
> +test_earlier_year()
> +{
> +  // Synthetic example where PY 1941 rule is still running.
> +  std::ofstream("tzdata.zi") << R"(# version test_earlier_year
> +R EY 1934 1941 - Ap lastSu 2 0 M
> +R EY 1934 1940 - S lastSu 2 0:30 S
> +Z Test/EarielYear 11:39:4 - LMT 1868 N 2
> +11:30 EY EY%sT 1943 Jan 1 12u
> +12 EY EY%sT
> +   )";
> +
> +  const auto& db = reload_tzdb();
> +  VERIFY( override_used ); // If this fails then XFAIL for the target.
> +  VERIFY( db.version == "test_earlier_year" );
> +
> +  auto* utz = locate_zone("Test/EarielYear");
> +  auto at_boundary
> +    = utz->get_info(sys_seconds{sys_days(1943y/January/1) + 12h});
> +  VERIFY( at_boundary.offset == 12h );
> +  VERIFY( at_boundary.save == 0min );
> +  VERIFY( at_boundary.abbrev == "EYMT" );
> +}
> +
> +void
> +test_at_boundary()
> +{
> +  std::ofstream("tzdata.zi") << R"(# version test_at_boundary
> +R p 1947 1966 - Ap Su>=1 2s 1 S
> +R p 1947 1965 - O Su>=1 2s 0 -
> +R p 1976 o - S lastSu 1 0 -
> +R p 1977 o - Mar lastSu 0s 1 S
> +R p 1977 o - S lastSu 0s 0 -
> +Z Europe/Lisbon -0:36:45 - LMT 1884
> +1 - CET 1976 S 26 1
> +0 p WE%sT 1986
> +   )";
> +
> +  const auto& db = reload_tzdb();
> +  VERIFY( override_used ); // If this fails then XFAIL for the target.
> +  VERIFY( db.version == "test_at_boundary" );
> +
> +  // The change from CET to WE%sT line happens 1976 Sep 26 00:00:00 UT,
> +  // should take into consideration 1976 lastSu rule that fires at the
> +  // same time (running save is 1h from 1966 Ap rule application),
> +  // and start in standard time (WET period).
> +  auto* utz = locate_zone("Europe/Lisbon");
> +  auto at_boundary
> +    = utz->get_info(sys_seconds{sys_days(1976y/September/26) + 0h});
> +  VERIFY( at_boundary.offset == 0h );
> +  VERIFY( at_boundary.save == 0min );
> +  VERIFY( at_boundary.abbrev == "WET" );
> +}
> +
> +int
> +main()
> +{
> +  test_positive();
> +  test_negative();
> +  test_next_year();
> +  test_prev_year();
> +  test_earlier_year();
> +  test_at_boundary();
> +}
> --
> 2.55.0
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20260723/9e6b4f5d/attachment-0001.htm>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: time_diff.csv
Type: text/csv
Size: 23224 bytes
Desc: not available
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20260723/9e6b4f5d/attachment-0001.csv>


More information about the Libstdc++ mailing list