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

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");
-- 
2.54.0

Reply via email to