[16][PATCH] libstdc++: Improve handling of leap second expiry time [PR123165]

Jonathan Wakely jwakely@redhat.com
Mon Jul 20 10:15:30 GMT 2026


On Mon, 20 Jul 2026 at 10:54 +0200, Tomasz Kamiński wrote:
>From: Jonathan Wakely <jwakely@redhat.com>
>
>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.
>
>For times after the hardcoded expiry, the inline __get_leap_second_info
>function calls a new library function which compares the number of
>hardcoded leap seconds in the header with the number of leap seconds
>defined in the tzdata leapseconds file (usually provided by the OS).
>
>There are three leap second lists that are relevant here. The first is
>the hardcoded list (and its expiry time) in the <chrono> header. That is
>fixed when the user code is compiled, and might be out of date by the
>time the application runs. The second list (and its expiry time) is
>hardcoded in tzdb.cc in the libstdc++.so library. If the application
>uses a newer libstdc++.so at runtime than the <chrono> header used at
>compile time, we can still avoid going to the filesystem for dates
>within the expiry time of the list in libstdc++.so. The third list is
>the most up-to-date one which is read from the leapseconds file.
>
>The code added by this commit tries to avoid reading the file when
>possible (because that's slower than using the in-memory lists), and if
>it does have to go to the file system, it tries to avoid doing so again
>next time the leap seconds are needed. The later is handled by storing

"by storing the number"

>of recently know leaps seconds in num_leap_seconds of struct
>NumLeapSeconds, which decides whether to use std::atomic_ref<unsigned>
>or perform all accesses while holding a lock on the pre-existing mutex
>used for the tzdb_list singleton. To set_locked/set functions are used

"Two"

>to differentiate #if/#else branches of _Node::_S_replace_head that
>already held corresponding mutext and use atomic respectivelly.

"mutex"
"atomics"
"respectively"

>
>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::__recent_leap_second_info):
>	Declare new function and make it a friend of varous classes.
>	(leap_second): Make private constructor constexpr. Remove friend
>	declaration for get_leap_second_info.
>	(__detail::__get_leap_second_info): Use new function for times
>	past the hardcoded expiry.
>	* src/c++20/tzdb.cc (tzdb_list::_Node::fixed_leaps): Move array
>	of leap seconds here from _S_read_leap_seconds.
>	(_Node::NumLeapSeconds): New class.
>	(_Node::num_leap_seconds): New static variable.
>	(__detail::__recent_leap_second_info): Define new function.
>	(tzdb_list::_Node::_S_read_leap_seconds): Populate vector from
>	_Node::fixed_leaps. Rename bool variable to clarify meaning.
>	(tzdb_list::_Node::_S_replace_head): Update num_leap_seconds
>	when updating the tzdb_list.
>	* testsuite/util/testsuite_abi.cc: Update known_versions and
>	latestp.
>	* testsuite/std/time/clock/utc/leap_second_info-2.cc: New test.
>
>Co-authored-by: Tomasz Kamiński <tkaminsk@redhat.com>
>Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>
>(cherry picked from commit e79f0f818c0e42d0d84c90a54684dfb269ac388c)
>(cherry picked from commit b12fdd952511786bf8d285e7080e94deeb32aa1d)
>(cherry picked from commit adb4f4a064c009cd616106fa4c648182c18f96d9)
>(cherry picked from commit a7ad4bee6fe7cd77aceb6e86380dcaeb6da82791)
>---
>As suggested by cherry-pick lines this squashes the original and
>tree follow-up patches. Please double check if I missed something.

Thanks for squashing these, you got all the relevant commits.

>
>I am landing this first as this does introduce
>6.36 version that my format patches uses.
>
>I have updated the commit description a bit, please check if you
>have any suggestions.
>
>Testing on x86_64-linux.. OK for gcc-16 when all test passes?
>
> libstdc++-v3/acinclude.m4                     |   2 +-
> libstdc++-v3/config/abi/pre/gnu.ver           |   7 +
> libstdc++-v3/configure                        |   2 +-
> libstdc++-v3/include/std/chrono               | 104 +++----
> libstdc++-v3/src/c++20/tzdb.cc                | 294 +++++++++++++++---
> .../std/time/clock/utc/leap_second_info-2.cc  | 108 +++++++
> libstdc++-v3/testsuite/util/testsuite_abi.cc  |   3 +-
> 7 files changed, 420 insertions(+), 100 deletions(-)
> create mode 100644 libstdc++-v3/testsuite/std/time/clock/utc/leap_second_info-2.cc
>
>diff --git a/libstdc++-v3/acinclude.m4 b/libstdc++-v3/acinclude.m4
>index e6e4f7b394a..a615692e7be 100644
>--- a/libstdc++-v3/acinclude.m4
>+++ b/libstdc++-v3/acinclude.m4
>@@ -4096,7 +4096,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 bd4da641829..06892cd2d18 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

This needs to be 16.2.0 (I see you've fixed this locally now).

And we need a follow-up patch on trunk to bump the version to 3.4.37
and move the newer symbols (allocate_at_least etc.) to the new
version. Maybe that should happen first, so that when the gcc-16
branch is changed, it matches what's already on trunk.


>+GLIBCXX_3.4.36 {
>+
>+    _ZNSt6chrono8__detail25__recent_leap_second_infoERNS_16leap_second_infoEj;
>+
>+} 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 359545c4bce..bc9dce5e288 100755
>--- a/libstdc++-v3/configure
>+++ b/libstdc++-v3/configure
>@@ -51429,7 +51429,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 674f867dcdc..692fd6025e7 100644
>--- a/libstdc++-v3/include/std/chrono
>+++ b/libstdc++-v3/include/std/chrono
>@@ -131,6 +131,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>       leap_second_info
>       get_leap_second_info(const utc_time<_Duration>& __ut);
>
>+    /// @cond undocumented
>+    namespace __detail
>+    {
>+      bool __recent_leap_second_info(leap_second_info&, unsigned);
>+    }
>+    /// @endcond
>+
>     /** A clock that measures Universal Coordinated Time (UTC).
>      *
>      * The epoch is 1970-01-01 00:00:00.
>@@ -2818,6 +2825,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>       friend class leap_second;
>       friend struct time_zone::_Impl;
>       friend class time_zone_link;
>+
>+      friend bool
>+      __detail::__recent_leap_second_info(leap_second_info&, unsigned);
>     };
>
>     class time_zone_link
>@@ -2943,16 +2953,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> 	{ return __x.date() <=> __y; }
>
>     private:
>-      explicit leap_second(seconds::rep __s) : _M_s(__s) { }
>+      constexpr explicit leap_second(seconds::rep __s) : _M_s(__s) { }
>
>       friend struct tzdb_list::_Node;
>
>       friend const tzdb& reload_tzdb();
>
>-      template<typename _Duration>
>-	friend leap_second_info
>-	get_leap_second_info(const utc_time<_Duration>&);
>-
>       seconds _M_s; // == date().time_since_epoch() * value().count()
>     };
>
>@@ -3217,6 +3223,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>
> namespace __detail
> {
>+    // This function is inline to support fast conversions between utc_time
>+    // and sys_time when possible, without requiring a chrono::tzdb object
>+    // to be constructed.
>     inline leap_second_info
>     __get_leap_second_info(sys_seconds __ss, bool __is_utc)
>     {
>@@ -3252,65 +3261,56 @@ namespace __detail
> 	1435708800, // 1 Jul 2015
> 	1483228800, // 1 Jan 2017
>       };
>+
>+      // The default result for times after the last leap year.
>+      constexpr leap_second_info __after_last{
>+	.is_leap_second = false,
>+	.elapsed = seconds(std::size(__leaps))
>+      };
>+
>       // 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)
> 	{
>-	  // Use updated leap_seconds from tzdb.
>-	  size_t __n = std::size(__leaps);
>-
>-	  auto __db = get_tzdb_list().begin();
>-	  auto __first = __db->leap_seconds.begin() + __n;
>-	  auto __last = __db->leap_seconds.end();
>-	  auto __pos = std::upper_bound(__first, __last, __ss);
>-	  seconds __elapsed(__n);
>-	  for (auto __i = __first; __i != __pos; ++__i)
>-	    __elapsed += __i->value();
>-
>-	  if (__is_utc)
>-	    {
>-	      // Convert utc_time to sys_time:
>-	      __ss -= __elapsed;
>-	      // See if that sys_time is before (or during) previous leap sec:
>-	      if (__pos != __first && __ss < __pos[-1])
>-		{
>-		  if ((__ss + 1s) >= __pos[-1])
>-		    return {true, __elapsed};
>-		  __elapsed -= __pos[-1].value();
>-		}
>-	    }
>-	  return {false, __elapsed};
>-	}
>-      else
>+#if _GLIBCXX_USE_CXX11_ABI || ! _GLIBCXX_USE_DUAL_ABI // use chrono::tzdb
>+	  // Call into the library because it might have knowledge of new
>+	  // leap seconds loaded at runtime from the tzdata files.
>+
>+	  // We misuse leap_second_info here to pass {bool, seconds} inputs:
>+	  leap_second_info __info{ .is_leap_second = __is_utc,
>+				   .elapsed = __ss.time_since_epoch() };
>+	  // If this returns true, then __info holds the output result:
>+	  if (__detail::__recent_leap_second_info(__info, std::size(__leaps)))
>+	    return __info;
> #endif
>-	{
>-	  seconds::rep __s = __ss.time_since_epoch().count();
>-	  const seconds::rep* __first = std::begin(__leaps);
>-	  const seconds::rep* __last = std::end(__leaps);
>+	  return __after_last;
>+	}
>
>-	  // Don't bother searching the list if we're after the last one.
>-	  if (__s > (__last[-1] + (__last - __first) + 1))
>-	    return { false, seconds(__last - __first) };
>+      seconds::rep __s = __ss.time_since_epoch().count();
>+      const seconds::rep* __first = std::begin(__leaps);
>+      const seconds::rep* __last = std::end(__leaps);
>
>-	  auto __pos = std::upper_bound(__first, __last, __s);
>-	  seconds __elapsed{__pos - __first};
>-	  if (__is_utc)
>+      // Don't bother searching the list if we're after the last one.
>+      if (__s > (__last[-1] + (__last - __first) + 1))
>+	return __after_last;
>+
>+      auto __pos = std::upper_bound(__first, __last, __s);
>+      seconds __elapsed{__pos - __first};
>+      if (__is_utc)
>+	{
>+	  // Convert utc_time to sys_time:
>+	  __s -= __elapsed.count();
>+	  // See if that sys_time is before (or during) previous leap sec:
>+	  if (__pos != __first && __s < __pos[-1])
> 	    {
>-	      // Convert utc_time to sys_time:
>-	      __s -= __elapsed.count();
>-	      // See if that sys_time is before (or during) previous leap sec:
>-	      if (__pos != __first && __s < __pos[-1])
>-		{
>-		  if ((__s + 1) >= __pos[-1])
>-		    return {true, __elapsed};
>-		  --__elapsed;
>-		}
>+	      if ((__s + 1) >= __pos[-1])
>+		return {true, __elapsed};
>+	      --__elapsed;
> 	    }
>-	  return {false, __elapsed};
> 	}
>+      return {false, __elapsed};
>     }
> } // namespace __detail
>
>diff --git a/libstdc++-v3/src/c++20/tzdb.cc b/libstdc++-v3/src/c++20/tzdb.cc
>index b0fbfc46a6d..0013ad1877c 100644
>--- a/libstdc++-v3/src/c++20/tzdb.cc
>+++ b/libstdc++-v3/src/c++20/tzdb.cc
>@@ -36,6 +36,7 @@
> #include <memory>     // atomic<shared_ptr<T>>
> #include <mutex>      // mutex
> #include <iomanip>    // quoted
>+#include <span>
> #if defined __GTHREADS && ! defined _GLIBCXX_HAS_GTHREADS
> # include <ext/concurrence.h> // __gnu_cxx::__mutex
> #endif
>@@ -66,6 +67,7 @@
> #endif
>
> #if USE_ATOMIC_SHARED_PTR && ! USE_ATOMIC_LIST_HEAD
>+// Cannot use atomic<shared_ptr<T>> without lock-free atomic<T*>.
> # error Unsupported combination
> #endif
>
>@@ -196,6 +198,13 @@ namespace std::chrono
>     static const tzdb& _S_replace_head(shared_ptr<_Node>, shared_ptr<_Node>);
>
>     static pair<vector<leap_second>, bool> _S_read_leap_seconds();
>+
>+    // This is here because _Node is a friend so can call private constructor.
>+    static const leap_second fixed_leaps[];
>+
>+    // This is a member so that it can access fixed_leaps.
>+    struct NumLeapSeconds;
>+    static NumLeapSeconds num_leap_seconds;
>   };
>
>   // Implementation of the private constructor used for the singleton object.
>@@ -1251,56 +1260,240 @@ namespace std::chrono
>   }
> #endif // TZDB_DISABLED
>
>+// These are the same values as the array in the <chrono> header, but might
>+// contain additional leap seconds if the libstdc++.so used at runtime is
>+// newer than the <chrono> header used to compile parts of the application.
>+constexpr leap_second tzdb_list::_Node::fixed_leaps[] {
>+#define LS leap_second
>+  LS(  78796800), // 1 Jul 1972
>+  LS(  94694400), // 1 Jan 1973
>+  LS( 126230400), // 1 Jan 1974
>+  LS( 157766400), // 1 Jan 1975
>+  LS( 189302400), // 1 Jan 1976
>+  LS( 220924800), // 1 Jan 1977
>+  LS( 252460800), // 1 Jan 1978
>+  LS( 283996800), // 1 Jan 1979
>+  LS( 315532800), // 1 Jan 1980
>+  LS( 362793600), // 1 Jul 1981
>+  LS( 394329600), // 1 Jul 1982
>+  LS( 425865600), // 1 Jul 1983
>+  LS( 489024000), // 1 Jul 1985
>+  LS( 567993600), // 1 Jan 1988
>+  LS( 631152000), // 1 Jan 1990
>+  LS( 662688000), // 1 Jan 1991
>+  LS( 709948800), // 1 Jul 1992
>+  LS( 741484800), // 1 Jul 1993
>+  LS( 773020800), // 1 Jul 1994
>+  LS( 820454400), // 1 Jan 1996
>+  LS( 867715200), // 1 Jul 1997
>+  LS( 915148800), // 1 Jan 1999
>+  LS(1136073600), // 1 Jan 2006
>+  LS(1230768000), // 1 Jan 2009
>+  LS(1341100800), // 1 Jul 2012
>+  LS(1435708800), // 1 Jul 2015
>+  LS(1483228800), // 1 Jan 2017
>+  // If new leap seconds get defined they should be added here.
>+  // Negative leap seconds are represented as -1 * timestamp.
>+#undef LS
>+};
>+
>+namespace
>+{
>+  // The expiry date corresponding to the list above.
>+  // tzdata 2026a leapseconds list expires at 2026-12-28 00:00:00 UTC
>+  constexpr seconds fixed_expiry{1798416000u};
>+}
>+
>+// This holds the most up-to-date number of leap seconds known at runtime.
>+// Initially zero, updated when _S_read_leap_seconds() is called.
>+struct tzdb_list::_Node::NumLeapSeconds
>+{
>+  // Called by __recent_leap_second_info to read num_leap_seconds.
>+  unsigned
>+  get()
>+  {
>+#if ATOMIC_INT_LOCK_FREE == 2
>+    atomic_ref<unsigned> ref(count);
>+    auto num = ref.load(memory_order::relaxed);
>+
>+    if (num == std::size(_Node::fixed_leaps))
>+      // A leapseconds file has been read and has no new leap seconds.
>+      return num;
>+
>+    if (num == 0)
>+      // No leapseconds file has been read yet.
>+      return 0;
>+
>+    // The tzdb_list has been initialized and contains a tzdb object with
>+    // new leap seconds, which the caller is going to use.
>+    // The relaxed load above does not synchronize with anything, so to
>+    // ensure that the get_tzdb_list() in the caller will see a tzdb object
>+    // set by _S_replace_head, we load num_leap_seconds again with acquire
>+    // ordering:
>+    return ref.load(memory_order::acquire);
>+#else
>+    lock_guard<mutex> l(list_mutex()); // This ensures acquire ordering.
>+    return count;
>+#endif
>+  }
>+
>+  // Called by __recent_leap_second_info to set num_leap_seconds when
>+  // we have determined there are no new leap seconds in a leapseconds file.
>+  void
>+  set_to_fixed_size()
>+  {
>+#if ATOMIC_INT_LOCK_FREE == 2
>+    atomic_ref<unsigned> ref(count);
>+    unsigned expected = 0;
>+    ref.compare_exchange_strong(expected, std::size(_Node::fixed_leaps),
>+				memory_order::relaxed);
>+#else
>+    lock_guard<mutex> l(list_mutex());
>+    if (count == 0)
>+      count = std::size(_Node::fixed_leaps);
>+#endif
>+  }
>+
>+  // Called by _Node::_S_replace_head
>+  // The two versions are named differently so that caller has to be explicit
>+  // about which version it calls, based on whether the mutex is held.
>+  void
>+  set(unsigned val)
>+  {
>+#if ATOMIC_INT_LOCK_FREE == 2
>+    atomic_ref<unsigned> ref(count);
>+    // The release op here synchronizes with the acquire op in get().
>+    ref.store(val, memory_order::release);
>+#else
>+    lock_guard<mutex> l(list_mutex());
>+    count = val;
>+#endif
>+  }
>+
>+  void
>+  set_locked(unsigned val, const lock_guard<mutex>&)
>+  {
>+#if ATOMIC_INT_LOCK_FREE == 2
>+    // Even though the caller locked the mutex, we still need to use an
>+    // atomic store in this case, because there could be concurrent loads.
>+    set(val);
>+#else
>+    // The only caller of this function locks list_mutex() so we would
>+    // deadlock if we locked it again here.
>+    count = val;
>+#endif
>+  }
>+
>+private:
>+  unsigned count = 0;
>+};
>+
>+constinit tzdb_list::_Node::NumLeapSeconds tzdb_list::_Node::num_leap_seconds;
>+
>+  namespace __detail
>+  {
>+    // Called by chrono::__detail::__get_leap_second_info in <chrono>
>+    // to get leap_second_info for times after the expiry date in the header.
>+    // The caller provides the time being queried in `info.elapsed` and
>+    // whether that is a UTC time in `info.is_leap_second`.
>+    // If it returns true, this function did the lookup and updated `info`.
>+    // If this returns false, it means the hardcoded list of leap seconds
>+    // in the header should be used for the lookup.
>+    bool
>+    __recent_leap_second_info(leap_second_info& info,
>+			      unsigned num_positive_leaps)
>+    {
>+      // Extract the input args from info:
>+      const auto [is_utc, secs] = info;
>+      // And then reuse info for the output result:
>+      info.is_leap_second = false;
>+      info.elapsed = seconds(num_positive_leaps);
>+
>+      auto update_info = [&](span<const leap_second> leaps)
>+      {
>+	if (leaps.size() == num_positive_leaps)
>+	  return false; // No new leap seconds, use the array in the header.
>+
>+	// info.elapsed already contains the first N leap seconds,
>+	// so we only search the end of the span.
>+	auto first = leaps.begin() + num_positive_leaps;
>+	auto pos = std::upper_bound(first, leaps.end(), sys_seconds(secs));
>+	for (auto i = first; i != pos; ++i)
>+	  info.elapsed += i->value();
>+
>+	if (is_utc)
>+	  {
>+	    // This should never happen, but check it so that pos[-1] is valid:
>+	    if (num_positive_leaps == 0) [[unlikely]]
>+	      return false;
>+
>+	    // Convert utc_time to sys_time:
>+	    sys_seconds ss(secs - info.elapsed);
>+	    // See if that sys_time is before (or during) previous leap sec:
>+	    if (ss < pos[-1])
>+	      {
>+		if ((ss + 1s) >= pos[-1])
>+		  info.is_leap_second = true;
>+		else
>+		  info.elapsed -= pos[-1].value();
>+	      }
>+	  }
>+	return true;
>+      };
>+
>+      using _Node = tzdb_list::_Node;
>+
>+      // If the caller was compiled using an older GCC with an older expiry
>+      // time in the header than the `fixed_expiry` defined above, we might
>+      // be able to answer the query easily using the static `fixed_leaps`.
>+      if (secs <= fixed_expiry)
>+	return update_info(_Node::fixed_leaps);
>+
>+      constexpr auto num_fixed_leaps = std::size(_Node::fixed_leaps);
>+
>+      auto num_leaps = _Node::num_leap_seconds.get();
>+      if (num_leaps == num_fixed_leaps)
>+	// A leapseconds file has been read and has no new leap seconds:
>+	return update_info(_Node::fixed_leaps);
>+      else if (num_leaps == 0)
>+	{
>+	  // The tzdb_list has not been initialized yet, so we don't know
>+	  // the correct number of leap seconds.
>+	  // We use _S_read_leap_seconds() to read the leapseconds file.
>+	  // If that tells us there are no new leapseconds, we don't need
>+	  // to parse all of tzdata.zi and initialize a whole tzdb object.
>+	  if (_Node::_S_read_leap_seconds().first.size() == num_fixed_leaps)
>+	    {
>+	      // There are no new leap seconds. Remember that so that the next
>+	      // call to this function can just use fixed_leaps.
>+	      _Node::num_leap_seconds.set_to_fixed_size();
>+	      return update_info(_Node::fixed_leaps);
>+	    }
>+	  // else there are new leap seconds. We init tzdb_list so that the
>+	  // new leap seconds are persisted in a tzdb object.
>+	}
>+
>+      // Use updated leap_seconds from tzdb.
>+      return update_info(get_tzdb_list().begin()->leap_seconds);
>+    }
>+  }
>+
>   // 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>
>   tzdb_list::_Node::_S_read_leap_seconds()
>   {
>-    // This list is valid until at least 2026-12-28 00:00:00 UTC.
>-    constexpr auto expires = sys_days{2026y/12/28};
>-    vector<leap_second> leaps
>-    {
>-      (leap_second)  78796800, // 1 Jul 1972
>-      (leap_second)  94694400, // 1 Jan 1973
>-      (leap_second) 126230400, // 1 Jan 1974
>-      (leap_second) 157766400, // 1 Jan 1975
>-      (leap_second) 189302400, // 1 Jan 1976
>-      (leap_second) 220924800, // 1 Jan 1977
>-      (leap_second) 252460800, // 1 Jan 1978
>-      (leap_second) 283996800, // 1 Jan 1979
>-      (leap_second) 315532800, // 1 Jan 1980
>-      (leap_second) 362793600, // 1 Jul 1981
>-      (leap_second) 394329600, // 1 Jul 1982
>-      (leap_second) 425865600, // 1 Jul 1983
>-      (leap_second) 489024000, // 1 Jul 1985
>-      (leap_second) 567993600, // 1 Jan 1988
>-      (leap_second) 631152000, // 1 Jan 1990
>-      (leap_second) 662688000, // 1 Jan 1991
>-      (leap_second) 709948800, // 1 Jul 1992
>-      (leap_second) 741484800, // 1 Jul 1993
>-      (leap_second) 773020800, // 1 Jul 1994
>-      (leap_second) 820454400, // 1 Jan 1996
>-      (leap_second) 867715200, // 1 Jul 1997
>-      (leap_second) 915148800, // 1 Jan 1999
>-      (leap_second)1136073600, // 1 Jan 2006
>-      (leap_second)1230768000, // 1 Jan 2009
>-      (leap_second)1341100800, // 1 Jul 2012
>-      (leap_second)1435708800, // 1 Jul 2015
>-      (leap_second)1483228800, // 1 Jan 2017
>-    };
>+    // Populate the vector with the leap seconds we already know about:
>+    vector<leap_second> leaps(fixed_leaps, std::end(fixed_leaps));
>
>-#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
>+    bool read_leaps_file = false;
>
> #ifndef TZDB_DISABLED
>     if (ifstream ls{zoneinfo_file(leaps_file)})
>       {
>-	auto exp_year = year_month_day(expires).year();
>+	constexpr year exp_year
>+	  = year_month_day(sys_days(duration_cast<days>(fixed_expiry))).year();
>+
> 	std::string s, w;
> 	s.reserve(80); // Avoid later reallocations.
> 	while (std::getline(ls, s))
>@@ -1309,6 +1502,7 @@ namespace std::chrono
>
> 	    if (!s.starts_with("Leap"))
> 	      continue;
>+
> 	    istringstream li(std::move(s));
> 	    li.exceptions(ios::failbit);
> 	    li.ignore(4);
>@@ -1339,12 +1533,14 @@ 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_leaps_file = true;
>       }
> #endif
>-    return {std::move(leaps), false};
>+
>+    return {std::move(leaps), read_leaps_file};
>   }
>
> #ifndef TZDB_DISABLED
>@@ -1402,8 +1598,13 @@ namespace std::chrono
> 	new_head_ptr->next = curr;
>       }
>     // XXX small window here where _S_head_cache still points to previous tzdb.
>+    _S_cache_list_head(new_head_ptr);
>+
>+    // This allows __recent_leap_second_info() to know that it can use
>+    // get_tzdb_list()->begin()->leap_seconds to get new leap seconds.
>+    num_leap_seconds.set(new_head_ptr->db.leap_seconds.size());
> #else
>-    lock_guard<mutex> l(list_mutex());
>+    lock_guard<mutex> lock(list_mutex());
>     if (const _Node* h = _S_head_owner.get())
>       {
> 	if (h->db.version == new_head_ptr->db.version)
>@@ -1411,8 +1612,11 @@ namespace std::chrono
> 	new_head_ptr->next = _S_head_owner;
>       }
>     _S_head_owner = std::move(new_head);
>-#endif
>     _S_cache_list_head(new_head_ptr);
>+
>+    num_leap_seconds.set_locked(new_head_ptr->db.leap_seconds.size(), lock);
>+#endif
>+
>     return new_head_ptr->db;
>   }
>
>diff --git a/libstdc++-v3/testsuite/std/time/clock/utc/leap_second_info-2.cc b/libstdc++-v3/testsuite/std/time/clock/utc/leap_second_info-2.cc
>new file mode 100644
>index 00000000000..090a28291c8
>--- /dev/null
>+++ b/libstdc++-v3/testsuite/std/time/clock/utc/leap_second_info-2.cc
>@@ -0,0 +1,108 @@
>+// { dg-do run { target c++20 } }
>+// { dg-require-effective-target tzdb }
>+// { dg-require-effective-target cxx11_abi }
>+// { dg-xfail-run-if "no weak override on AIX" { powerpc-ibm-aix* } }
>+
>+#include <chrono>
>+#include <fstream>
>+#include <testsuite_hooks.h>
>+
>+using namespace std::chrono_literals;
>+
>+static bool override_used = false;
>+
>+namespace __gnu_cxx
>+{
>+  const char* zoneinfo_dir_override() {
>+    override_used = true;
>+    return "./";
>+  }
>+}
>+
>+void
>+test_known_leaps()
>+{
>+  // Test some values within the list of known leap seconds.
>+  auto s = std::chrono::utc_seconds(-1s);
>+  auto lsi = get_leap_second_info(s);
>+  VERIFY( lsi.is_leap_second == false );
>+  VERIFY( lsi.elapsed == 0s );
>+
>+  s = std::chrono::utc_seconds(126230402s); // 1 Jan 1974
>+  lsi = get_leap_second_info(s);
>+  VERIFY( lsi.is_leap_second == true );
>+  VERIFY( lsi.elapsed == 3s );
>+
>+  s = std::chrono::utc_seconds(1483228826s); // 1 Jan 2017
>+  lsi = get_leap_second_info(s);
>+  VERIFY( lsi.is_leap_second == true );
>+  VERIFY( lsi.elapsed == 27s );
>+
>+  // We should not have checked the filesystem for the times above.
>+  VERIFY( ! override_used );
>+}
>+
>+void
>+test_future_leaps()
>+{
>+  // XXX adjust this if real leap seconds are added to the hardcoded lists:
>+  const auto hardcoded_count = 27s;
>+
>+  // Make sure there's no old file from a previous test run.
>+  std::ofstream("leapseconds") << "";
>+
>+  using std::chrono::years;
>+  auto s = std::chrono::utc_seconds(1483228826s + years(100)); // 1 Jan 2117
>+  auto lsi = get_leap_second_info(s);
>+  VERIFY( override_used ); // If this fails then XFAIL for the target.
>+  VERIFY( lsi.is_leap_second == false );
>+  VERIFY( lsi.elapsed == hardcoded_count );
>+  lsi = get_leap_second_info(s + years(10));
>+  VERIFY( lsi.is_leap_second == false );
>+  VERIFY( lsi.elapsed == hardcoded_count );
>+
>+  std::ofstream("leapseconds") << R"(
>+# No need to repeat the real leap seconds here, they're hardcoded in the lib.
>+# These are fake new leap seconds for testing purposes:
>+Leap	2099	Dec	31	23:59:60	+	S
>+Leap	2120	Jun	30	23:59:59	-	S
>+Leap	2120	Dec	30	23:59:59	-	S
>+)";
>+
>+  override_used = false;
>+  std::chrono::reload_tzdb();
>+  VERIFY( override_used );
>+
>+  // Check the same dates again using the custom leapseconds file:
>+  lsi = get_leap_second_info(s);
>+  VERIFY( lsi.is_leap_second == false );
>+  VERIFY( lsi.elapsed == hardcoded_count + 1s ); // One positive leap second.
>+  lsi = get_leap_second_info(s + years(10));
>+  VERIFY( lsi.is_leap_second == false );
>+  VERIFY( lsi.elapsed == hardcoded_count - 1s ); // Two negative leap seconds.
>+
>+  // Overwrite the custom file again:
>+  override_used = false;
>+  std::ofstream("leapseconds") << "";
>+
>+  // This should not re-read the custom file, the head of the tzdb_list
>+  // should already have been populated by calling get_leap_second_info(s):
>+  auto& tzdb = std::chrono::get_tzdb();
>+  // The file was not read again:
>+  VERIFY( ! override_used );
>+  // The list in the tzdb contains the three fake leap seconds:
>+  VERIFY( tzdb.leap_seconds.size() == hardcoded_count.count() + 3 );
>+  // And repeating the queries above gives the same results:
>+  lsi = get_leap_second_info(s);
>+  VERIFY( lsi.is_leap_second == false );
>+  VERIFY( lsi.elapsed == hardcoded_count + 1s ); // One positive leap second.
>+  lsi = get_leap_second_info(s + years(10));
>+  VERIFY( lsi.is_leap_second == false );
>+  VERIFY( lsi.elapsed == hardcoded_count - 1s ); // Two negative leap seconds.
>+}
>+
>+int main()
>+{
>+  test_known_leaps();
>+  test_future_leaps();
>+}
>diff --git a/libstdc++-v3/testsuite/util/testsuite_abi.cc b/libstdc++-v3/testsuite/util/testsuite_abi.cc
>index 8fb38355cad..4e80c5f184a 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.55.0
>
>



More information about the Libstdc++ mailing list