| Message ID | 20260506141730.403268-1-jwakely@redhat.com |
|---|---|
| State | New |
| Headers |
Return-Path: <gcc-patches-bounces~patchwork=sourceware.org@gcc.gnu.org> X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id E71B04BA799D for <patchwork@sourceware.org>; Wed, 6 May 2026 14:19:10 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org E71B04BA799D Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=U6AtDe6F X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTP id 7DBB04BA23C9 for <gcc-patches@gcc.gnu.org>; Wed, 6 May 2026 14:17:38 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 7DBB04BA23C9 Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 7DBB04BA23C9 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1778077063; cv=none; b=qu7w5HFYFlhX0qOEiItzMCKfKADkkL8Hr5mIcckLUnniOBN9Ie5WWikx8zr+64/fYfqzSXVkTFMvkZ1OqRuq83CWpo0TYotrByjq3Z/RORDaZI8a1fsdQEACIEJGsdevHJtdEYfdwkgBYVBA9+hyrTYkcxmXSBgOcZ1V1nw9XAk= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1778077063; c=relaxed/simple; bh=Nm3LILGpnKLQ09cjx0BM7IhzueqM4Ha1Ur/6s4ci+Y8=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=tx0d32x6yNhPhHYE5cBCc7k7ZPZfJaOEG0jH4gdI9rXvA4dRcI/AcrRNLwFQaMf67Bw32InMo96N8nfPwUTBdnKev+B0aVuqm7stz0XHzP6qrT6Jchnw5XxHjHGNGgg0CXMBSf0VxsPjuy8RcZiG59dbx/6CbGDVkl14wtBcFH8= ARC-Authentication-Results: i=1; sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=U6AtDe6F DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 7DBB04BA23C9 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1778077058; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=dO3fd8yloxMnjZM6KyV/27b5MXxom+8DfKQHyYObJXs=; b=U6AtDe6FLK1ILpupd7t6kGxyeUmBkeKGKguZbRX6scVoPHaqDyJTPhVu5D2uQ4kEo5N9Wo wO+92/bDlIfugdb/gwRUfBJXmXKV1ZMXxQRWquEcd2su1a7ugdpYPhMgxJw1c66qMOSFuf khl6i1BC/BtaX4+BhGGa9fLy8qVr2hE= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-278-uK2GKbeyMrSrVSig2_I6Iw-1; Wed, 06 May 2026 10:17:34 -0400 X-MC-Unique: uK2GKbeyMrSrVSig2_I6Iw-1 X-Mimecast-MFC-AGG-ID: uK2GKbeyMrSrVSig2_I6Iw_1778077053 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id B48AA1956094; Wed, 6 May 2026 14:17:33 +0000 (UTC) Received: from zen.kayari.org (unknown [10.44.32.38]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 75FB81944CC4; Wed, 6 May 2026 14:17:32 +0000 (UTC) From: Jonathan Wakely <jwakely@redhat.com> To: gcc-patches@gcc.gnu.org, libstdc++@gcc.gnu.org Subject: [PATCH] libstdc++: Cache leap second expiry time in <chrono> [PR123165] Date: Wed, 6 May 2026 15:16:19 +0100 Message-ID: <20260506141730.403268-1-jwakely@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: j-vwiHA_JNFHhb1GgLBSUxqFvI59ClhRATN15bHE-fc_1778077053 X-Mimecast-Originator: redhat.com Content-Type: text/plain Content-Transfer-Encoding: 8bit X-Spam-Status: No, score=-12.5 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H5, RCVD_IN_MSPIKE_WL, SPF_HELO_PASS, SPF_NONE, TXREP, URIBL_BLOCKED autolearn=unavailable autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gcc-patches mailing list <gcc-patches.gcc.gnu.org> List-Unsubscribe: <https://gcc.gnu.org/mailman/options/gcc-patches>, <mailto:gcc-patches-request@gcc.gnu.org?subject=unsubscribe> List-Archive: <https://gcc.gnu.org/pipermail/gcc-patches/> List-Post: <mailto:gcc-patches@gcc.gnu.org> List-Help: <mailto:gcc-patches-request@gcc.gnu.org?subject=help> List-Subscribe: <https://gcc.gnu.org/mailman/listinfo/gcc-patches>, <mailto:gcc-patches-request@gcc.gnu.org?subject=subscribe> Errors-To: gcc-patches-bounces~patchwork=sourceware.org@gcc.gnu.org |
| 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
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 > >
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 >> >>
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 >>
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 > >> > >
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.
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 >>
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 > >> > >
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 >> >> >> >>
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 >>> >> >>> >>>
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 >>>> >> >>>> >>>>
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 >>>> >> >>>> >>>>
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");