On Wed, May 6, 2026 at 4:46 PM Tomasz Kaminski <[email protected]> wrote:
> > > On Wed, May 6, 2026 at 4:18 PM Jonathan Wakely <[email protected]> wrote: > >> This change allows the hardcoded list of leap seconds in <chrono> to be >> used even when the program is executing after the hardcoded expiry date >> in that header. If the OS-provided leapseconds file has a later expiry >> date (or contains new leap seconds added after the one in 2017) then the >> new __detail::__leap_seconds_expiry() function will return that new >> dynamically-obtained expiry date. The __detail::__get_leap_second_info >> function in the header can check that new expiry date instead of relying >> only on the hardcoded one. >> >> This change means that in the worst case we now make two calls into the >> library (one to get the dynamic expiry date and then possibly another >> one to get the actual list of new leap seconds). Previously we just make >> one, to get the list. The change seems worthwhile, because it means that >> in more cases we don't need to increment+decrement the reference count >> on a tzdb object and use its leapseconds vector, we can just use the >> hardcoded array. >> >> The new expiry date is stored in a global variable, rather than being >> per-tzdb object, but that seems fine because we only ever expect that >> expiry date to move forwards in time, not to move forwards and backwards >> as new tzdb objects are loaded by chrono::reload_tzdb(). Even if a new >> list of leap seconds is loaded, we still expect an expiry date that was >> loaded previously to be valid. >> > I was wondering about it in connection to the test that overrides the zone > directory, > and may move the date around. But for those already impacted by the fact > that > any date with an expiry date before one that we hardcode is ignored. > However, > for that reason, I would ensure we do not move the date > backward; suggestion below. > (In also could move backward if reading #experies fails). > > >> >> libstdc++-v3/ChangeLog: >> >> PR libstdc++/123165 >> * acinclude.m4 (libtool_VERSION): Bump version. >> * config/abi/pre/gnu.ver (GLIBCXX_3.4.36): Add new symbol >> version and export new symbol. >> * configure: Regenerate. >> * include/std/chrono (__detail::__leap_seconds_expiry): >> Declare new function. >> (__detail::__get_leap_second_info): Use new function. >> * src/c++20/tzdb.cc (__detail::__leap_seconds_expiry): Define. >> (tzdb_list::_Node::_S_read_leap_seconds): Read 'expires' line >> from leapseconds file and optionally update global cache. >> * testsuite/std/time/tzdb/leap_seconds.cc: Add expires line to >> replacement leapseconds file. >> * testsuite/util/testsuite_abi.cc: Update known_versions and >> latestp. >> --- >> >> Tested x86_64-linux. > > >> libstdc++-v3/acinclude.m4 | 2 +- >> libstdc++-v3/config/abi/pre/gnu.ver | 7 ++ >> libstdc++-v3/configure | 2 +- >> libstdc++-v3/include/std/chrono | 9 +- >> libstdc++-v3/src/c++20/tzdb.cc | 88 ++++++++++++++++--- >> .../testsuite/std/time/tzdb/leap_seconds.cc | 1 + >> libstdc++-v3/testsuite/util/testsuite_abi.cc | 3 +- >> 7 files changed, 96 insertions(+), 16 deletions(-) >> >> diff --git a/libstdc++-v3/acinclude.m4 b/libstdc++-v3/acinclude.m4 >> index 8dc9e17b214c..3a4b11a98a28 100644 >> --- a/libstdc++-v3/acinclude.m4 >> +++ b/libstdc++-v3/acinclude.m4 >> @@ -4085,7 +4085,7 @@ changequote([,])dnl >> fi >> >> # For libtool versioning info, format is CURRENT:REVISION:AGE >> -libtool_VERSION=6:35:0 >> +libtool_VERSION=6:36:0 >> >> # Everything parsed; figure out what files and settings to use. >> case $enable_symvers in >> diff --git a/libstdc++-v3/config/abi/pre/gnu.ver >> b/libstdc++-v3/config/abi/pre/gnu.ver >> index bd4da6418295..35aaf89984d1 100644 >> --- a/libstdc++-v3/config/abi/pre/gnu.ver >> +++ b/libstdc++-v3/config/abi/pre/gnu.ver >> @@ -2623,6 +2623,13 @@ GLIBCXX_3.4.35 { >> >> } GLIBCXX_3.4.34; >> >> +# GCC 17.1.0 >> +GLIBCXX_3.4.36 { >> + >> + _ZNSt6chrono8__detail21__leap_seconds_expiryEv; >> + >> +} GLIBCXX_3.4.35; >> + >> # Symbols in the support library (libsupc++) have their own tag. >> CXXABI_1.3 { >> >> diff --git a/libstdc++-v3/configure b/libstdc++-v3/configure >> index 6713e4504b1c..013c388b9c2f 100755 >> --- a/libstdc++-v3/configure >> +++ b/libstdc++-v3/configure >> @@ -51418,7 +51418,7 @@ $as_echo "$as_me: WARNING: === Symbol versioning >> will be disabled." >&2;} >> fi >> >> # For libtool versioning info, format is CURRENT:REVISION:AGE >> -libtool_VERSION=6:35:0 >> +libtool_VERSION=6:36:0 >> >> # Everything parsed; figure out what files and settings to use. >> case $enable_symvers in >> diff --git a/libstdc++-v3/include/std/chrono >> b/libstdc++-v3/include/std/chrono >> index 674f867dcdc7..228293f12bef 100644 >> --- a/libstdc++-v3/include/std/chrono >> +++ b/libstdc++-v3/include/std/chrono >> @@ -3217,6 +3217,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >> >> namespace __detail >> { >> + // The list below is known to be valid until (at least) this date. >> + // This value is defined in the library (possibly to a newer value >> than >> + // the hardcoded value below) and can change at runtime. >> + sys_seconds __leap_seconds_expiry(); >> + >> inline leap_second_info >> __get_leap_second_info(sys_seconds __ss, bool __is_utc) >> { >> @@ -3252,12 +3257,12 @@ namespace __detail >> 1435708800, // 1 Jul 2015 >> 1483228800, // 1 Jan 2017 >> }; >> +#if _GLIBCXX_USE_CXX11_ABI || ! _GLIBCXX_USE_DUAL_ABI // use chrono::tzdb >> // The list above is known to be valid until (at least) this date >> // and only contains positive leap seconds. >> constexpr sys_seconds __expires(1798416000s); // 2026-12-28 >> 00:00:00 U > > > >> -#if _GLIBCXX_USE_CXX11_ABI || ! _GLIBCXX_USE_DUAL_ABI >> - if (__ss > __expires) >> + if (__ss > __expires && __ss > __leap_seconds_expiry()) >> { >> // Use updated leap_seconds from tzdb. >> size_t __n = std::size(__leaps); >> diff --git a/libstdc++-v3/src/c++20/tzdb.cc >> b/libstdc++-v3/src/c++20/tzdb.cc >> index b0fbfc46a6d3..ba0020814ba8 100644 >> --- a/libstdc++-v3/src/c++20/tzdb.cc >> +++ b/libstdc++-v3/src/c++20/tzdb.cc >> @@ -1251,6 +1251,40 @@ namespace std::chrono >> } >> #endif // TZDB_DISABLED >> >> +namespace >> +{ >> +#if ATOMIC_LONG_LOCK_FREE == 2 >> + using expiry_type = unsigned long; >> +#else >> + using expiry_type = unsigned; >> +#endif >> + // When GCC 16.1 was released with stable C++20 chrono support (in >> 2026), >> + // the last leap second in the list was the one in 2017. If another >> leap >> + // second is introduced in future, objects compiled by GCC 16.1 will >> not >> + // contain that leap second in the hardcoded list in <chrono>. >> + // This expiry time must be less than that first post-2017 leap second, >> + // so that old copies of __get_leap_second_info will use >> tzdb::leap_seconds >> + // which will contain the post-2017 leap seconds. >> + // If no new leap second is introduced, then this expiry time can just >> be >> + // updated to the 'expires' value read from the leapseconds file. >> + // tzdata 2026a leapseconds list expires at 2026-12-28 00:00:00 UTC >> + constinit std::atomic<expiry_type> leap_seconds_expiry{1798416000u}; >> > Could you extract the __expires > from tzdb_list::_Node::_S_read_leap_seconds() > We may move leaps, so they remain close together. > (in this file) as the global (but TU-local) variables, and use the > __expires as initializer here, > so we do not need to update two prices. This will not add any symbols to > the library, as we are in source file. > (This is why I am not suggesting doing that in header, as we will need to > make the data inline). > > > >> +} >> + >> + namespace __detail >> + { >> + // Called by chrono::__detail::__get_leap_second_info in <chrono>. >> + // The value returned by this function determines whether the >> hardcoded >> + // list in __get_leap_second_info is used, or if the >> tzdb::leap_seconds >> + // vector is used, which might require parsing and constructing a >> tzdb. >> + sys_seconds >> + __leap_seconds_expiry() >> + { >> + auto val = leap_seconds_expiry.load(memory_order::relaxed); >> + return sys_seconds(seconds(val)); >> + } >> + } >> + >> // Return leap_second values, and a bool indicating whether the values >> are >> // current (true), or potentially out of date (false). >> pair<vector<leap_second>, bool> >> @@ -1289,13 +1323,8 @@ namespace std::chrono >> (leap_second)1483228800, // 1 Jan 2017 >> }; >> >> -#if 0 >> - // This optimization isn't valid if the file has additional leap >> seconds >> - // defined since the library was compiled, but the system clock has >> been >> - // set to a time before the hardcoded expiration date. >> - if (system_clock::now() < expires) >> - return {std::move(leaps), true}; >> -#endif >> + sys_seconds new_expires = expires; >> + bool read_new_leaps = true; >> >> #ifndef TZDB_DISABLED >> if (ifstream ls{zoneinfo_file(leaps_file)}) >> @@ -1308,7 +1337,16 @@ namespace std::chrono >> // Leap YEAR MONTH DAY HH:MM:SS CORR R/S >> >> if (!s.starts_with("Leap")) >> - continue; >> + { >> + if (s.starts_with("#expires ")) >> + __try { >> + auto e = >> sys_seconds(seconds(std::stoll(s.substr(9)))); >> + if (e > new_expires) >> + new_expires = e; >> + } __catch (const std::exception&) { /* ignore */ } >> > Note that if reading the expires line fails, new_expires will be moved > to build default (__expires), i.e. backward from already been successfully > loaded > tzdb. > >> + continue; >> + } >> + >> istringstream li(std::move(s)); >> li.exceptions(ios::failbit); >> li.ignore(4); >> @@ -1339,12 +1377,40 @@ namespace std::chrono >> leaps.push_back(ls); >> } >> } >> - s = std::move(li).str(); // return storage to s >> + s = std::move(li).str(); // give allocated storage back to s >> } >> - return {std::move(leaps), true}; >> + >> + read_new_leaps = true; >> } >> #endif >> - return {std::move(leaps), false}; >> + >> + if (leaps.size() > 27) >> > And replaces this wit std::size(__leaps), they are defined in this > function. > No need for magic number. >> >> + { >> + // One or more new leap seconds have been introduced since 2026. >> + // See comment on __detail::__leap_seconds_expiry() above. >> + // Object files compiled by older versions of GCC may not have >> + // any leap seconds after 2017 in the hardcoded list in <chrono>, >> + // so the expiry time that the header code uses must be before the >> + // new leap seconds. >> + new_expires = leaps[27].date() - 1s; >> + } >> + >> + // Should we update the global expiry time? >> + const sys_seconds old_expires = __detail::__leap_seconds_expiry(); >> + >> + if (new_expires != old_expires) >> > This should check if new_expires > old_expires to avoid moving backward. > case of tests or failures to load. > >> + { >> + expiry_type old_exp = old_expires.time_since_epoch().count(); >> + expiry_type new_exp = new_expires.time_since_epoch().count(); >> + >> + // We don't care about this compare-exchange failing. If another >> + // thread updated the expiry time, just use that value instead. >> + leap_seconds_expiry.compare_exchange_strong(old_exp, new_exp, >> + memory_order::release, >> > Why do you use release here? There is no memory writes (except atomic) > that we want to see in other threads. After loading the value we may > continue > to use static data. > >> + >> memory_order::relaxed); >> + } >> + >> + return {std::move(leaps), read_new_leaps}; >> } >> >> #ifndef TZDB_DISABLED >> diff --git a/libstdc++-v3/testsuite/std/time/tzdb/leap_seconds.cc >> b/libstdc++-v3/testsuite/std/time/tzdb/leap_seconds.cc >> index 5999635a89f0..24ef7d44858d 100644 >> --- a/libstdc++-v3/testsuite/std/time/tzdb/leap_seconds.cc >> +++ b/libstdc++-v3/testsuite/std/time/tzdb/leap_seconds.cc >> @@ -52,6 +52,7 @@ Leap 2016 Dec 31 23:59:60 + S >> # These are fake leap seconds for testing purposes: >> Leap 2093 Jun 30 23:59:59 - S >> Leap 2093 Dec 31 23:59:60 + S >> +#expires 3991680000 (2096-06-28 00:00:00 UTC) >> )"; >> >> const auto& db = std::chrono::get_tzdb(); >> diff --git a/libstdc++-v3/testsuite/util/testsuite_abi.cc >> b/libstdc++-v3/testsuite/util/testsuite_abi.cc >> index 8fb38355cadd..4e80c5f184a6 100644 >> --- a/libstdc++-v3/testsuite/util/testsuite_abi.cc >> +++ b/libstdc++-v3/testsuite/util/testsuite_abi.cc >> @@ -217,6 +217,7 @@ check_version(symbol& test, bool added) >> known_versions.push_back("GLIBCXX_3.4.33"); >> known_versions.push_back("GLIBCXX_3.4.34"); >> known_versions.push_back("GLIBCXX_3.4.35"); >> + known_versions.push_back("GLIBCXX_3.4.36"); >> known_versions.push_back("GLIBCXX_LDBL_3.4.31"); >> known_versions.push_back("GLIBCXX_IEEE128_3.4.29"); >> known_versions.push_back("GLIBCXX_IEEE128_3.4.30"); >> @@ -260,7 +261,7 @@ check_version(symbol& test, bool added) >> test.version_status = symbol::incompatible; >> >> // Check that added symbols are added in the latest pre-release >> version. >> - bool latestp = (test.version_name == "GLIBCXX_3.4.35" >> + bool latestp = (test.version_name == "GLIBCXX_3.4.36" >> || test.version_name == "CXXABI_1.3.17" >> || test.version_name == "CXXABI_FLOAT128" >> || test.version_name == "CXXABI_TM_1"); >> -- >> 2.54.0 >> >>
