libstdc++: Cache leap second expiry time in <chrono> [PR123165]

Message ID 20260506141730.403268-1-jwakely@redhat.com
State New
Headers
Series libstdc++: Cache leap second expiry time in <chrono> [PR123165] |

Commit Message

Jonathan Wakely May 6, 2026, 2:16 p.m. UTC
  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.

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(-)
  

Comments

Tomasz Kaminski May 6, 2026, 2:46 p.m. UTC | #1
On Wed, May 6, 2026 at 4:18 PM Jonathan Wakely <jwakely@redhat.com> 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()
(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
>
>
  
Tomasz Kaminski May 6, 2026, 2:51 p.m. UTC | #2
On Wed, May 6, 2026 at 4:46 PM Tomasz Kaminski <tkaminsk@redhat.com> wrote:

>
>
> On Wed, May 6, 2026 at 4:18 PM Jonathan Wakely <jwakely@redhat.com> 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
>>
>>
  
Jonathan Wakely May 6, 2026, 3:14 p.m. UTC | #3
On Wed, 6 May 2026 at 15:47, Tomasz Kaminski <tkaminsk@redhat.com> wrote:
>
>
>
> On Wed, May 6, 2026 at 4:18 PM Jonathan Wakely <jwakely@redhat.com> 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()
> (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.

No, the magic number is correct. If new leap seconds are added they
will get added to the initializer-list for 'leaps' here in tzdb.cc,
but they won't be in the hardcoded list baked into existing object
files compiled by GCC 16. What matters is not whether we read any from
the file, but whether the list contains more than the 27 original leap
seconds known by <chrono> in GCC 16.

(I'll read the rest of the feedback carefully later)


>>
>> +      {
>> +       // 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
>>
  
Tomasz Kaminski May 6, 2026, 4:05 p.m. UTC | #4
On Wed, May 6, 2026 at 5:14 PM Jonathan Wakely <jwakely@redhat.com> wrote:

> On Wed, 6 May 2026 at 15:47, Tomasz Kaminski <tkaminsk@redhat.com> wrote:
> >
> >
> >
> > On Wed, May 6, 2026 at 4:18 PM Jonathan Wakely <jwakely@redhat.com>
> 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{leap27th_expiry};
> >
> > Could you extract the __expires  from
> tzdb_list::_Node::_S_read_leap_seconds()
> > (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).
>
OK, as I understand the code, this value can be updated to one second
prior to the date of the 28th leap second, or current time, if none was
inserted.
However, the commit message does not contain the number of leap seconds
know for GCC17,

Maybe change comment to something like this:
// When GCC 16.1 was released with stable C++20 chrono support (in 2026),
// the leap second data contained 27 entries, with the last one in 2017.
constexpr min_leap_second_count = 27; /// review: use that as magic number,
so searching for it will find this comment

// 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 28th (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.
// Since no new leap second was introduced, 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};



> >
> >
> >>
> >> +}
> >> +
> >> +  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.
>
> No, the magic number is correct. If new leap seconds are added they
> will get added to the initializer-list for 'leaps' here in tzdb.cc,
> but they won't be in the hardcoded list baked into existing object
> files compiled by GCC 16. What matters is not whether we read any from
> the file, but whether the list contains more than the 27 original leap
> seconds known by <chrono> in GCC 16.
>
> (I'll read the rest of the feedback carefully later)
>
>
> >>
> >> +      {
> >> +       // 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
> >>
>
>
  
Jonathan Wakely May 6, 2026, 4:09 p.m. UTC | #5
On Wed, 6 May 2026 at 17:05, Tomasz Kaminski <tkaminsk@redhat.com> wrote:
>
>
>
> On Wed, May 6, 2026 at 5:14 PM Jonathan Wakely <jwakely@redhat.com> wrote:
>>
>> On Wed, 6 May 2026 at 15:47, Tomasz Kaminski <tkaminsk@redhat.com> wrote:
>> >
>> >
>> >
>> > On Wed, May 6, 2026 at 4:18 PM Jonathan Wakely <jwakely@redhat.com> 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{leap27th_expiry};
>> >
>> > Could you extract the __expires  from tzdb_list::_Node::_S_read_leap_seconds()
>> > (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).
>
> OK, as I understand the code, this value can be updated to one second
> prior to the date of the 28th leap second, or current time, if none was inserted.
> However, the commit message does not contain the number of leap seconds know for GCC17,
>
> Maybe change comment to something like this:
> // When GCC 16.1 was released with stable C++20 chrono support (in 2026),
> // the leap second data contained 27 entries, with the last one in 2017.
> constexpr min_leap_second_count = 27; /// review: use that as magic number, so searching for it will find this comment

Ack.
  
Jonathan Wakely May 6, 2026, 5:28 p.m. UTC | #6
On Wed, 6 May 2026 at 15:47, Tomasz Kaminski <tkaminsk@redhat.com> wrote:
>
>
>
> On Wed, May 6, 2026 at 4:18 PM Jonathan Wakely <jwakely@redhat.com> 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).

There are three problem cases I was worried about.

The happy path is where we read a good expiry time from the
leapseconds file, so we want to store it in the global variable.
That's the easy case.

If the file doesn't contain any #expires line so we should not update
the global.

The second problem case is where it contains an #expires line, but we
fail to parse the value (maybe the format changed in a future version
of the file?). This should not happen, but is possible for a custom
leapseconds file provided by the user or their sysadmin, and we should
not update the global in this case.

The third problem is where a bad leapseconds file is installed and it
has a bad #expires line, with a date too far in the future (maybe
somebody makes a typo in a custom file and it has too many digits). In
this case, the user/sysadmin might install a fixed leapseconds file,
then trigger the application to call reload_tzdb() to read the fixed
file. In this case, we would want to update the global even if it
means moving backwards in time.

In all cases, if a 28th leap second has been defined, that becomes the
new expiry.

>
>>
>>
>> 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()
> (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.

Yes, that's wrong, thanks.

>>
>> +               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.

We might also *not* use the static data, and use
get_tzdb_list()->begin()->leapseconds. But that already imposes a
memory barrier, and there are no loads of the std::atomic that use
acquire ordering, to synchronize with this release. So I agree it can
be relaxed here.



>>
>> +                                                   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
>>
  
Tomasz Kaminski May 7, 2026, 5:58 a.m. UTC | #7
On Wed, May 6, 2026 at 7:28 PM Jonathan Wakely <jwakely@redhat.com> wrote:

> On Wed, 6 May 2026 at 15:47, Tomasz Kaminski <tkaminsk@redhat.com> wrote:
> >
> >
> >
> > On Wed, May 6, 2026 at 4:18 PM Jonathan Wakely <jwakely@redhat.com>
> 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).
>
I think I would like to propose a bit of different model. As different GCC
versions will
hardcode different number of leap seconds in the header, we should provide
function looking like
that:
   // Returns expiry time for nth leap second.
   __expiry_of_leap_second(size_t nth);
This function should return 1s prior the expiry of nth leap second, if that
is know to the
tzdb (as you do), but if current time zone database didn't load leap second
data with it,
we could return infinity (expiry_type max). This is totally fine, as
reload_tzdb call that
will load nth leap second will cause a new value to be returned (this is
checked on each all).
Then in the header, we do something like: f (__ss > __expires && __ss
> __expiry_of_leap_second(std::size(leaps)))

Now in the source file, we will have:
const sys_seconds static_leaps[] = ....; // leaps second know when compiled
const atomic<expiry_time> next_leap(expiry_max); // There is no next leap
second data at this point

Implementation of __expiry_of_leap_second will be something like:
   if (nth < sizeof(static_leaps))
     return __leaps[_nth]; // this leap second data was compiled into binary
  else if (nth == sizeof(static_leaps))
     return __next_leap.load(relaxed); // time of next leap second not know
by binary
  else
     // This is caused by linking code compiled with new headers against
     // older version of leapstdc++, we know that nth leap will be after
__next_leap,
     // so return currently know value of __next_leap.
     return __next_leap.load(relaxed);

The loading leaps seconds file does not need to check expiry, just do
something like:
    if (leaps.size() > static_leaps.size()) // we loaded the next leap
second
      expiry_time = leaps[static_leaps.size()].time_since_epoch().count() -
1;
   else
      expiry_time = expiry_max; // There is no new leap second data.

And simply update expiry_time to this value.



> There are three problem cases I was worried about.
>
> The happy path is where we read a good expiry time from the
> leapseconds file, so we want to store it in the global variable.
> That's the easy case.
>
> If the file doesn't contain any #expires line so we should not update
> the global.
>
Counterintuitively, I do not think the #experies line is important when
loading dynamically updates the time, so my suggestion solves that.


>
> The second problem case is where it contains an #expires line, but we
> fail to parse the value (maybe the format changed in a future version
> of the file?). This should not happen, but is possible for a custom
> leapseconds file provided by the user or their sysadmin, and we should
> not update the global in this case.
>
We do not look at #expires so this is also solved.

>
> The third problem is where a bad leapseconds file is installed and it
> has a bad #expires line, with a date too far in the future (maybe
> somebody makes a typo in a custom file and it has too many digits). In
> this case, the user/sysadmin might install a fixed leapseconds file,
> then trigger the application to call reload_tzdb() to read the fixed
> file. In this case, we would want to update the global even if it
> means moving backwards in time.
>
This would be equivalent to someone accidentally putting a wrong file
with 28th leap second put very close to the date (between header expiry
and now), that would cause wrong converison, as next_leap will be udpate.
But with my suggestion for reload, putting a correct version will always
fix that,
as we override next_leap:
  * if 28th leap was removed, we will update timout to maximium, until it
will show up
  * if value was changed, we will also update it to it

And in my approach the expiry time always corresponds to tzdb latest.

>
> In all cases, if a 28th leap second has been defined, that becomes the
> new expiry.
>
> >
> >>
> >>
> >> 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()
> > (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.
>
> Yes, that's wrong, thanks.
>
> >>
> >> +               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.
>
> We might also *not* use the static data, and use
> get_tzdb_list()->begin()->leapseconds. But that already imposes a
> memory barrier, and there are no loads of the std::atomic that use
> acquire ordering, to synchronize with this release. So I agree it can
> be relaxed here.
>
>
>
> >>
> >> +
>  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
> >>
>
>
  
Tomasz Kaminski May 7, 2026, 6:15 a.m. UTC | #8
On Thu, May 7, 2026 at 7:58 AM Tomasz Kaminski <tkaminsk@redhat.com> wrote:

>
>
> On Wed, May 6, 2026 at 7:28 PM Jonathan Wakely <jwakely@redhat.com> wrote:
>
>> On Wed, 6 May 2026 at 15:47, Tomasz Kaminski <tkaminsk@redhat.com> wrote:
>> >
>> >
>> >
>> > On Wed, May 6, 2026 at 4:18 PM Jonathan Wakely <jwakely@redhat.com>
>> 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).
>>
> I think I would like to propose a bit of different model. As different GCC
> versions will
> hardcode different number of leap seconds in the header, we should provide
> function looking like
> that:
>    // Returns expiry time for nth leap second.
>    __expiry_of_leap_second(size_t nth);
> This function should return 1s prior the expiry of nth leap second, if
> that is know to the
> tzdb (as you do), but if current time zone database didn't load leap
> second data with it,
> we could return infinity (expiry_type max). This is totally fine, as
> reload_tzdb call that
> will load nth leap second will cause a new value to be returned (this is
> checked on each all).
> Then in the header, we do something like: f (__ss > __expires && __ss
> > __expiry_of_leap_second(std::size(leaps)))
>
> Now in the source file, we will have:
> const sys_seconds static_leaps[] = ....; // leaps second know when compiled
> const atomic<expiry_time> next_leap(expiry_max); // There is no next leap
> second data at this point
>
> Implementation of __expiry_of_leap_second will be something like:
>    if (nth < sizeof(static_leaps))
>      return __leaps[_nth]; // this leap second data was compiled into
> binary
>   else if (nth == sizeof(static_leaps))
>      return __next_leap.load(relaxed); // time of next leap second not
> know by binary
>   else
>
     // This is caused by linking code compiled with new headers against
>      // older version of leapstdc++, we know that nth leap will be after
> __next_leap,
>      // so return currently know value of __next_leap.
>      return __next_leap.load(relaxed);
>
The above could be simply 0, as we know the expiry time in that header is
later
date that __next_heap (because it included that leap second). Returning
__next_leap
however guaratees tht if we didn't load the new data from disk (and it is
infinite),
we will use header information that have more data that library file.


>
> The loading leaps seconds file does not need to check expiry, just do
> something like:
>     if (leaps.size() > static_leaps.size()) // we loaded the next leap
> second
>       expiry_time = leaps[static_leaps.size()].time_since_epoch().count()
> - 1;
>    else
>       expiry_time = expiry_max; // There is no new leap second data.
>
> And simply update expiry_time to this value.
>
>
>
>> There are three problem cases I was worried about.
>>
>> The happy path is where we read a good expiry time from the
>> leapseconds file, so we want to store it in the global variable.
>> That's the easy case.
>>
>> If the file doesn't contain any #expires line so we should not update
>> the global.
>>
> Counterintuitively, I do not think the #experies line is important when
> loading dynamically updates the time, so my suggestion solves that.
>
>
>>
>> The second problem case is where it contains an #expires line, but we
>> fail to parse the value (maybe the format changed in a future version
>> of the file?). This should not happen, but is possible for a custom
>> leapseconds file provided by the user or their sysadmin, and we should
>> not update the global in this case.
>>
> We do not look at #expires so this is also solved.
>
>>
>> The third problem is where a bad leapseconds file is installed and it
>> has a bad #expires line, with a date too far in the future (maybe
>> somebody makes a typo in a custom file and it has too many digits). In
>> this case, the user/sysadmin might install a fixed leapseconds file,
>> then trigger the application to call reload_tzdb() to read the fixed
>> file. In this case, we would want to update the global even if it
>> means moving backwards in time.
>>
> This would be equivalent to someone accidentally putting a wrong file
> with 28th leap second put very close to the date (between header expiry
> and now), that would cause wrong converison, as next_leap will be udpate.
> But with my suggestion for reload, putting a correct version will always
> fix that,
> as we override next_leap:
>   * if 28th leap was removed, we will update timout to maximium, until it
> will show up
>   * if value was changed, we will also update it to it
>
> And in my approach the expiry time always corresponds to tzdb latest.
>
>>
>> In all cases, if a 28th leap second has been defined, that becomes the
>> new expiry.
>>
>> >
>> >>
>> >>
>> >> 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()
>> > (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.
>>
>> Yes, that's wrong, thanks.
>>
>> >>
>> >> +               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.
>>
>> We might also *not* use the static data, and use
>> get_tzdb_list()->begin()->leapseconds. But that already imposes a
>> memory barrier, and there are no loads of the std::atomic that use
>> acquire ordering, to synchronize with this release. So I agree it can
>> be relaxed here.
>>
>>
>>
>> >>
>> >> +
>>  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
>> >>
>>
>>
  
Jonathan Wakely May 7, 2026, 7:58 a.m. UTC | #9
I think we might as well just move the whole function into the library, and
only have one place that keeps the list of leap seconds and the expiry.

That way we don't need to care about what's in the header.

On Thu, 7 May 2026, 07:15 Tomasz Kaminski, <tkaminsk@redhat.com> wrote:

>
>
> On Thu, May 7, 2026 at 7:58 AM Tomasz Kaminski <tkaminsk@redhat.com>
> wrote:
>
>>
>>
>> On Wed, May 6, 2026 at 7:28 PM Jonathan Wakely <jwakely@redhat.com>
>> wrote:
>>
>>> On Wed, 6 May 2026 at 15:47, Tomasz Kaminski <tkaminsk@redhat.com>
>>> wrote:
>>> >
>>> >
>>> >
>>> > On Wed, May 6, 2026 at 4:18 PM Jonathan Wakely <jwakely@redhat.com>
>>> 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).
>>>
>> I think I would like to propose a bit of different model. As different
>> GCC versions will
>> hardcode different number of leap seconds in the header, we should
>> provide function looking like
>> that:
>>    // Returns expiry time for nth leap second.
>>    __expiry_of_leap_second(size_t nth);
>> This function should return 1s prior the expiry of nth leap second, if
>> that is know to the
>> tzdb (as you do), but if current time zone database didn't load leap
>> second data with it,
>> we could return infinity (expiry_type max). This is totally fine, as
>> reload_tzdb call that
>> will load nth leap second will cause a new value to be returned (this is
>> checked on each all).
>> Then in the header, we do something like: f (__ss > __expires && __ss
>> > __expiry_of_leap_second(std::size(leaps)))
>>
>> Now in the source file, we will have:
>> const sys_seconds static_leaps[] = ....; // leaps second know when
>> compiled
>> const atomic<expiry_time> next_leap(expiry_max); // There is no next leap
>> second data at this point
>>
>> Implementation of __expiry_of_leap_second will be something like:
>>    if (nth < sizeof(static_leaps))
>>      return __leaps[_nth]; // this leap second data was compiled into
>> binary
>>   else if (nth == sizeof(static_leaps))
>>      return __next_leap.load(relaxed); // time of next leap second not
>> know by binary
>>   else
>>
>      // This is caused by linking code compiled with new headers against
>>      // older version of leapstdc++, we know that nth leap will be after
>> __next_leap,
>>      // so return currently know value of __next_leap.
>>      return __next_leap.load(relaxed);
>>
> The above could be simply 0, as we know the expiry time in that header is
> later
> date that __next_heap (because it included that leap second). Returning
> __next_leap
> however guaratees tht if we didn't load the new data from disk (and it is
> infinite),
> we will use header information that have more data that library file.
>
>
>>
>> The loading leaps seconds file does not need to check expiry, just do
>> something like:
>>     if (leaps.size() > static_leaps.size()) // we loaded the next leap
>> second
>>       expiry_time = leaps[static_leaps.size()].time_since_epoch().count()
>> - 1;
>>    else
>>       expiry_time = expiry_max; // There is no new leap second data.
>>
>> And simply update expiry_time to this value.
>>
>>
>>
>>> There are three problem cases I was worried about.
>>>
>>> The happy path is where we read a good expiry time from the
>>> leapseconds file, so we want to store it in the global variable.
>>> That's the easy case.
>>>
>>> If the file doesn't contain any #expires line so we should not update
>>> the global.
>>>
>> Counterintuitively, I do not think the #experies line is important when
>> loading dynamically updates the time, so my suggestion solves that.
>>
>>
>>>
>>> The second problem case is where it contains an #expires line, but we
>>> fail to parse the value (maybe the format changed in a future version
>>> of the file?). This should not happen, but is possible for a custom
>>> leapseconds file provided by the user or their sysadmin, and we should
>>> not update the global in this case.
>>>
>> We do not look at #expires so this is also solved.
>>
>>>
>>> The third problem is where a bad leapseconds file is installed and it
>>> has a bad #expires line, with a date too far in the future (maybe
>>> somebody makes a typo in a custom file and it has too many digits). In
>>> this case, the user/sysadmin might install a fixed leapseconds file,
>>> then trigger the application to call reload_tzdb() to read the fixed
>>> file. In this case, we would want to update the global even if it
>>> means moving backwards in time.
>>>
>> This would be equivalent to someone accidentally putting a wrong file
>> with 28th leap second put very close to the date (between header expiry
>> and now), that would cause wrong converison, as next_leap will be udpate.
>> But with my suggestion for reload, putting a correct version will always
>> fix that,
>> as we override next_leap:
>>   * if 28th leap was removed, we will update timout to maximium, until it
>> will show up
>>   * if value was changed, we will also update it to it
>>
>> And in my approach the expiry time always corresponds to tzdb latest.
>>
>>>
>>> In all cases, if a 28th leap second has been defined, that becomes the
>>> new expiry.
>>>
>>> >
>>> >>
>>> >>
>>> >> 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()
>>> > (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.
>>>
>>> Yes, that's wrong, thanks.
>>>
>>> >>
>>> >> +               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.
>>>
>>> We might also *not* use the static data, and use
>>> get_tzdb_list()->begin()->leapseconds. But that already imposes a
>>> memory barrier, and there are no loads of the std::atomic that use
>>> acquire ordering, to synchronize with this release. So I agree it can
>>> be relaxed here.
>>>
>>>
>>>
>>> >>
>>> >> +
>>>  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
>>> >>
>>>
>>>
  
Tomasz Kaminski May 7, 2026, 8:02 a.m. UTC | #10
On Thu, May 7, 2026 at 9:58 AM Jonathan Wakely <jwakely.gcc@gmail.com>
wrote:

> I think we might as well just move the whole function into the library,
> and only have one place that keeps the list of leap seconds and the expiry.
>
The separation requires us to pass one parameter: the number of leap
seconds.
so I don't think it adds that much complexity.

>
> That way we don't need to care about what's in the header.
>
We also avoid the risk that someone will slap constexpr of this function in
header
file, do one test and propose to make it so.

>
>

>
> On Thu, 7 May 2026, 07:15 Tomasz Kaminski, <tkaminsk@redhat.com> wrote:
>
>>
>>
>> On Thu, May 7, 2026 at 7:58 AM Tomasz Kaminski <tkaminsk@redhat.com>
>> wrote:
>>
>>>
>>>
>>> On Wed, May 6, 2026 at 7:28 PM Jonathan Wakely <jwakely@redhat.com>
>>> wrote:
>>>
>>>> On Wed, 6 May 2026 at 15:47, Tomasz Kaminski <tkaminsk@redhat.com>
>>>> wrote:
>>>> >
>>>> >
>>>> >
>>>> > On Wed, May 6, 2026 at 4:18 PM Jonathan Wakely <jwakely@redhat.com>
>>>> 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).
>>>>
>>> I think I would like to propose a bit of different model. As different
>>> GCC versions will
>>> hardcode different number of leap seconds in the header, we should
>>> provide function looking like
>>> that:
>>>    // Returns expiry time for nth leap second.
>>>    __expiry_of_leap_second(size_t nth);
>>> This function should return 1s prior the expiry of nth leap second, if
>>> that is know to the
>>> tzdb (as you do), but if current time zone database didn't load leap
>>> second data with it,
>>> we could return infinity (expiry_type max). This is totally fine, as
>>> reload_tzdb call that
>>> will load nth leap second will cause a new value to be returned (this is
>>> checked on each all).
>>> Then in the header, we do something like: f (__ss > __expires && __ss
>>> > __expiry_of_leap_second(std::size(leaps)))
>>>
>>> Now in the source file, we will have:
>>> const sys_seconds static_leaps[] = ....; // leaps second know when
>>> compiled
>>> const atomic<expiry_time> next_leap(expiry_max); // There is no next
>>> leap second data at this point
>>>
>>> Implementation of __expiry_of_leap_second will be something like:
>>>    if (nth < sizeof(static_leaps))
>>>      return __leaps[_nth]; // this leap second data was compiled into
>>> binary
>>>   else if (nth == sizeof(static_leaps))
>>>      return __next_leap.load(relaxed); // time of next leap second not
>>> know by binary
>>>   else
>>>
>>      // This is caused by linking code compiled with new headers against
>>>      // older version of leapstdc++, we know that nth leap will be after
>>> __next_leap,
>>>      // so return currently know value of __next_leap.
>>>      return __next_leap.load(relaxed);
>>>
>> The above could be simply 0, as we know the expiry time in that header is
>> later
>> date that __next_heap (because it included that leap second). Returning
>> __next_leap
>> however guaratees tht if we didn't load the new data from disk (and it is
>> infinite),
>> we will use header information that have more data that library file.
>>
>>
>>>
>>> The loading leaps seconds file does not need to check expiry, just do
>>> something like:
>>>     if (leaps.size() > static_leaps.size()) // we loaded the next leap
>>> second
>>>       expiry_time =
>>> leaps[static_leaps.size()].time_since_epoch().count() - 1;
>>>    else
>>>       expiry_time = expiry_max; // There is no new leap second data.
>>>
>>> And simply update expiry_time to this value.
>>>
>>>
>>>
>>>> There are three problem cases I was worried about.
>>>>
>>>> The happy path is where we read a good expiry time from the
>>>> leapseconds file, so we want to store it in the global variable.
>>>> That's the easy case.
>>>>
>>>> If the file doesn't contain any #expires line so we should not update
>>>> the global.
>>>>
>>> Counterintuitively, I do not think the #experies line is important when
>>> loading dynamically updates the time, so my suggestion solves that.
>>>
>>>
>>>>
>>>> The second problem case is where it contains an #expires line, but we
>>>> fail to parse the value (maybe the format changed in a future version
>>>> of the file?). This should not happen, but is possible for a custom
>>>> leapseconds file provided by the user or their sysadmin, and we should
>>>> not update the global in this case.
>>>>
>>> We do not look at #expires so this is also solved.
>>>
>>>>
>>>> The third problem is where a bad leapseconds file is installed and it
>>>> has a bad #expires line, with a date too far in the future (maybe
>>>> somebody makes a typo in a custom file and it has too many digits). In
>>>> this case, the user/sysadmin might install a fixed leapseconds file,
>>>> then trigger the application to call reload_tzdb() to read the fixed
>>>> file. In this case, we would want to update the global even if it
>>>> means moving backwards in time.
>>>>
>>> This would be equivalent to someone accidentally putting a wrong file
>>> with 28th leap second put very close to the date (between header expiry
>>> and now), that would cause wrong converison, as next_leap will be udpate.
>>> But with my suggestion for reload, putting a correct version will always
>>> fix that,
>>> as we override next_leap:
>>>   * if 28th leap was removed, we will update timout to maximium, until
>>> it will show up
>>>   * if value was changed, we will also update it to it
>>>
>>> And in my approach the expiry time always corresponds to tzdb latest.
>>>
>>>>
>>>> In all cases, if a 28th leap second has been defined, that becomes the
>>>> new expiry.
>>>>
>>>> >
>>>> >>
>>>> >>
>>>> >> 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()
>>>> > (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.
>>>>
>>>> Yes, that's wrong, thanks.
>>>>
>>>> >>
>>>> >> +               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.
>>>>
>>>> We might also *not* use the static data, and use
>>>> get_tzdb_list()->begin()->leapseconds. But that already imposes a
>>>> memory barrier, and there are no loads of the std::atomic that use
>>>> acquire ordering, to synchronize with this release. So I agree it can
>>>> be relaxed here.
>>>>
>>>>
>>>>
>>>> >>
>>>> >> +
>>>>  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
>>>> >>
>>>>
>>>>
  
Tomasz Kaminski May 7, 2026, 8:08 a.m. UTC | #11
On Thu, May 7, 2026 at 9:58 AM Jonathan Wakely <jwakely.gcc@gmail.com>
wrote:

> I think we might as well just move the whole function into the library,
> and only have one place that keeps the list of leap seconds and the expiry.
>
My suggestion is to simplify your patch (no need to read expires), and just
add one parameter.
So I am not sure we are adding much complexity, compared to before.

>
> That way we don't need to care about what's in the header.
>
This prevents the risk of someone slapping constexpr, doing one test that
is before hardcoded
expiry, and proposing to do so in standard :)


>
> On Thu, 7 May 2026, 07:15 Tomasz Kaminski, <tkaminsk@redhat.com> wrote:
>
>>
>>
>> On Thu, May 7, 2026 at 7:58 AM Tomasz Kaminski <tkaminsk@redhat.com>
>> wrote:
>>
>>>
>>>
>>> On Wed, May 6, 2026 at 7:28 PM Jonathan Wakely <jwakely@redhat.com>
>>> wrote:
>>>
>>>> On Wed, 6 May 2026 at 15:47, Tomasz Kaminski <tkaminsk@redhat.com>
>>>> wrote:
>>>> >
>>>> >
>>>> >
>>>> > On Wed, May 6, 2026 at 4:18 PM Jonathan Wakely <jwakely@redhat.com>
>>>> 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).
>>>>
>>> I think I would like to propose a bit of different model. As different
>>> GCC versions will
>>> hardcode different number of leap seconds in the header, we should
>>> provide function looking like
>>> that:
>>>    // Returns expiry time for nth leap second.
>>>    __expiry_of_leap_second(size_t nth);
>>> This function should return 1s prior the expiry of nth leap second, if
>>> that is know to the
>>> tzdb (as you do), but if current time zone database didn't load leap
>>> second data with it,
>>> we could return infinity (expiry_type max). This is totally fine, as
>>> reload_tzdb call that
>>> will load nth leap second will cause a new value to be returned (this is
>>> checked on each all).
>>> Then in the header, we do something like: f (__ss > __expires && __ss
>>> > __expiry_of_leap_second(std::size(leaps)))
>>>
>>> Now in the source file, we will have:
>>> const sys_seconds static_leaps[] = ....; // leaps second know when
>>> compiled
>>> const atomic<expiry_time> next_leap(expiry_max); // There is no next
>>> leap second data at this point
>>>
>>> Implementation of __expiry_of_leap_second will be something like:
>>>    if (nth < sizeof(static_leaps))
>>>      return __leaps[_nth]; // this leap second data was compiled into
>>> binary
>>>   else if (nth == sizeof(static_leaps))
>>>      return __next_leap.load(relaxed); // time of next leap second not
>>> know by binary
>>>   else
>>>
>>      // This is caused by linking code compiled with new headers against
>>>      // older version of leapstdc++, we know that nth leap will be after
>>> __next_leap,
>>>      // so return currently know value of __next_leap.
>>>      return __next_leap.load(relaxed);
>>>
>> The above could be simply 0, as we know the expiry time in that header is
>> later
>> date that __next_heap (because it included that leap second). Returning
>> __next_leap
>> however guaratees tht if we didn't load the new data from disk (and it is
>> infinite),
>> we will use header information that have more data that library file.
>>
>>
>>>
>>> The loading leaps seconds file does not need to check expiry, just do
>>> something like:
>>>     if (leaps.size() > static_leaps.size()) // we loaded the next leap
>>> second
>>>       expiry_time =
>>> leaps[static_leaps.size()].time_since_epoch().count() - 1;
>>>    else
>>>       expiry_time = expiry_max; // There is no new leap second data.
>>>
>>> And simply update expiry_time to this value.
>>>
>>>
>>>
>>>> There are three problem cases I was worried about.
>>>>
>>>> The happy path is where we read a good expiry time from the
>>>> leapseconds file, so we want to store it in the global variable.
>>>> That's the easy case.
>>>>
>>>> If the file doesn't contain any #expires line so we should not update
>>>> the global.
>>>>
>>> Counterintuitively, I do not think the #experies line is important when
>>> loading dynamically updates the time, so my suggestion solves that.
>>>
>>>
>>>>
>>>> The second problem case is where it contains an #expires line, but we
>>>> fail to parse the value (maybe the format changed in a future version
>>>> of the file?). This should not happen, but is possible for a custom
>>>> leapseconds file provided by the user or their sysadmin, and we should
>>>> not update the global in this case.
>>>>
>>> We do not look at #expires so this is also solved.
>>>
>>>>
>>>> The third problem is where a bad leapseconds file is installed and it
>>>> has a bad #expires line, with a date too far in the future (maybe
>>>> somebody makes a typo in a custom file and it has too many digits). In
>>>> this case, the user/sysadmin might install a fixed leapseconds file,
>>>> then trigger the application to call reload_tzdb() to read the fixed
>>>> file. In this case, we would want to update the global even if it
>>>> means moving backwards in time.
>>>>
>>> This would be equivalent to someone accidentally putting a wrong file
>>> with 28th leap second put very close to the date (between header expiry
>>> and now), that would cause wrong converison, as next_leap will be udpate.
>>> But with my suggestion for reload, putting a correct version will always
>>> fix that,
>>> as we override next_leap:
>>>   * if 28th leap was removed, we will update timout to maximium, until
>>> it will show up
>>>   * if value was changed, we will also update it to it
>>>
>>> And in my approach the expiry time always corresponds to tzdb latest.
>>>
>>>>
>>>> In all cases, if a 28th leap second has been defined, that becomes the
>>>> new expiry.
>>>>
>>>> >
>>>> >>
>>>> >>
>>>> >> 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()
>>>> > (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.
>>>>
>>>> Yes, that's wrong, thanks.
>>>>
>>>> >>
>>>> >> +               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.
>>>>
>>>> We might also *not* use the static data, and use
>>>> get_tzdb_list()->begin()->leapseconds. But that already imposes a
>>>> memory barrier, and there are no loads of the std::atomic that use
>>>> acquire ordering, to synchronize with this release. So I agree it can
>>>> be relaxed here.
>>>>
>>>>
>>>>
>>>> >>
>>>> >> +
>>>>  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
>>>> >>
>>>>
>>>>
  

Patch

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 UTC
 
-#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};
+}
+
+  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 */ }
+		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)
+      {
+	// 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)
+      {
+	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,
+						    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");