On Wed, May 6, 2026 at 4:46 PM Tomasz Kaminski <[email protected]> wrote:

>
>
> On Wed, May 6, 2026 at 4:18 PM Jonathan Wakely <[email protected]> wrote:
>
>> This change allows the hardcoded list of leap seconds in <chrono> to be
>> used even when the program is executing after the hardcoded expiry date
>> in that header. If the OS-provided leapseconds file has a later expiry
>> date (or contains new leap seconds added after the one in 2017) then the
>> new __detail::__leap_seconds_expiry() function will return that new
>> dynamically-obtained expiry date. The __detail::__get_leap_second_info
>> function in the header can check that new expiry date instead of relying
>> only on the hardcoded one.
>>
>> This change means that in the worst case we now make two calls into the
>> library (one to get the dynamic expiry date and then possibly another
>> one to get the actual list of new leap seconds). Previously we just make
>> one, to get the list. The change seems worthwhile, because it means that
>> in more cases we don't need to increment+decrement the reference count
>> on a tzdb object and use its leapseconds vector, we can just use the
>> hardcoded array.
>>
>> The new expiry date is stored in a global variable, rather than being
>> per-tzdb object, but that seems fine because we only ever expect that
>> expiry date to move forwards in time, not to move forwards and backwards
>> as new tzdb objects are loaded by chrono::reload_tzdb(). Even if a new
>> list of leap seconds is loaded, we still expect an expiry date that was
>> loaded previously to be valid.
>>
> I was wondering about it in connection to the test that overrides the zone
> directory,
> and may move the date around. But for those already impacted by the fact
> that
> any date with an expiry date before one that we hardcode is ignored.
> However,
> for that reason, I would ensure we do not move the date
> backward; suggestion below.
> (In also could move backward if reading #experies fails).
>
>
>>
>> libstdc++-v3/ChangeLog:
>>
>>         PR libstdc++/123165
>>         * acinclude.m4 (libtool_VERSION): Bump version.
>>         * config/abi/pre/gnu.ver (GLIBCXX_3.4.36): Add new symbol
>>         version and export new symbol.
>>         * configure: Regenerate.
>>         * include/std/chrono (__detail::__leap_seconds_expiry):
>>         Declare new function.
>>         (__detail::__get_leap_second_info): Use new function.
>>         * src/c++20/tzdb.cc (__detail::__leap_seconds_expiry): Define.
>>         (tzdb_list::_Node::_S_read_leap_seconds): Read 'expires' line
>>         from leapseconds file and optionally update global cache.
>>         * testsuite/std/time/tzdb/leap_seconds.cc: Add expires line to
>>         replacement leapseconds file.
>>         * testsuite/util/testsuite_abi.cc: Update known_versions and
>>         latestp.
>> ---
>>
>> Tested x86_64-linux.
>
>
>>  libstdc++-v3/acinclude.m4                     |  2 +-
>>  libstdc++-v3/config/abi/pre/gnu.ver           |  7 ++
>>  libstdc++-v3/configure                        |  2 +-
>>  libstdc++-v3/include/std/chrono               |  9 +-
>>  libstdc++-v3/src/c++20/tzdb.cc                | 88 ++++++++++++++++---
>>  .../testsuite/std/time/tzdb/leap_seconds.cc   |  1 +
>>  libstdc++-v3/testsuite/util/testsuite_abi.cc  |  3 +-
>>  7 files changed, 96 insertions(+), 16 deletions(-)
>>
>> diff --git a/libstdc++-v3/acinclude.m4 b/libstdc++-v3/acinclude.m4
>> index 8dc9e17b214c..3a4b11a98a28 100644
>> --- a/libstdc++-v3/acinclude.m4
>> +++ b/libstdc++-v3/acinclude.m4
>> @@ -4085,7 +4085,7 @@ changequote([,])dnl
>>  fi
>>
>>  # For libtool versioning info, format is CURRENT:REVISION:AGE
>> -libtool_VERSION=6:35:0
>> +libtool_VERSION=6:36:0
>>
>>  # Everything parsed; figure out what files and settings to use.
>>  case $enable_symvers in
>> diff --git a/libstdc++-v3/config/abi/pre/gnu.ver
>> b/libstdc++-v3/config/abi/pre/gnu.ver
>> index bd4da6418295..35aaf89984d1 100644
>> --- a/libstdc++-v3/config/abi/pre/gnu.ver
>> +++ b/libstdc++-v3/config/abi/pre/gnu.ver
>> @@ -2623,6 +2623,13 @@ GLIBCXX_3.4.35 {
>>
>>  } GLIBCXX_3.4.34;
>>
>> +# GCC 17.1.0
>> +GLIBCXX_3.4.36 {
>> +
>> +    _ZNSt6chrono8__detail21__leap_seconds_expiryEv;
>> +
>> +} GLIBCXX_3.4.35;
>> +
>>  # Symbols in the support library (libsupc++) have their own tag.
>>  CXXABI_1.3 {
>>
>> diff --git a/libstdc++-v3/configure b/libstdc++-v3/configure
>> index 6713e4504b1c..013c388b9c2f 100755
>> --- a/libstdc++-v3/configure
>> +++ b/libstdc++-v3/configure
>> @@ -51418,7 +51418,7 @@ $as_echo "$as_me: WARNING: === Symbol versioning
>> will be disabled." >&2;}
>>  fi
>>
>>  # For libtool versioning info, format is CURRENT:REVISION:AGE
>> -libtool_VERSION=6:35:0
>> +libtool_VERSION=6:36:0
>>
>>  # Everything parsed; figure out what files and settings to use.
>>  case $enable_symvers in
>> diff --git a/libstdc++-v3/include/std/chrono
>> b/libstdc++-v3/include/std/chrono
>> index 674f867dcdc7..228293f12bef 100644
>> --- a/libstdc++-v3/include/std/chrono
>> +++ b/libstdc++-v3/include/std/chrono
>> @@ -3217,6 +3217,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>
>>  namespace __detail
>>  {
>> +    // The list below is known to be valid until (at least) this date.
>> +    // This value is defined in the library (possibly to a newer value
>> than
>> +    // the hardcoded value below) and can change at runtime.
>> +    sys_seconds __leap_seconds_expiry();
>> +
>>      inline leap_second_info
>>      __get_leap_second_info(sys_seconds __ss, bool __is_utc)
>>      {
>> @@ -3252,12 +3257,12 @@ namespace __detail
>>         1435708800, // 1 Jul 2015
>>         1483228800, // 1 Jan 2017
>>        };
>> +#if _GLIBCXX_USE_CXX11_ABI || ! _GLIBCXX_USE_DUAL_ABI // use chrono::tzdb
>>        // The list above is known to be valid until (at least) this date
>>        // and only contains positive leap seconds.
>>        constexpr sys_seconds __expires(1798416000s); // 2026-12-28
>> 00:00:00 U
>
>
>
>> -#if _GLIBCXX_USE_CXX11_ABI || ! _GLIBCXX_USE_DUAL_ABI
>> -      if (__ss > __expires)
>> +      if (__ss > __expires && __ss > __leap_seconds_expiry())
>>         {
>>           // Use updated leap_seconds from tzdb.
>>           size_t __n = std::size(__leaps);
>> diff --git a/libstdc++-v3/src/c++20/tzdb.cc
>> b/libstdc++-v3/src/c++20/tzdb.cc
>> index b0fbfc46a6d3..ba0020814ba8 100644
>> --- a/libstdc++-v3/src/c++20/tzdb.cc
>> +++ b/libstdc++-v3/src/c++20/tzdb.cc
>> @@ -1251,6 +1251,40 @@ namespace std::chrono
>>    }
>>  #endif // TZDB_DISABLED
>>
>> +namespace
>> +{
>> +#if ATOMIC_LONG_LOCK_FREE == 2
>> +  using expiry_type = unsigned long;
>> +#else
>> +  using expiry_type = unsigned;
>> +#endif
>> +  // When GCC 16.1 was released with stable C++20 chrono support (in
>> 2026),
>> +  // the last leap second in the list was the one in 2017. If another
>> leap
>> +  // second is introduced in future, objects compiled by GCC 16.1 will
>> not
>> +  // contain that leap second in the hardcoded list in <chrono>.
>> +  // This expiry time must be less than that first post-2017 leap second,
>> +  // so that old copies of __get_leap_second_info will use
>> tzdb::leap_seconds
>> +  // which will contain the post-2017 leap seconds.
>> +  // If no new leap second is introduced, then this expiry time can just
>> be
>> +  // updated to the 'expires' value read from the leapseconds file.
>> +  // tzdata 2026a leapseconds list expires at 2026-12-28 00:00:00 UTC
>> +  constinit std::atomic<expiry_type> leap_seconds_expiry{1798416000u};
>>
> Could you extract the __expires
> from tzdb_list::_Node::_S_read_leap_seconds()
>
We may move leaps, so they remain close together.

> (in this file)  as the global (but TU-local) variables, and use the
> __expires as initializer here,
> so we do not need to update two prices. This will not add any symbols to
> the library, as we are in source file.
> (This is why I am not suggesting doing that in header, as we will need to
> make the data inline).
>
>
>
>> +}
>> +
>> +  namespace __detail
>> +  {
>> +    // Called by chrono::__detail::__get_leap_second_info in <chrono>.
>> +    // The value returned by this function determines whether the
>> hardcoded
>> +    // list in __get_leap_second_info is used, or if the
>> tzdb::leap_seconds
>> +    // vector is used, which might require parsing and constructing a
>> tzdb.
>> +    sys_seconds
>> +    __leap_seconds_expiry()
>> +    {
>> +      auto val = leap_seconds_expiry.load(memory_order::relaxed);
>> +      return sys_seconds(seconds(val));
>> +    }
>> +  }
>> +
>>    // Return leap_second values, and a bool indicating whether the values
>> are
>>    // current (true), or potentially out of date (false).
>>    pair<vector<leap_second>, bool>
>> @@ -1289,13 +1323,8 @@ namespace std::chrono
>>        (leap_second)1483228800, // 1 Jan 2017
>>      };
>>
>> -#if 0
>> -    // This optimization isn't valid if the file has additional leap
>> seconds
>> -    // defined since the library was compiled, but the system clock has
>> been
>> -    // set to a time before the hardcoded expiration date.
>> -    if (system_clock::now() < expires)
>> -      return {std::move(leaps), true};
>> -#endif
>> +    sys_seconds new_expires = expires;
>> +    bool read_new_leaps = true;
>>
>>  #ifndef TZDB_DISABLED
>>      if (ifstream ls{zoneinfo_file(leaps_file)})
>> @@ -1308,7 +1337,16 @@ namespace std::chrono
>>             // Leap  YEAR  MONTH  DAY  HH:MM:SS  CORR  R/S
>>
>>             if (!s.starts_with("Leap"))
>> -             continue;
>> +             {
>> +               if (s.starts_with("#expires "))
>> +                 __try {
>> +                   auto e =
>> sys_seconds(seconds(std::stoll(s.substr(9))));
>> +                   if (e > new_expires)
>> +                     new_expires = e;
>> +                 } __catch (const std::exception&) { /* ignore */ }
>>
> Note that if reading the expires line fails, new_expires will be moved
> to build default (__expires), i.e. backward from already been successfully
> loaded
> tzdb.
>
>> +               continue;
>> +             }
>> +
>>             istringstream li(std::move(s));
>>             li.exceptions(ios::failbit);
>>             li.ignore(4);
>> @@ -1339,12 +1377,40 @@ namespace std::chrono
>>                       leaps.push_back(ls);
>>                   }
>>               }
>> -           s = std::move(li).str(); // return storage to s
>> +           s = std::move(li).str(); // give allocated storage back to s
>>           }
>> -       return {std::move(leaps), true};
>> +
>> +       read_new_leaps = true;
>>        }
>>  #endif
>> -    return {std::move(leaps), false};
>> +
>> +    if (leaps.size() > 27)
>>
> And replaces this wit std::size(__leaps), they are defined in this
> function.
> No need for magic number.
>>
>> +      {
>> +       // One or more new leap seconds have been introduced since 2026.
>> +       // See comment on __detail::__leap_seconds_expiry() above.
>> +       // Object files compiled by older versions of GCC may not have
>> +       // any leap seconds after 2017 in the hardcoded list in <chrono>,
>> +       // so the expiry time that the header code uses must be before the
>> +       // new leap seconds.
>> +       new_expires = leaps[27].date() - 1s;
>> +      }
>> +
>> +    // Should we update the global expiry time?
>> +    const sys_seconds old_expires = __detail::__leap_seconds_expiry();
>> +
>> +    if (new_expires != old_expires)
>>
> This should check if new_expires > old_expires to avoid moving backward.
> case of tests or failures to load.
>
>> +      {
>> +       expiry_type old_exp = old_expires.time_since_epoch().count();
>> +       expiry_type new_exp = new_expires.time_since_epoch().count();
>> +
>> +       // We don't care about this compare-exchange failing. If another
>> +       // thread updated the expiry time, just use that value instead.
>> +       leap_seconds_expiry.compare_exchange_strong(old_exp, new_exp,
>> +                                                   memory_order::release,
>>
>  Why do you use release here? There is no memory writes (except atomic)
> that we want to see in other threads.  After loading the value we may
> continue
> to use static data.
>
>> +
>>  memory_order::relaxed);
>> +      }
>> +
>> +    return {std::move(leaps), read_new_leaps};
>>    }
>>
>>  #ifndef TZDB_DISABLED
>> diff --git a/libstdc++-v3/testsuite/std/time/tzdb/leap_seconds.cc
>> b/libstdc++-v3/testsuite/std/time/tzdb/leap_seconds.cc
>> index 5999635a89f0..24ef7d44858d 100644
>> --- a/libstdc++-v3/testsuite/std/time/tzdb/leap_seconds.cc
>> +++ b/libstdc++-v3/testsuite/std/time/tzdb/leap_seconds.cc
>> @@ -52,6 +52,7 @@ Leap  2016    Dec     31      23:59:60        +       S
>>  # These are fake leap seconds for testing purposes:
>>  Leap   2093    Jun     30      23:59:59        -       S
>>  Leap   2093    Dec     31      23:59:60        +       S
>> +#expires 3991680000 (2096-06-28 00:00:00 UTC)
>>  )";
>>
>>    const auto& db = std::chrono::get_tzdb();
>> diff --git a/libstdc++-v3/testsuite/util/testsuite_abi.cc
>> b/libstdc++-v3/testsuite/util/testsuite_abi.cc
>> index 8fb38355cadd..4e80c5f184a6 100644
>> --- a/libstdc++-v3/testsuite/util/testsuite_abi.cc
>> +++ b/libstdc++-v3/testsuite/util/testsuite_abi.cc
>> @@ -217,6 +217,7 @@ check_version(symbol& test, bool added)
>>        known_versions.push_back("GLIBCXX_3.4.33");
>>        known_versions.push_back("GLIBCXX_3.4.34");
>>        known_versions.push_back("GLIBCXX_3.4.35");
>> +      known_versions.push_back("GLIBCXX_3.4.36");
>>        known_versions.push_back("GLIBCXX_LDBL_3.4.31");
>>        known_versions.push_back("GLIBCXX_IEEE128_3.4.29");
>>        known_versions.push_back("GLIBCXX_IEEE128_3.4.30");
>> @@ -260,7 +261,7 @@ check_version(symbol& test, bool added)
>>         test.version_status = symbol::incompatible;
>>
>>        // Check that added symbols are added in the latest pre-release
>> version.
>> -      bool latestp = (test.version_name == "GLIBCXX_3.4.35"
>> +      bool latestp = (test.version_name == "GLIBCXX_3.4.36"
>>                      || test.version_name == "CXXABI_1.3.17"
>>                      || test.version_name == "CXXABI_FLOAT128"
>>                      || test.version_name == "CXXABI_TM_1");
>> --
>> 2.54.0
>>
>>

Reply via email to