On Fri, 17 Jul 2026, 12:45 Tomasz Kamiński, <[email protected]> wrote:

> As when member of Transitions::Entries that do not correspond
> to rule activation (rule is empty) is either minimum or maximum
> value of sys_seconds, adjusting it to std_offset lead to overflow
> (and UB). In practice this caused next.when to have very low value,
> and thus considered to be active rule. The to_local conversion now
> return local_seconds constucted directly from time_since_epoch
> (i.e. minimum and maximum).
>
> In consequence above, the save was still not applied for zones
> that specified UNTIL after firing of last rule, that aslo have
> non zero save (e.g. Asia/Bishkek 2005 transition).
>
> libstdc++-v3/ChangeLog:
>
>         PR libstdc++/116110
>         * src/c++20/tzdb.cc (find_active_rule::to_local): Do not
>         modify min/max values for rules.
>         * testsuite/std/time/time_zone/wall_cascade.cc: Add test
>         for last_transition.
> ---
> I haven't noticed this at first, because this was not causing
> any regression (the save was not applied before). Found that
> while analysing output of the comparision.
>
> For output comparision I am using a slightly different program
> than your. It quries sys_info object for give time from
> both libstdc++ and date, and compares abbrev, offset and save.
> Then I move to time point at the end of sys_info that finished
> first, and perform check again.
>

Yes that's a good way to handle adjacent unmerged infos.



> The above does not fail if the time range of same offset, save
> and abbrev is split differently between libstdc++ and date.
> (I still emit a WARN on that, but I do not think that fixing
> them is necessary).
>
> Testing on x86_64-linux. *time_zone* test passed.
> OK for trunk?
>

OK



>  libstdc++-v3/src/c++20/tzdb.cc                | 13 ++--
>  .../std/time/time_zone/wall_cascade.cc        | 73 +++++++++++++++++++
>  2 files changed, 81 insertions(+), 5 deletions(-)
>
> diff --git a/libstdc++-v3/src/c++20/tzdb.cc
> b/libstdc++-v3/src/c++20/tzdb.cc
> index e2d607dccb9..460a242fed1 100644
> --- a/libstdc++-v3/src/c++20/tzdb.cc
> +++ b/libstdc++-v3/src/c++20/tzdb.cc
> @@ -853,12 +853,15 @@ namespace std::chrono
>
>        auto to_local = [&](const Transitions::Entry& tran, const Rule*
> before)
>        {
> -       local_seconds ls(tran.when.time_since_epoch() + std_offset);
> -       if (!before || !tran.rule)
> +       local_seconds ls(tran.when.time_since_epoch());
> +       if (!tran.rule)
> +         // `when` is either min() or max(), and applying std_offset
> overflows.
>           return ls;
> -       if (tran.rule->when.indicator == at_time::Wall)
> -         // 'when' was converted from local time without considering
> -         // running 'save' in first place
> +
> +       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;
>        };
> 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