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