On Tue, 19 May 2026 at 09:02, Tomasz Kamiński <[email protected]> wrote:
>
> Below including a quick draft of alternative solution where the merging
> is integragted into pre-existing loop. We use merge_window (non-zero
> only after DST chgange), and skip adding sys_info related to them.
>
> Let me know what you think about these approach. I certainly like it
> more.
> ---
>  libstdc++-v3/src/c++20/tzdb.cc                | 58 ++++++++-----
>  .../std/time/time_zone/zone_merge.cc          | 84 +++++++++++++++++++
>  2 files changed, 122 insertions(+), 20 deletions(-)
>  create mode 100644 libstdc++-v3/testsuite/std/time/time_zone/zone_merge.cc
>
> diff --git a/libstdc++-v3/src/c++20/tzdb.cc b/libstdc++-v3/src/c++20/tzdb.cc
> index 183a0c61a61..e9a348cc3c8 100644
> --- a/libstdc++-v3/src/c++20/tzdb.cc
> +++ b/libstdc++-v3/src/c++20/tzdb.cc
> @@ -28,6 +28,8 @@
>  // In the usual dual-abi build, std::chrono::tzdb is only defined for cxx11.
>  #define _GLIBCXX_USE_CXX11_ABI 1
>
> +#define _GLIBCXX_ASSERTIONS
> +
>  #include <chrono>
>  #include <fstream>    // ifstream
>  #include <sstream>    // istringstream
> @@ -78,6 +80,8 @@
>  # endif
>  #endif
>
> +#include <iostream>
> +
>  namespace __gnu_cxx
>  {
>  #ifdef _AIX
> @@ -504,6 +508,9 @@ namespace std::chrono
>        seconds
>        offset() const noexcept { return m_offset; }
>
> +      minutes
> +      save() const noexcept { return m_save; }
> +
>        // RULES: The name of the rules that apply for this period.
>        string_view
>        rules() const noexcept
> @@ -867,8 +874,15 @@ namespace std::chrono
>      info.abbrev = ri.format();
>
>      string_view letters;
> -    if (i != infos.begin() && i[-1].expanded())
> -      letters = i[-1].next_letters();
> +    seconds prev_offset = ri.offset();
> +    if (i == infos.begin())
> +      prev_offset = ri.offset();
> +    else if (const ZoneInfo& prev = i[-1]; !prev.expanded())
> +      prev_offset = prev.offset();
> +    else if (letters = prev.next_letters(); letters.empty())
> +      prev_offset = prev.offset() + prev.save();
> +    else
> +      prev_offset = ri.offset();
>
>      if (letters.empty())
>        {
> @@ -940,9 +954,10 @@ namespace std::chrono
>        }
>
>      const Rule* curr_rule = nullptr;
> -
> +    seconds merge_window = (prev_offset > info.offset) ? (prev_offset - 
> info.offset) : seconds(0);
>      while (info.begin < info.end && num_after > 0)
>        {
> +
>         sys_seconds t = info.begin;
>         const year_month_day date(chrono::floor<days>(t));
>         const Rule* next_rule = nullptr;
> @@ -984,9 +999,6 @@ namespace std::chrono
>
>             if (t < rule_start && rule_start < info.end)
>               {
> -               if (rule_start - t < days(1)) // XXX shouldn't be needed!
> -                 continue;
> -
>                 // Found a closer transition than the previous info.end.
>                 info.end = rule_start;
>                 next_rule = &rule;
> @@ -995,7 +1007,6 @@ namespace std::chrono
>
>         format_abbrev_str(info, letters);
>
> -       bool merged = false;
>  #if 0

This #if 0 group was an earlier prototype for merging, so should be removed.

This draft approach certainly seems easier to reason about, and closer
to how I imagined merging would work.


>         if (!new_infos.empty())
>           {
> @@ -1015,21 +1026,28 @@ namespace std::chrono
>         else
>           letters = {};
>
> -       if (!merged)
> +       // Merge the rule with next one (full description goes here)
> +       if (bool merged = (info.end - t <= merge_window))
> +         info.begin = t;
> +       else
> +       {
>           new_infos.emplace_back(info, letters);
> +         if (info.begin <= tp && tp < info.end) // Found the result.
> +           result_index = new_infos.size() - 1;
> +         else if (result_index >= 0)
> +           {
> +             // Finish on a DST sys_info if possible, so that if we resume
> +             // generating sys_info objects after this time point, save=0
> +             // should be correct for the next sys_info.
> +             if (num_after > 1 || info.save != 0min)
> +               --num_after;
> +           }
> +         info.begin = info.end;
> +       }
>
> -       if (info.begin <= tp && tp < info.end) // Found the result.
> -         result_index = new_infos.size() - 1;
> -       else if (result_index >= 0 && !merged)
> -         {
> -           // Finish on a DST sys_info if possible, so that if we resume
> -           // generating sys_info objects after this time point, save=0
> -           // should be correct for the next sys_info.
> -           if (num_after > 1 || info.save != 0min)
> -             --num_after;
> -         }
> -
> -       info.begin = info.end;
> +       // Merging applies only when switching between rules with different
> +       // offset, for any later applications it should be zero.
> +       merge_window = seconds(0);
>         if (next_rule)
>           {
>             info.end = ri.until();
> diff --git a/libstdc++-v3/testsuite/std/time/time_zone/zone_merge.cc 
> b/libstdc++-v3/testsuite/std/time/time_zone/zone_merge.cc
> new file mode 100644
> index 00000000000..75942f976b6
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/std/time/time_zone/zone_merge.cc
> @@ -0,0 +1,84 @@
> +// { 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* } }
> +
> +// When two adjacent Zone lines differ in total offset and the new line's
> +// rule set has a rule firing within |jump| of the boundary (where jump
> +// is a backward local-time jump), zic.c's writezone folds that rule
> +// into the boundary, so the new line begins with the post-rule save.
> +//
> +// Mirrors America/Argentina/Buenos_Aires around 1999-10-03.
> +
> +#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;
> +
> +  // stdoff jumps from -3 to -4 at the same instant a save=1 rule fires.
> +  // In the new (-4) frame the rule fires 1 hour after the boundary at
> +  // UT 03:00, so the merge folds the rule into the boundary and the
> +  // new line begins at offset=-3, save=1 (abbrev "-03").
> +  std::ofstream("tzdata.zi") << R"(# version test_zone_merge
> +R T 1999 o - O 3 0 1 -
> +R T 2000 o - Mar 3 0 0 -
> +Z Test/BA -3 -  %z  1999 O 3
> +          -4 T  %z  2000 Mar 3
> +          -3 -  %z
> +)";
> +
> +  const auto& db = reload_tzdb();
> +  VERIFY( override_used );
> +  VERIFY( db.version == "test_zone_merge" );
> +
> +  auto* tz = locate_zone("Test/BA");
> +
> +  // The boundary is the wall UNTIL "1999 O 3" (default time 00:00)
> +  // interpreted in the prior (-3) frame, i.e. UT 03:00 1999-10-03.
> +  sys_seconds boundary{sys_days(1999y/October/3) + 3h};
> +
> +  auto before = tz->get_info(boundary - 1s);
> +  VERIFY( before.offset == -3h );
> +  VERIFY( before.save == 0min );
> +  VERIFY( before.abbrev == "-03" );
> +
> +  // The new line's first sys_info already has save=1 from the merge,
> +  // total offset -3h, abbrev "-03".
> +  auto at_boundary = tz->get_info(boundary);
> +  VERIFY( at_boundary.offset == -3h );
> +  VERIFY( at_boundary.save == 60min );
> +  VERIFY( at_boundary.abbrev == "-03" );
> +
> +  auto plus_30min = tz->get_info(boundary + 30min);
> +  VERIFY( plus_30min.offset == -3h );
> +  VERIFY( plus_30min.save == 60min );
> +  VERIFY( plus_30min.abbrev == "-03" );
> +
> +  // Sanity: well after the boundary, still in the merged sys_info
> +  // until the Mar 3 2000 transition.
> +  auto winter = tz->get_info(sys_days(2000y/January/15));
> +  VERIFY( winter.offset == -3h );
> +  VERIFY( winter.save == 60min );
> +  VERIFY( winter.abbrev == "-03" );
> +
> +  // After Mar 3 2000: line 2 ends, line 3 begins.  No DST rule fires
> +  // at this boundary, so total offset reverts to -3h with save=0.
> +  auto spring = tz->get_info(sys_days(2000y/April/15));
> +  VERIFY( spring.offset == -3h );
> +  VERIFY( spring.save == 0min );
> +  VERIFY( spring.abbrev == "-03" );
> +}
> --
> 2.54.0
>

Reply via email to