[committed] libstdc++: Add C++20 clocks

Message ID 20221113011640.920781-1-jwakely@redhat.com
State Committed
Commit 1736bf5a61c7364c5da6fa52e5e2bfdbc9507c97
Headers
Series [committed] libstdc++: Add C++20 clocks |

Commit Message

Jonathan Wakely Nov. 13, 2022, 1:16 a.m. UTC
  Tested x86_64-linux and powerpc64le-linux. Pushed to trunk.

-- >8 --

Also add the basic types for timezones, without the non-inline
definitions needed to actually use them.

The get_leap_second_info function currently uses a hardcoded list of
leap seconds, correct as of the end of 2022. That needs to be replaced
with a dynamically generated list read from the system tzdata. That will
be done in a later patch.

libstdc++-v3/ChangeLog:

	* include/std/chrono (utc_clock, tai_clock, gps_clock): Define.
	(clock_time_conversion, clock_cast): Define.
	(sys_info, local_info): Define structs for timezone information.
	(nonexistent_local_time, ambiguous_local_time): Define
	exceptions for invalid times.
	(time_zone, time_zone_link, leap_second, zoned_traits, tzdb)
	(tzdb_list): Define classes representing time zones.
	(get_leap_second_info): Define new function returning leap
	second offset for a given time point.
	* testsuite/std/time/clock/gps/1.cc: New test.
	* testsuite/std/time/clock/tai/1.cc: New test.
	* testsuite/std/time/clock/utc/1.cc: New test.
---
 libstdc++-v3/include/std/chrono               | 744 +++++++++++++++++-
 .../testsuite/std/time/clock/gps/1.cc         |  38 +
 .../testsuite/std/time/clock/tai/1.cc         |  41 +
 .../testsuite/std/time/clock/utc/1.cc         |  24 +
 4 files changed, 844 insertions(+), 3 deletions(-)
 create mode 100644 libstdc++-v3/testsuite/std/time/clock/gps/1.cc
 create mode 100644 libstdc++-v3/testsuite/std/time/clock/tai/1.cc
 create mode 100644 libstdc++-v3/testsuite/std/time/clock/utc/1.cc
  

Comments

Jonathan Wakely Nov. 13, 2022, 11:01 a.m. UTC | #1
On Sun, 13 Nov 2022 at 01:17, Jonathan Wakely via Libstdc++
<libstdc++@gcc.gnu.org> wrote:
>
> Tested x86_64-linux and powerpc64le-linux. Pushed to trunk.
>
> -- >8 --
>
> Also add the basic types for timezones, without the non-inline
> definitions needed to actually use them.

This is the patch for the rest of the time zone support.

Not pushed yet.
diff --git a/libstdc++-v3/config/abi/pre/gnu.ver b/libstdc++-v3/config/abi/pre/gnu.ver
index 225d6dc482b..8e5d0bf081f 100644
--- a/libstdc++-v3/config/abi/pre/gnu.ver
+++ b/libstdc++-v3/config/abi/pre/gnu.ver
@@ -2485,6 +2485,14 @@ GLIBCXX_3.4.31 {
 
     _ZSt15__try_use_facet*;
 
+    _ZNSt6chrono11reload_tzdbEv;
+    _ZNSt6chrono8get_tzdbEv;
+    _ZNSt6chrono13get_tzdb_listEv;
+    _ZNSt6chrono14remote_versionB5cxx11Ev;
+    _ZNSt6chrono14remote_versionEv;
+    _ZNKSt6chrono4tzdb12current_zoneEv;
+    _ZNKSt6chrono4tzdb11locate_zoneESt17basic_string_viewIcSt11char_traitsIcEE;
+
 } GLIBCXX_3.4.30;
 
 # Symbols in the support library (libsupc++) have their own tag.
diff --git a/libstdc++-v3/include/Makefile.am b/libstdc++-v3/include/Makefile.am
index 27dfa2be2f3..08ce9abbcbc 100644
--- a/libstdc++-v3/include/Makefile.am
+++ b/libstdc++-v3/include/Makefile.am
@@ -175,6 +175,7 @@ bits_headers = \
 	${bits_srcdir}/char_traits.h \
 	${bits_srcdir}/charconv.h \
 	${bits_srcdir}/chrono.h \
+	${bits_srcdir}/chrono_io.h \
 	${bits_srcdir}/codecvt.h \
 	${bits_srcdir}/cow_string.h \
 	${bits_srcdir}/deque.tcc \
diff --git a/libstdc++-v3/include/Makefile.in b/libstdc++-v3/include/Makefile.in
index 64621922f77..401d0eead58 100644
--- a/libstdc++-v3/include/Makefile.in
+++ b/libstdc++-v3/include/Makefile.in
@@ -528,6 +528,7 @@ bits_freestanding = \
 @GLIBCXX_HOSTED_TRUE@	${bits_srcdir}/char_traits.h \
 @GLIBCXX_HOSTED_TRUE@	${bits_srcdir}/charconv.h \
 @GLIBCXX_HOSTED_TRUE@	${bits_srcdir}/chrono.h \
+@GLIBCXX_HOSTED_TRUE@	${bits_srcdir}/chrono_io.h \
 @GLIBCXX_HOSTED_TRUE@	${bits_srcdir}/codecvt.h \
 @GLIBCXX_HOSTED_TRUE@	${bits_srcdir}/cow_string.h \
 @GLIBCXX_HOSTED_TRUE@	${bits_srcdir}/deque.tcc \
diff --git a/libstdc++-v3/include/bits/chrono.h b/libstdc++-v3/include/bits/chrono.h
index 05987ca09df..432b25affea 100644
--- a/libstdc++-v3/include/bits/chrono.h
+++ b/libstdc++-v3/include/bits/chrono.h
@@ -1069,6 +1069,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 		 const time_point<_Clock, _Dur2>& __rhs)
       { return !(__lhs < __rhs); }
 
+#if __cpp_variable_templates
+    template<typename _Tp>
+      inline constexpr bool __is_duration_v = false;
+    template<typename _Rep, typename _Period>
+      inline constexpr bool __is_duration_v<duration<_Rep, _Period>> = true;
+    template<typename _Tp>
+      inline constexpr bool __is_time_point_v = false;
+    template<typename _Clock, typename _Dur>
+      inline constexpr bool __is_time_point_v<time_point<_Clock, _Dur>> = true;
+#endif
+
     /// @}
     /// @} group chrono
 
diff --git a/libstdc++-v3/include/bits/chrono_io.h b/libstdc++-v3/include/bits/chrono_io.h
new file mode 100644
index 00000000000..779a65ece91
--- /dev/null
+++ b/libstdc++-v3/include/bits/chrono_io.h
@@ -0,0 +1,1633 @@
+// <format> Formatting -*- C++ -*-
+
+// Copyright The GNU Toolchain Authors.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// Under Section 7 of GPL version 3, you are granted additional
+// permissions described in the GCC Runtime Library Exception, version
+// 3.1, as published by the Free Software Foundation.
+
+// You should have received a copy of the GNU General Public License and
+// a copy of the GCC Runtime Library Exception along with this program;
+// see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
+// <http://www.gnu.org/licenses/>.
+
+/** @file include/bits/chrono_io.h
+ *  This is an internal header file, included by other library headers.
+ *  Do not attempt to use it directly. @headername{chrono}
+ */
+
+#ifndef _GLIBCXX_CHRONO_IO_H
+#define _GLIBCXX_CHRONO_IO_H 1
+
+#pragma GCC system_header
+
+#if __cplusplus >= 202002L
+
+#include <sstream> // ostringstream
+#include <iomanip> // setw, setfill
+#include <format>
+
+#include <bits/charconv.h>
+
+namespace std _GLIBCXX_VISIBILITY(default)
+{
+_GLIBCXX_BEGIN_NAMESPACE_VERSION
+
+namespace chrono
+{
+/// @addtogroup chrono
+/// @{
+
+/// @cond undocumented
+namespace __detail
+{
+  // STATICALLY-WIDEN, see C++20 [time.general]
+  // It doesn't matter for format strings (which can only be char or wchar_t)
+  // but this returns the narrow string for anything that isn't wchar_t. This
+  // is done because const char* can be inserted into any ostream type, and
+  // will be widened at runtime if necessary.
+  template<typename _CharT>
+    consteval auto
+    _Widen(const char* __narrow, const wchar_t* __wide)
+    {
+      if constexpr (is_same_v<_CharT, wchar_t>)
+	return __wide;
+      else
+	return __narrow;
+    }
+#define _GLIBCXX_WIDEN_(C, S) ::std::chrono::__detail::_Widen<C>(S, L##S)
+#define _GLIBCXX_WIDEN(S) _GLIBCXX_WIDEN_(_CharT, S)
+
+
+  // Write an arbitrary duration suffix into the buffer.
+  template<typename _Period>
+    constexpr const char*
+    __units_suffix_misc(char* __buf, size_t /* TODO check length? */) noexcept
+    {
+      namespace __tc = std::__detail;
+      char* __p = __buf;
+      __p[0] = '[';
+      unsigned __nlen = __tc::__to_chars_len((uintmax_t)_Period::num);
+      __tc::__to_chars_10_impl(__p + 1, __nlen, (uintmax_t)_Period::num);
+      __p += 1 + __nlen;
+      if constexpr (_Period::den != 1)
+	{
+	  __p[0] = '/';
+	  unsigned __dlen = __tc::__to_chars_len((uintmax_t)_Period::den);
+	  __tc::__to_chars_10_impl(__p + 1, __dlen, (uintmax_t)_Period::den);
+	  __p += 1 + __dlen;
+	}
+      __p[0] = ']';
+      __p[1] = 's';
+      __p[2] = '\0';
+      return __buf;
+    }
+
+  template<typename _Period, typename _CharT>
+    constexpr auto
+    __units_suffix(char* __buf, size_t __n) noexcept
+    {
+      // The standard say these are all narrow strings, which would need to
+      // be widened at run-time when inserted into a wide stream. We use
+      // STATICALLY-WIDEN to widen at compile-time.
+#define _GLIBCXX_UNITS_SUFFIX(period, suffix) \
+    if constexpr (is_same_v<_Period, period>) \
+      return _GLIBCXX_WIDEN(suffix);	      \
+    else
+
+      _GLIBCXX_UNITS_SUFFIX(atto,  "as")
+      _GLIBCXX_UNITS_SUFFIX(femto, "fs")
+      _GLIBCXX_UNITS_SUFFIX(pico,  "ps")
+      _GLIBCXX_UNITS_SUFFIX(nano,  "ns")
+      _GLIBCXX_UNITS_SUFFIX(milli, "ms")
+#if _GLIBCXX_USE_ALT_MICROSECONDS_SUFFIX
+      // Deciding this at compile-time is wrong, maybe use nl_langinfo(CODESET)
+      // to check runtime environment and return u8"\u00b5s", "\xb5s", or "us".
+      _GLIBCXX_UNITS_SUFFIX(micro, "\u00b5s")
+#else
+      _GLIBCXX_UNITS_SUFFIX(micro, "us")
+#endif
+      _GLIBCXX_UNITS_SUFFIX(centi, "cs")
+      _GLIBCXX_UNITS_SUFFIX(deci,  "ds")
+      _GLIBCXX_UNITS_SUFFIX(ratio<1>, "s")
+      _GLIBCXX_UNITS_SUFFIX(deca,  "das")
+      _GLIBCXX_UNITS_SUFFIX(hecto, "hs")
+      _GLIBCXX_UNITS_SUFFIX(kilo,  "ks")
+      _GLIBCXX_UNITS_SUFFIX(mega,  "Ms")
+      _GLIBCXX_UNITS_SUFFIX(giga,  "Gs")
+      _GLIBCXX_UNITS_SUFFIX(tera,  "Ts")
+      _GLIBCXX_UNITS_SUFFIX(tera,  "Ts")
+      _GLIBCXX_UNITS_SUFFIX(peta,  "Ps")
+      _GLIBCXX_UNITS_SUFFIX(exa,   "Es")
+      _GLIBCXX_UNITS_SUFFIX(ratio<60>,    "min")
+      _GLIBCXX_UNITS_SUFFIX(ratio<3600>,  "h")
+      _GLIBCXX_UNITS_SUFFIX(ratio<86400>, "d")
+#undef _GLIBCXX_UNITS_SUFFIX
+      return __detail::__units_suffix_misc<_Period>(__buf, __n);
+    }
+} // namespace __detail
+/// @endcond
+
+  /** Write a `chrono::duration` to an ostream.
+   *
+   * @since C++20
+   */
+  template<typename _CharT, typename _Traits,
+	   typename _Rep, typename _Period>
+    inline basic_ostream<_CharT, _Traits>&
+    operator<<(std::basic_ostream<_CharT, _Traits>& __os,
+	       const duration<_Rep, _Period>& __d)
+    {
+      using period = typename _Period::type;
+      char __buf[sizeof("[/]s") + 2 * numeric_limits<intmax_t>::digits10];
+      std::basic_ostringstream<_CharT, _Traits> __s;
+      __s.flags(__os.flags());
+      __s.imbue(__os.getloc());
+      __s.precision(__os.precision());
+      __s << __d.count();
+      __s << __detail::__units_suffix<period, _CharT>(__buf, sizeof(__buf));
+      __os << std::move(__s).str();
+      return __os;
+    }
+
+/// @cond undocumented
+namespace __detail
+{
+  // An unspecified type returned by `chrono::local_time_format`.
+  template<typename _Duration>
+    struct __local_time_fmt
+    {
+      local_time<_Duration> _M_time;
+      const string* _M_abbrev;
+      const seconds* _M_offset_sec;
+    };
+
+  struct __local_fmt_t;
+}
+/// @endcond
+
+  /** Return an object that asssociates timezone info with a local time.
+   *
+   * A `chrono::local_time` object has no timezone associated with it. This
+   * function creates an object that allows formatting a `local_time` as
+   * though it refers to a timezone with the given abbreviated name and
+   * offset from UTC.
+   *
+   * @since C++20
+   */
+  template<typename _Duration>
+    inline __detail::__local_time_fmt<_Duration>
+    local_time_format(local_time<_Duration> __time,
+		      const string* __abbrev = nullptr,
+		      const seconds* __offset_sec = nullptr)
+    { return {__time, __abbrev, __offset_sec}; }
+
+  /// @}
+} // namespace chrono
+
+/// @cond undocumented
+namespace __format
+{
+  [[noreturn,__gnu__::__always_inline__]]
+  inline void
+  __no_timezone_available()
+  { __throw_format_error("format error: no timezone available for %Z or %z"); }
+
+  [[noreturn,__gnu__::__always_inline__]]
+  inline void
+  __not_valid_for_duration()
+  { __throw_format_error("format error: chrono-format-spec not valid for "
+			 "chrono::duration"); }
+
+  template<typename _CharT>
+    struct _ChronoSpec : _Spec<_CharT>
+    {
+      basic_string_view<_CharT> _M_chrono_specs;
+    };
+
+  // Represents the information provided by a chrono type.
+  // e.g. month_weekday has month and weekday but no year or time of day,
+  // hh_mm_ss has time of day but no date, utc_time is time_point+timezone.
+  enum _ChronoParts {
+    _Year = 1, _Month = 2, _Day = 4, _Weekday = 8, _TimeOfDay = 16,
+    _TimeZone = 32,
+    _Date = _Year | _Month | _Day | _Weekday,
+    _DateTime = _Date | _TimeOfDay,
+  };
+
+  constexpr _ChronoParts
+  operator|(_ChronoParts __x, _ChronoParts __y)
+  { return static_cast<_ChronoParts>((int)__x | (int)__y); }
+
+  template<typename _CharT>
+    struct __formatter_chrono
+    {
+      template<typename _ParseContext>
+	constexpr typename _ParseContext::iterator
+	_M_parse(_ParseContext& __pc, _ChronoParts __parts)
+	{
+	  auto __first = __pc.begin();
+	  const auto __last = __pc.end();
+	  _ChronoSpec<_CharT> __spec{};
+
+	  auto __finalize = [this, &__spec] {
+	    _M_spec = __spec;
+	  };
+
+	  auto __finished = [&] {
+	    if (__first == __last || *__first == '}')
+	      {
+		__finalize();
+		return true;
+	      }
+	    return false;
+	  };
+
+	  if (__finished())
+	    return __first;
+
+	  __first = __spec._M_parse_fill_and_align(__first, __last);
+	  if (__finished())
+	    return __first;
+
+	  __first = __spec._M_parse_width(__first, __last, __pc);
+	  if (__finished())
+	    return __first;
+
+	  __first = __spec._M_parse_precision(__first, __last, __pc);
+	  if (__finished())
+	    return __first;
+
+	  __first = __spec._M_parse_locale(__first, __last);
+	  if (__finished())
+	    return __first;
+
+	  // TODO parse chrono-specs, stop at __last or '}' or '{'
+	  // check each conversion-spec against __parts
+	  // (so fail for %Y if no year in parts)
+	  // save range in __spec._M_chrono_specs
+
+	  const auto __chrono_specs = __first;
+
+	  while (!__finished())
+	    {
+	      if (*__first == '{')
+		__throw_format_error("chrono format error: '{' in chrono-specs");
+	      if (*__first != '%')
+		{
+		  // literal-char
+		  ++__first;
+		  continue;
+		}
+
+	      if (__first == __last)
+		__throw_format_error("chrono format error: unescaped '%' "
+				     " in chrono-specs");
+
+	      if (*__first == '%' || *__first == 'n' || *__first == 't')
+		{
+		  // escaped character
+		  ++__first;
+		  continue;
+		}
+
+	      char __modifier = 0;
+	      if (*__first == 'E' || *__first == 'O')
+		__modifier = *__first++;
+
+	      switch (*__first)
+	      {
+		// Types that do not support a modifier.
+		case 'a':
+		case 'A':
+		case 'b':
+		case 'B':
+		case 'D':
+		case 'F':
+		case 'g':
+		case 'G':
+		case 'h':
+		case 'j':
+		case 'p':
+		case 'q':
+		case 'Q':
+		case 'r':
+		case 'R':
+		case 'T':
+		case 'Z':
+		  if (__modifier)
+		    __modifier = 'x';
+		  break;
+		// Types that do not support the O modifier.
+		case 'c':
+		case 'C':
+		case 'x':
+		case 'X':
+		case 'Y':
+		  if (__modifier == 'O')
+		    __modifier = 'x';
+		  break;
+		// Types that do not support the E modifier.
+		case 'd':
+		case 'e':
+		case 'H':
+		case 'I':
+		case 'm':
+		case 'M':
+		case 'S':
+		case 'u':
+		case 'U':
+		case 'V':
+		case 'w':
+		case 'W':
+		  if (__modifier == 'E')
+		    __modifier = 'x';
+		  break;
+		// Types that support both the E and O modifiers.
+		case 'y':
+		case 'z':
+		  break;
+		default:
+		  __throw_format_error("chrono format error: invalid "
+				       "conversion specifier");
+	      }
+	      if (__modifier == 'x')
+		__throw_format_error("chrono format error: invalid modifier");
+
+	      _ChronoParts __needed{};
+	      switch (*__first)
+	      {
+		case 'a':
+		case 'A':
+		case 'w':
+		  __needed = _Weekday;
+		  break;
+		case 'b':
+		case 'h':
+		case 'B':
+		case 'm':
+		  __needed = _Month;
+		  break;
+		case 'c':
+		  __needed = _DateTime;
+		  break;
+		case 'C':
+		case 'g':
+		case 'y':
+		case 'Y':
+		  __needed = _Year;
+		  break;
+		case 'd':
+		case 'e':
+		  // TODO day of the month
+		  break;
+		case 'D':
+		case 'F':
+		case 'G':
+		case 'u':
+		case 'x':
+		  __needed = _Date;
+		  break;
+		case 'H':
+		case 'I':
+		case 'M':
+		case 'p':
+		case 'r':
+		case 'R':
+		case 'S':
+		case 'T':
+		case 'X':
+		  __needed = _TimeOfDay;
+		  break;
+		case 'j':
+		case 'q':
+		case 'Q':
+		  // TODO needs special handling
+		  break;
+		case 'U':
+		case 'V':
+		case 'W':
+		  __needed = _Month | _Day;
+		  break;
+		case 'z':
+		case 'Z':
+		  __needed = _TimeZone;
+		  break;
+	      }
+	      if ((__needed & __parts) != __needed)
+		__throw_format_error("chrono format error: format argument "
+				     "does not contain the information "
+				     "requested by the chrono-specs");
+	      ++__first;
+	    }
+	  size_t __n = __first - __chrono_specs;
+	  _M_spec._M_chrono_specs = {__chrono_specs, __n};
+	  return __first;
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_format(const _Tp& __t, _FormatContext& __ctx) const
+	{
+	  basic_ostringstream<_CharT> __os;
+	  if (!_M_spec._M_localized)
+	    __os.imbue(std::locale::classic());
+	  else
+	    __os.imbue(__ctx.locale());
+
+	  if constexpr (chrono::__is_duration_v<_Tp>
+			  ||__is_specialization_of<_Tp, chrono::hh_mm_ss>)
+	    {
+	      if (__t < __t.zero())
+		{
+		  __os << _S_plus_minus[1];
+		  __t = chrono::abs(__t);
+		}
+	    }
+	  else if constexpr (__is_specialization_of<_Tp, chrono::hh_mm_ss>)
+
+	  if (_M_spec._M_chrono_specs.empty())
+	    {
+	      // TODO write to stream
+	    }
+	  else
+	    {
+	      // TODO
+	    }
+	  return __ctx.out();
+	}
+
+    private:
+      static constexpr const _CharT* _S_chars=_GLIBCXX_WIDEN("0123456789+-:/");
+      static constexpr const _CharT* _S_plus_minus = _S_chars + 10;
+      static constexpr _CharT _S_colon = _S_chars[12];
+      static constexpr _CharT _S_slash = _S_chars[13];
+
+
+      template<typename _Tp>
+	static auto
+	_S_time_since_midnight(const _Tp& __t)
+	{
+	  using ::std::chrono::__detail::__local_time_fmt;
+	  if constexpr (chrono::__is_duration_v<_Tp>)
+	    return __t;
+	  else if constexpr (chrono::__is_time_point_v<_Tp>)
+	    return __t - chrono::floor<chrono::days>(__t);
+	  else if constexpr (__is_specialization_of<_Tp, __local_time_fmt>)
+	    return __t._M_time - chrono::floor<chrono::days>(__t._M_time);
+	}
+
+      template<typename _Tp>
+	static auto
+	_S_ymd_hms(const _Tp& __t)
+	{
+	  if constexpr (chrono::__is_duration_v<_Tp>)
+	    __not_valid_for_duration();
+	  // TODO: handle __local_time_fmt?
+
+	  const auto __days = chrono::floor<chrono::days>(__t);
+	  const auto __ymd = chrono::year_month_day{__days};
+	  const auto __hms = chrono::hh_mm_ss{__t - __days};
+	  return pair(__ymd, __hms);
+	}
+
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_a_A(const _Tp& __t, typename _FormatContext::iterator __out,
+	       _FormatContext& __ctx, bool __full) const
+	{
+	  if constexpr (chrono::__is_duration_v<_Tp>)
+	    __not_valid_for_duration();
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_b_B(const _Tp& __t, typename _FormatContext::iterator __out,
+	       _FormatContext& __ctx, bool __full) const
+	{
+	  if constexpr (chrono::__is_duration_v<_Tp>)
+	    __not_valid_for_duration();
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_c(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx, bool __mod = false) const
+	{
+	  if constexpr (chrono::__is_duration_v<_Tp>)
+	    __not_valid_for_duration();
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_C(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx, bool __mod = false) const
+	{
+	  if constexpr (chrono::__is_duration_v<_Tp>)
+	    __not_valid_for_duration();
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_d(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx, bool __mod = false) const
+	{
+	  if constexpr (chrono::__is_duration_v<_Tp>)
+	    __not_valid_for_duration();
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_D(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx) const
+	{
+	  __out = _M_m(__t, std::move(__out), __ctx);
+	  __format::__write(std::move(__out), _S_slash);
+	  __out = _M_d(__t, std::move(__out), __ctx);
+	  __format::__write(std::move(__out), _S_slash);
+	  __out = _M_y(__t, std::move(__out), __ctx);
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_e(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx, bool __mod = false) const
+	{
+	  if constexpr (chrono::__is_duration_v<_Tp>)
+	    __not_valid_for_duration();
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_F(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx) const
+	{
+	  __out = _M_Y(__t, std::move(__out), __ctx);
+	  __format::__write(std::move(__out), _S_plus_minus[1]);
+	  __out = _M_m(__t, std::move(__out), __ctx);
+	  __format::__write(std::move(__out), _S_plus_minus[1]);
+	  __out = _M_d(__t, std::move(__out), __ctx);
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_g_G(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx, bool __full) const
+	{
+	  if constexpr (chrono::__is_duration_v<_Tp>)
+	    __not_valid_for_duration();
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_H(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx, bool __mod = false) const
+	{
+	  auto __x = _S_time_since_midnight(__t);
+	  if (__mod)
+	    {
+	      // TODO: locale's alternative representation
+	    }
+	  else
+	    {
+	    }
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_I(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx, bool __mod = false) const
+	{
+	  auto __x = _S_time_since_midnight(__t);
+	  auto __h = chrono::duration_cast<chrono::hours>(__x);
+	  if (__mod)
+	    {
+	      // TODO: locale's alternative representation
+	    }
+	  else
+	    {
+	      _CharT __buf[2];
+	    }
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_j(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx) const
+	{
+	  int __d;
+	  if constexpr (chrono::__is_duration_v<_Tp>)
+	    __d = chrono::duration_cast<chrono::days>(__t).count();
+	  else
+	    {
+	    }
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_m(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx, bool __mod = false) const
+	{
+	  if constexpr (chrono::__is_duration_v<_Tp>)
+	    __not_valid_for_duration();
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_M(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx, bool __mod = false) const
+	{
+	  if constexpr (chrono::__is_duration_v<_Tp>)
+	    __not_valid_for_duration();
+	}
+
+      // %n handled in _M_format
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_p(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx) const
+	{
+	  if constexpr (chrono::__is_duration_v<_Tp>)
+	    __not_valid_for_duration();
+	}
+
+      // %q and %Q handled in _M_format
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_r(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx) const
+	{
+	  if constexpr (chrono::__is_duration_v<_Tp>)
+	    {
+	    }
+	  else
+	    {
+	    }
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_R(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx) const
+	{
+	  __out = _M_H(__t, std::move(__out), __ctx);
+	  __format::__write(std::move(__out), _S_colon);
+	  return _M_M(__t, std::move(__out), __ctx);
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_S(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx, bool __mod = false) const
+	{
+	  auto __x = _S_time_since_midnight(__t);
+	  using _Xrep = typename decltype(__x)::rep;
+	  if constexpr (chrono::treat_as_floating_point_v<_Xrep>)
+	    {
+	      // TODO: locale's alternative rep
+	    }
+	  else
+	    {
+	      auto __s = chrono::duration_cast<chrono::seconds>(__x).count();
+	      __s %= 60;
+	      _CharT __buf[2];
+	      __buf[0] = _S_chars[__s / 10];
+	      __buf[1] = _S_chars[__s % 10];
+	      return __format::__write(std::move(__out), {__buf, 2});
+	    }
+	}
+
+      // %t handled in _M_format
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_T(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx) const
+	{
+	  __out = _M_H(__t, std::move(__out), __ctx);
+	  __format::__write(std::move(__out), _S_colon);
+	  __out = _M_M(__t, std::move(__out), __ctx);
+	  __format::__write(std::move(__out), _S_colon);
+	  return _M_S(__t, std::move(__out), __ctx);
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_u(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx, bool __mod = false) const
+	{
+	  // ISO weekday as a decimal number (1-7), where Monday is 1.
+	  if (__mod)
+	    {
+	      // TODO: locale's alt rep
+	    }
+	  else
+	    {
+	      // TODO
+	    }
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_U(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx, bool __mod = false) const
+	{
+	  // Week number of the year as a decimal number.
+	  if (__mod)
+	    {
+	      // TODO: locale's alt rep
+	    }
+	  else
+	    {
+	      // TODO
+	    }
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_V(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx, bool __mod = false) const
+	{
+	  // ISO week-based week number as a decimal number.
+	  if (__mod)
+	    {
+	      // TODO: locale's alt rep
+	    }
+	  else
+	    {
+	      // TODO
+	    }
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_w(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx, bool __mod = false) const
+	{
+	  // Weekday as a decimal number (0-6), where Sunday is 0.
+	  if (__mod)
+	    {
+	      // TODO: locale's alt rep
+	    }
+	  else
+	    {
+	      // TODO
+	    }
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_W(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx, bool __mod = false) const
+	{
+	  // Week number of the year as a decimal number.
+	  if (__mod)
+	    {
+	      // TODO: locale's alt rep
+	    }
+	  else
+	    {
+	      // TODO
+	    }
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_x(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx, bool __mod = false) const
+	{
+	  // Locale's date rep
+	  if (__mod)
+	    {
+	      // TODO: locale's alt rep
+	    }
+	  else
+	    {
+	      // TODO
+	    }
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_X(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx, bool __mod = false) const
+	{
+	  // Locale's time rep
+	  if (__mod)
+	    {
+	      // TODO: locale's alt rep
+	    }
+	  else
+	    {
+	      // TODO
+	    }
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_y(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx, char __mod = 0) const
+	{
+	  // Last two decimal digits of the year.
+	  if constexpr (chrono::__is_duration_v<_Tp>)
+	    __not_valid_for_duration();
+	  if (__mod == 'O')
+	    {
+	      // TODO: locale's alt rep
+	    }
+	  else if (__mod == 'E')
+	    {
+	      // TODO: locale's alt rep of offset from %EC
+	    }
+	  else
+	    {
+	      // TODO
+	    }
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_Y(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx, bool __mod = false) const
+	{
+	  // Year as a decimal number.
+	  if constexpr (chrono::__is_duration_v<_Tp>)
+	    __not_valid_for_duration();
+	  if (__mod)
+	    {
+	      // TODO: locale's alt full year rep
+	    }
+	  else
+	    {
+	      // TODO
+	    }
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_z(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx, bool __mod = false) const
+	{
+	  using ::std::chrono::__detail::__local_time_fmt;
+
+	  int __neg = 0, __hh = 0, __mm = 0;
+	  if constexpr (chrono::__is_time_point_v<_Tp>)
+	    {
+	      using _Clock = typename _Tp::clock;
+	      if constexpr (is_same_v<_Clock, chrono::system_clock>
+			      || is_same_v<_Clock, chrono::utc_clock>
+			      || is_same_v<_Clock, chrono::tai_clock>
+			      || is_same_v<_Clock, chrono::gps_clock>)
+		__neg = __hh = __mm = 0;
+	      else
+		__no_timezone_available();
+	    }
+	  else if constexpr (__is_specialization_of<_Tp, __local_time_fmt>)
+	    {
+	      if (!__t._M_offset_sec)
+		__no_timezone_available();
+
+	      chrono::seconds __s = chrono::abs(*__t._M_offset_sec);
+	      auto __hours = chrono::duration_cast<chrono::hours>(__s);
+	      auto __minutes = chrono::duration_cast<chrono::minutes>(__s);
+	      __mm = (__minutes - __hours).count();
+	      __hh = __hours.count();
+	    }
+	  else
+	    __no_timezone_available();
+
+	  _CharT __buf[6] = {
+	    _S_plus_minus[__neg], _S_chars[__hh / 10], _S_chars[__hh % 10]
+	  };
+	  __buf[3] = _S_colon;
+	  __buf[3 + __mod] = _S_chars[__mm / 10];
+	  __buf[4 + __mod] = _S_chars[__mm % 10];
+	  return __format::__write(std::move(__out), {__buf, 5u + __mod});
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_Z(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx) const
+	{
+	  using ::std::chrono::__detail::__local_time_fmt;
+
+	  basic_string_view<_CharT> __abbrev{nullptr, 0};
+	  if constexpr (chrono::__is_time_point_v<_Tp>)
+	    {
+	      const _CharT* __utctaigps = _GLIBCXX_WIDEN("UTCTAIGPS");
+
+	      using _Clock = typename _Tp::clock;
+	      if constexpr (is_same_v<_Clock, chrono::system_clock>
+			      || is_same_v<_Clock, chrono::utc_clock>)
+		__abbrev = {__utctaigps, 3};
+	      else if constexpr (is_same_v<_Clock, chrono::tai_clock>)
+		__abbrev = {__utctaigps + 3, 3};
+	      else if constexpr (is_same_v<_Clock, chrono::gps_clock>)
+		__abbrev = {__utctaigps + 6, 3};
+	    }
+	  else if constexpr (__is_specialization_of<_Tp, __local_time_fmt>)
+	    {
+	      if (__t._M_abbrev)
+		__abbrev = __t._M_abbrev; // TODO: widen
+	    }
+
+	  if (!__abbrev.data())
+	    __no_timezone_available();
+
+	  return __format::__write(std::move(__out), __abbrev);
+	}
+
+      // %% handled in _M_format
+
+      _ChronoSpec<_CharT> _M_spec;
+    };
+
+} // namespace __format
+/// @endcond
+
+  template<typename _Rep, typename _Period, typename _CharT>
+    struct formatter<chrono::duration<_Rep, _Period>, _CharT>
+    {
+      constexpr typename basic_format_parse_context<_CharT>::iterator
+      parse(basic_format_parse_context<_CharT>& __pc)
+      {
+	auto __it = _M_f._M_parse(__pc, __format::_TimeOfDay);
+	if constexpr (!is_floating_point_v<_Rep>)
+	  if (_M_f._M_spec._M_prec_kind != __format::_WP_none)
+	    __throw_format_error("format error: invalid precision for duration");
+	return __it;
+      }
+
+      template<typename _Out>
+	typename basic_format_context<_Out, _CharT>::iterator
+	format(const chrono::duration<_Rep, _Period>& __d,
+	       basic_format_context<_Out, _CharT>& __fc) const
+	{
+	  return _M_f._M_format(__d, __fc);
+	}
+
+    private:
+      __format::__formatter_chrono<_CharT> _M_f;
+    };
+
+  template<typename _CharT>
+    struct formatter<chrono::day, _CharT>;
+  template<typename _CharT>
+    struct formatter<chrono::month, _CharT>;
+  template<typename _CharT>
+    struct formatter<chrono::year, _CharT>;
+
+  template<typename _CharT>
+    struct formatter<chrono::weekday, _CharT>
+    {
+
+
+
+    private:
+      __format::__formatter_chrono<_CharT> _M_f;
+    };
+
+
+  // TODO define these
+  template<typename _CharT>
+    struct formatter<chrono::weekday_indexed, _CharT>;
+  template<typename _CharT>
+    struct formatter<chrono::weekday_last, _CharT>;
+  template<typename _CharT>
+    struct formatter<chrono::month_day, _CharT>;
+  template<typename _CharT>
+    struct formatter<chrono::month_day_last, _CharT>;
+  template<typename _CharT>
+    struct formatter<chrono::month_weekday, _CharT>;
+  template<typename _CharT>
+    struct formatter<chrono::month_weekday_last, _CharT>;
+  template<typename _CharT>
+    struct formatter<chrono::year_month, _CharT>;
+  template<typename _CharT>
+    struct formatter<chrono::year_month_day, _CharT>;
+  template<typename _CharT>
+    struct formatter<chrono::year_month_day_last, _CharT>;
+  template<typename _CharT>
+    struct formatter<chrono::year_month_weekday, _CharT>;
+  template<typename _CharT>
+    struct formatter<chrono::year_month_weekday_last, _CharT>;
+  template<typename _Rep, typename _Period, typename _CharT>
+    struct formatter<chrono::hh_mm_ss<chrono::duration<_Rep, _Period>>, _CharT>;
+  template<typename _CharT>
+    struct formatter<chrono::sys_info, _CharT>;
+  template<typename _CharT>
+    struct formatter<chrono::local_info, _CharT>;
+
+  template<typename _Duration, typename _CharT>
+    struct formatter<chrono::sys_time<_Duration>, _CharT>
+    {
+      template<typename _ParseContext>
+	constexpr typename _ParseContext::iterator
+	parse(_ParseContext& __pc)
+	{
+	  return _M_f._M_parse(__pc, __format::_DateTime);
+	}
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::sys_time<_Duration>& __t,
+	       _FormatContext& __pc) const
+	{
+	  return _M_f._M_format(__t, __pc);
+	}
+
+    private:
+      __format::__formatter_chrono<_CharT> _M_f;
+    };
+
+  template<typename _Duration, typename _CharT>
+    struct formatter<chrono::utc_time<_Duration>, _CharT>
+    : __format::__formatter_chrono<_CharT>
+    {
+      template<typename _ParseContext>
+	constexpr typename _ParseContext::iterator
+	parse(_ParseContext& __pc)
+	{
+	  return _M_f._M_parse(__pc, __format::_DateTime);
+	}
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::utc_time<_Duration>& __t,
+	       _FormatContext& __pc) const
+	{
+	  return _M_f._M_format(__t, __pc);
+	}
+
+    private:
+      __format::__formatter_chrono<_CharT> _M_f;
+    };
+
+  template<typename _Duration, typename _CharT>
+    struct formatter<chrono::tai_time<_Duration>, _CharT>
+    : __format::__formatter_chrono<_CharT>
+    {
+      template<typename _ParseContext>
+	constexpr typename _ParseContext::iterator
+	parse(_ParseContext& __pc)
+	{
+	  return _M_f._M_parse(__pc, __format::_DateTime);
+	}
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::tai_time<_Duration>& __t,
+	       _FormatContext& __pc) const
+	{
+	  return _M_f._M_format(__t, __pc);
+	}
+
+    private:
+      __format::__formatter_chrono<_CharT> _M_f;
+    };
+
+  template<typename _Duration, typename _CharT>
+    struct formatter<chrono::gps_time<_Duration>, _CharT>
+    : __format::__formatter_chrono<_CharT>
+    {
+      template<typename _ParseContext>
+	constexpr typename _ParseContext::iterator
+	parse(_ParseContext& __pc)
+	{
+	  return _M_f._M_parse(__pc, __format::_DateTime);
+	}
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::gps_time<_Duration>& __t,
+	       _FormatContext& __pc) const
+	{
+	  return _M_f._M_format(__t, __pc);
+	}
+
+    private:
+      __format::__formatter_chrono<_CharT> _M_f;
+    };
+
+  template<typename _Duration, typename _CharT>
+    struct formatter<chrono::file_time<_Duration>, _CharT>
+    {
+      template<typename _ParseContext>
+	constexpr typename _ParseContext::iterator
+	parse(_ParseContext& __pc)
+	{
+	  return _M_f._M_parse(__pc, __format::_DateTime);
+	}
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::file_time<_Duration>& __t,
+	       _FormatContext& __ctx) const
+	{
+	  using namespace chrono;
+	  return _M_f._M_format(clock_cast<system_clock>(__t), __ctx);
+	}
+
+    private:
+      __format::__formatter_chrono<_CharT> _M_f;
+    };
+
+  template<typename _Duration, typename _CharT>
+    struct formatter<chrono::local_time<_Duration>, _CharT>
+    {
+      template<typename _ParseContext>
+	constexpr typename _ParseContext::iterator
+	parse(_ParseContext& __pc)
+	{
+	  return _M_f._M_parse(__pc, __format::_Date|__format::_TimeOfDay);
+	}
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::local_time<_Duration>& __t,
+	       _FormatContext& __ctx) const
+	{
+	  return _M_f._M_format(__t, __ctx);
+	}
+
+    private:
+      __format::__formatter_chrono<_CharT> _M_f;
+    };
+
+  template<typename _Duration, typename _CharT>
+    struct formatter<chrono::__detail::__local_time_fmt<_Duration>, _CharT>
+    {
+      template<typename _ParseContext>
+	constexpr typename _ParseContext::iterator
+	parse(_ParseContext& __pc)
+	{
+	  return _M_f._M_parse(__pc, __format::_DateTime);
+	}
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::__detail::__local_time_fmt<_Duration>& __t,
+	       _FormatContext& __ctx) const
+	{
+	  return _M_f._M_format(__t, __ctx);
+	}
+
+    private:
+      __format::__formatter_chrono<_CharT> _M_f;
+    };
+
+  template<typename _Duration, typename _TimeZonePtr, typename _CharT>
+    struct formatter<chrono::zoned_time<_Duration, _TimeZonePtr>, _CharT>
+    : formatter<chrono::__detail::__local_time_fmt<_Duration>, _CharT>
+    {
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::zoned_time<_Duration, _TimeZonePtr>& __tp,
+	       _FormatContext& __ctx) const
+	{
+	  using chrono::__detail::__local_time_fmt;
+	  using _Base = formatter<__local_time_fmt<_Duration>, _CharT>;
+	  const chrono::sys_info __info = __tp.get_info();
+	  return _Base::format({__tp.get_local_time(), &__info.abbrev,
+				&__info.offset}, __ctx);
+	}
+    };
+
+namespace chrono
+{
+/// @addtogroup chrono
+/// @{
+
+  // TODO: from_stream for duration
+#if 0
+  template<typename _CharT, typename _Traits, typename _Rep, typename _Period,
+	   typename _Alloc = allocator<_CharT>>
+    basic_istream<_CharT, _Traits>&
+    from_stream(basic_istream<_CharT, _Traits>& __is, const _CharT* __fmt,
+		duration<_Rep, _Period>& __d,
+		basic_string<_CharT, _Traits, _Alloc>* __abbrev = nullptr,
+		minutes* __offset = nullptr)
+    {
+    }
+#endif
+
+  template<typename _CharT, typename _Traits>
+    inline basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os, const day& __d)
+    {
+      using _Ctx = __conditional_t<is_same_v<_CharT, char>,
+				   format_context, wformat_context>;
+      using _Str = basic_string_view<_CharT>;
+      _Str __s = _GLIBCXX_WIDEN("{:%02d} is not a valid day");
+      if (__d.ok())
+	__s = __s.substr(0, 7);
+      __os << std::vformat(__s, make_format_args<_Ctx>((unsigned)__d));
+      return __os;
+    }
+
+  // TODO from_stream for day
+
+  template<typename _CharT, typename _Traits>
+    inline basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os, const month& __m)
+    {
+      using _Ctx = __conditional_t<is_same_v<_CharT, char>,
+				   format_context, wformat_context>;
+      using _Str = basic_string_view<_CharT>;
+      _Str __s = _GLIBCXX_WIDEN("{:L%b}{} is not a valid month");
+      if (__m.ok())
+	__os << std::vformat(__os.getloc(), __s.substr(0, 6),
+			     make_format_args<_Ctx>(__m));
+      else
+	__os << std::vformat(__s.substr(6),
+			     make_format_args<_Ctx>((unsigned)__m));
+      return __os;
+    }
+
+  // TODO from_stream for month
+
+  template<typename _CharT, typename _Traits>
+    inline basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os, const year& __y)
+    {
+      using _Ctx = __conditional_t<is_same_v<_CharT, char>,
+				   format_context, wformat_context>;
+      using _Str = basic_string_view<_CharT>;
+      _Str __s = _GLIBCXX_WIDEN("{:%04d} is not a valid year");
+      if (__y.ok())
+	__s = __s.substr(0, 7);
+      __os << std::vformat(__s, make_format_args<_Ctx>((int)__y));
+      return __os;
+    }
+
+  // TODO from_stream for year
+
+  template<typename _CharT, typename _Traits>
+    inline basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os, const weekday& __wd)
+    {
+      using _Ctx = __conditional_t<is_same_v<_CharT, char>,
+				   format_context, wformat_context>;
+      using _Str = basic_string_view<_CharT>;
+      _Str __s = _GLIBCXX_WIDEN("{:L%a}{} is not a valid weekday");
+      if (__wd.ok())
+	__os << std::vformat(__os.getloc(), __s.substr(0, 6),
+			     make_format_args<_Ctx>(__wd));
+      else
+	__os << std::vformat(__s.substr(6),
+			     make_format_args<_Ctx>((unsigned)__wd));
+      return __os;
+    }
+
+  // TODO from_stream for weekday
+
+  template<typename _CharT, typename _Traits>
+    inline basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os,
+	       const weekday_indexed& __wdi)
+    {
+      // The standard says to format wdi.weekday() and wdi.index() using
+      // either "{:L}[{}]" or "{:L}[{} is not a valid index]". The {:L} spec
+      // means to format the weekday using ostringstream, so just do that.
+      basic_stringstream<_CharT> __os2;
+      __os2.imbue(__os.getloc);
+      __os2 << __wdi.weekday();
+      const auto __i = __wdi.index();
+      if constexpr (is_same_v<_CharT, char>)
+	__os2 << std::format("[{}", __i);
+      else
+	__os2 << std::format(L"[{}", __i);
+      basic_string_view<_CharT> __s = _GLIBCXX_WIDEN(" is not a valid index]");
+      if (__i >= 1 && __i <= 5)
+	__os2 << __s.back();
+      else
+	__os2 << __s;
+      __os << __os2.view();
+      return __os;
+    }
+
+  template<typename _CharT, typename _Traits>
+    inline basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os,
+	       const weekday_last& __wdl)
+    {
+      // As above, just write straight to a stringstream, as if by "{:L}[last]"
+      basic_stringstream<_CharT> __os2;
+      __os2.imbue(__os.getloc);
+      __os2 << __wdl.weekday() << _GLIBCXX_WIDEN("[last]");
+      __os << __os2.view();
+      return __os;
+    }
+
+  template<typename _CharT, typename _Traits>
+    inline basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os, const month_day& __md)
+    {
+      // As above, just write straight to a stringstream, as if by "{:L}/{}"
+      basic_stringstream<_CharT> __os2;
+      __os2.imbue(__os.getloc);
+      __os2 << __md.month();
+      if constexpr (is_same_v<_CharT, char>)
+	__os2 << '/';
+      else
+	__os2 << L'/';
+      __os2 << __md.day();
+      __os << __os2.view();
+      return __os;
+    }
+
+  // TODO from_stream for month_day
+
+  template<typename _CharT, typename _Traits>
+    inline basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os,
+	       const month_day_last& __mdl)
+    {
+      // As above, just write straight to a stringstream, as if by "{:L}/last"
+      basic_stringstream<_CharT> __os2;
+      __os2.imbue(__os.getloc);
+      __os2 << __mdl.month();
+      if constexpr (is_same_v<_CharT, char>)
+	__os2 << "/last";
+      else
+	__os2 << L"/last";
+      __os << __os2.view();
+      return __os;
+    }
+
+  template<typename _CharT, typename _Traits>
+    inline basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os,
+	       const month_weekday& __mwd)
+    {
+      // As above, just write straight to a stringstream, as if by "{:L}/{:L}"
+      basic_stringstream<_CharT> __os2;
+      __os2.imbue(__os.getloc);
+      __os2 << __mwd.month();
+      if constexpr (is_same_v<_CharT, char>)
+	__os2 << '/';
+      else
+	__os2 << L'/';
+      __os2 << __mwd.weekday_indexed();
+      __os << __os2.view();
+      return __os;
+    }
+
+  template<typename _CharT, typename _Traits>
+    inline basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os,
+	       const month_weekday_last& __mwdl)
+    {
+      // As above, just write straight to a stringstream, as if by "{:L}/{:L}"
+      basic_stringstream<_CharT> __os2;
+      __os2.imbue(__os.getloc);
+      __os2 << __mwdl.month();
+      if constexpr (is_same_v<_CharT, char>)
+	__os2 << '/';
+      else
+	__os2 << L'/';
+      __os2 << __mwdl.weekday_last();
+      __os << __os2.view();
+      return __os;
+    }
+
+  template<typename _CharT, typename _Traits>
+    inline basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os, const year_month& __ym)
+    {
+      // As above, just write straight to a stringstream, as if by "{}/{:L}"
+      basic_stringstream<_CharT> __os2;
+      __os2.imbue(__os.getloc);
+      __os2 << __ym.year();
+      if constexpr (is_same_v<_CharT, char>)
+	__os2 << '/';
+      else
+	__os2 << L'/';
+      __os2 << __ym.month();
+      __os << __os2.view();
+      return __os;
+    }
+
+  // TODO from_stream for year_month
+
+  template<typename _CharT, typename _Traits>
+    inline basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os,
+	       const year_month_day& __ymd)
+    {
+      using _Ctx = __conditional_t<is_same_v<_CharT, char>,
+				   format_context, wformat_context>;
+      using _Str = basic_string_view<_CharT>;
+      _Str __s = _GLIBCXX_WIDEN("{:%F}{} is not a valid date");
+      __os << std::vformat(__ymd.ok() ? __s.substr(0, 5) : __s.substr(5),
+			   make_format_args<_Ctx>(__ymd));
+      return __os;
+    }
+
+  // TODO from_stream for year_month_day
+
+  template<typename _CharT, typename _Traits>
+    inline basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os,
+	       const year_month_day_last& __ymdl)
+    {
+      // As above, just write straight to a stringstream, as if by "{}/{:L}"
+      basic_stringstream<_CharT> __os2;
+      __os2.imbue(__os.getloc);
+      __os2 << __ymdl.year();
+      if constexpr (is_same_v<_CharT, char>)
+	__os2 << '/';
+      else
+	__os2 << L'/';
+      __os2 << __ymdl.month_day_last();
+      __os << __os2.view();
+      return __os;
+    }
+
+  template<typename _CharT, typename _Traits>
+    inline basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os,
+	       const year_month_weekday& __ymwd)
+    {
+      // As above, just write straight to a stringstream, as if by
+      // "{}/{:L}/{:L}"
+      basic_stringstream<_CharT> __os2;
+      __os2.imbue(__os.getloc);
+      _CharT __slash;
+      if constexpr (is_same_v<_CharT, char>)
+	__slash = '/';
+      else
+	__slash = L'/';
+      __os2 << __ymwd.year() << __slash << __ymwd.month() << __slash
+	    << __ymwd.weekday_indexed();
+      __os << __os2.view();
+      return __os;
+    }
+
+  template<typename _CharT, typename _Traits>
+    inline basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os,
+	       const year_month_weekday_last& __ymwdl)
+    {
+      // As above, just write straight to a stringstream, as if by
+      // "{}/{:L}/{:L}"
+      basic_stringstream<_CharT> __os2;
+      __os2.imbue(__os.getloc);
+      _CharT __slash;
+      if constexpr (is_same_v<_CharT, char>)
+	__slash = '/';
+      else
+	__slash = L'/';
+      __os2 << __ymwdl.year() << __slash << __ymwdl.month() << __slash
+	    << __ymwdl.weekday_last();
+      __os << __os2.view();
+      return __os;
+    }
+
+  template<typename _CharT, typename _Traits, typename _Duration>
+    inline basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os,
+	       const hh_mm_ss<_Duration>& __hms)
+    {
+      return __os << format(__os.getloc(), _GLIBCXX_WIDEN("{:L%T}"), __hms);
+    }
+
+  template<typename _CharT, typename _Traits>
+    basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os, const sys_info& __i)
+    {
+      // Streams out the sys_info object r in an unspecified format.
+      __os << '[' << __i.begin.time_since_epoch().count()
+	   << ' ' << __i.end.time_since_epoch().count()
+	   << ' ' << __i.offset.count()
+	   << ' ' << __i.save.count()
+	   << ' ' << __i.abbrev
+	   << ']';
+      return __os;
+    }
+
+  template<typename _CharT, typename _Traits>
+    basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os, const local_info& __i)
+    {
+      // Streams out the local_info object r in an unspecified format.
+      __os << '[' << __i.result
+	   << ' ' << __i.first
+	   << ' ' << __i.second
+	   << ']';
+      return __os;
+    }
+
+  template<typename _CharT, typename _Traits, typename _Duration,
+	   typename _TimeZonePtr>
+    inline basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os,
+	       const zoned_time<_Duration, _TimeZonePtr>& __t)
+    {
+      __os << format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T %Z}"), __t);
+      return __os;
+    }
+
+
+  template<typename _CharT, typename _Traits, typename _Duration>
+    requires (!treat_as_floating_point_v<typename _Duration::rep>)
+      && ratio_less_v<typename _Duration::period, days::period>
+    inline basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os,
+	       const sys_time<_Duration>& __tp)
+    {
+      __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"), __tp);
+      return __os;
+    }
+
+  template<typename _CharT, typename _Traits, typename _Duration>
+    inline basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os, const sys_days& __dp)
+    {
+      __os << year_month_day{__dp};
+      return __os;
+    }
+
+  // TODO: from_stream for sys_time
+
+  template<typename _CharT, typename _Traits, typename _Duration>
+    inline basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os,
+	       const utc_time<_Duration>& __t)
+    {
+      __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"), __t);
+      return __os;
+    }
+
+  // TODO: from_stream for utc_time
+
+  template<typename _CharT, typename _Traits, typename _Duration>
+    inline basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os,
+	       const tai_time<_Duration>& __t)
+    {
+      __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"), __t);
+      return __os;
+    }
+
+  // TODO: from_stream for tai_time
+
+  template<typename _CharT, typename _Traits, typename _Duration>
+    inline basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os,
+	       const gps_time<_Duration>& __t)
+    {
+      __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"), __t);
+      return __os;
+    }
+
+  // TODO: from_stream for gps_time
+
+
+  template<typename _CharT, typename _Traits, typename _Duration>
+    inline basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os,
+	       const file_time<_Duration>& __t)
+    {
+      __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"), __t);
+      return __os;
+    }
+
+  // TODO: from_stream for file_time
+
+  template<typename _CharT, typename _Traits, typename _Duration>
+    inline basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os,
+	       const local_time<_Duration>& __lt)
+    {
+      __os << sys_time<_Duration>{__lt.time_since_epoch()};
+      return __os;
+    }
+
+  // TODO: from_stream for local_time
+#undef _GLIBCXX_WIDEN
+
+  /// @} group chrono
+} // namespace chrono
+
+_GLIBCXX_END_NAMESPACE_VERSION
+} // namespace std
+
+#endif // C++20
+
+#endif //_GLIBCXX_CHRONO_IO_H
diff --git a/libstdc++-v3/include/std/chrono b/libstdc++-v3/include/std/chrono
index 90b73f8198e..c39161152ce 100644
--- a/libstdc++-v3/include/std/chrono
+++ b/libstdc++-v3/include/std/chrono
@@ -41,11 +41,8 @@
 #include <bits/chrono.h>
 
 #if __cplusplus >= 202002L
-# include <sstream>
 # include <string>
 # include <vector>
-# include <bits/charconv.h> // __to_chars_len, __to_chars_10_impl
-# include <bits/stl_algo.h> // upper_bound TODO: move leap_second_info to .so
 # include <bits/shared_ptr.h>
 # include <bits/unique_ptr.h>
 #endif
@@ -616,8 +613,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
       friend constexpr year_month_day
       operator/(const year_month& __ym, const day& __d) noexcept;
-
-      // TODO: Implement operator<<, to_stream, from_stream.
     };
 
     // MONTH
@@ -740,8 +735,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
       friend constexpr month_weekday_last
       operator/(const weekday_last& __wdl, const month& __m) noexcept;
-
-      // TODO: Implement operator<<, to_stream, from_stream.
     };
 
     inline constexpr month January{1};
@@ -918,8 +911,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
       friend constexpr year_month_weekday_last
       operator/(const month_weekday_last& __mwdl, const year& __y) noexcept;
-
-      // TODO: Implement operator<<, to_stream, from_stream.
     };
 
     // WEEKDAY
@@ -1041,8 +1032,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	auto __n = static_cast<long long>(__x._M_wd) - __y._M_wd;
 	return days{__detail::__modulo(__n, 7)};
       }
-
-      // TODO: operator<<, from_stream.
     };
 
     inline constexpr weekday Sunday{0};
@@ -1099,8 +1088,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
       friend constexpr year_month_weekday
       operator/(const year_month& __ym, const weekday_indexed& __wdi) noexcept;
-
-      // TODO: Implement operator<<.
     };
 
     constexpr weekday_indexed
@@ -1140,8 +1127,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
       friend constexpr year_month_weekday_last
       operator/(const year_month& __ym, const weekday_last& __wdl) noexcept;
-
-      // TODO: Implement operator<<.
     };
 
     constexpr weekday_last
@@ -1213,8 +1198,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
       friend constexpr year_month_day
       operator/(const month_day& __md, int __y) noexcept;
-
-      // TODO: Implement operator<<, from_stream.
     };
 
     // MONTH_DAY_LAST
@@ -1267,8 +1250,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
       friend constexpr year_month_day_last
       operator/(const month_day_last& __mdl, int __y) noexcept;
-
-      // TODO: Implement operator<<.
     };
 
     // MONTH_WEEKDAY
@@ -1328,8 +1309,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
       friend constexpr year_month_weekday
       operator/(const month_weekday& __mwd, int __y) noexcept;
-
-      // TODO: Implement operator<<.
     };
 
     // MONTH_WEEKDAY_LAST
@@ -1390,8 +1369,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
       friend constexpr year_month_weekday_last
       operator/(const month_weekday_last& __mwdl, int __y) noexcept;
-
-      // TODO: Implement operator<<.
     };
 
     // YEAR_MONTH
@@ -1533,8 +1510,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
       friend constexpr year_month_day_last
       operator/(const year_month& __ym, last_spec) noexcept;
-
-      // TODO: Implement operator<<, from_stream.
     };
 
     // YEAR_MONTH_DAY
@@ -1686,8 +1661,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       friend constexpr year_month_day
       operator/(const month_day& __md, int __y) noexcept
       { return chrono::year(__y) / __md; }
-
-      // TODO: Implement operator<<, from_stream.
     };
 
     // Construct from days since 1970/01/01.
@@ -1917,8 +1890,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       friend constexpr year_month_day_last
       operator/(const chrono::month_day_last& __mdl, int __y) noexcept
       { return chrono::year(__y) / __mdl; }
-
-      // TODO: Implement operator<<.
     };
 
     // year_month_day ctor from year_month_day_last
@@ -2107,8 +2078,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       friend constexpr year_month_weekday
       operator/(const month_weekday& __mwd, int __y) noexcept
       { return chrono::year(__y) / __mwd; }
-
-      // TODO: Implement operator<<.
     };
 
     // YEAR_MONTH_WEEKDAY_LAST
@@ -2256,8 +2225,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       friend constexpr year_month_weekday_last
       operator/(const chrono::month_weekday_last& __mwdl, int __y) noexcept
       { return chrono::year(__y) / __mwdl; }
-
-      // TODO: Implement operator<<.
     };
 
     // HH_MM_SS
@@ -2364,8 +2331,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	    return _M_h + _M_m + _M_s + _M_ss;
 	}
 
-	// TODO: Implement operator<<.
-
       private:
 	bool _M_is_neg;
 	chrono::hours _M_h;
@@ -2415,6 +2380,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
     // C++20 [time.zones] Time zones
 
+    class tzdb;
+
     struct sys_info
     {
       sys_seconds begin;
@@ -2473,6 +2440,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       time_zone(time_zone&&) = default;
       time_zone& operator=(time_zone&&) = default;
 
+      ~time_zone();
+
       string_view name() const noexcept { return _M_name; }
 
       template<typename _Duration>
@@ -2504,8 +2473,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       { return __x.name() <=> __y.name(); }
 
     private:
-      string _M_name;
+      friend const tzdb& reload_tzdb();
+
       struct _Impl;
+      explicit time_zone(unique_ptr<_Impl> __p);
+      string _M_name;
       unique_ptr<_Impl> _M_impl;
     };
 
@@ -2532,6 +2504,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
     private:
       friend const tzdb& reload_tzdb();
+
+      explicit time_zone_link(nullptr_t) { }
+
       // TODO unspecified additional constructors
       string _M_name;
       string _M_target;
@@ -2632,9 +2607,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       explicit leap_second(seconds::rep __s) : _M_s(__s) { }
 
       friend const tzdb& reload_tzdb();
-      template<typename _Dur>
+
+      template<typename _Duration>
 	friend leap_second_info
-	get_leap_second_info(const utc_time<_Dur>&);
+	get_leap_second_info(const utc_time<_Duration>&);
+
+      static leap_second_info _S_get_info(const utc_seconds&);
 
       seconds _M_s; // == date().time_since_epoch() * value().count()
     };
@@ -2705,11 +2683,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       private:
 	explicit const_iterator(const shared_ptr<_Node>&) noexcept;
 
+	friend class tzdb_list;
+
 	shared_ptr<_Node> _M_node;
 	void* _M_reserved = nullptr;
       };
 
-      // TODO const tzdb& front() const noexcept;
+      const tzdb& front() const noexcept;
 
       const_iterator erase_after(const_iterator);
 
@@ -2718,9 +2698,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       const_iterator cbegin() const noexcept { return begin(); }
       const_iterator cend() const noexcept { return end(); }
 
+#ifndef _GLIBCXX_DEFINING_TZDB
     private:
+#endif
       constexpr explicit tzdb_list(nullptr_t);
 
+      static string _S_latest_version();
+      shared_ptr<_Node> _S_load(string_view);
+
       friend const tzdb_list& get_tzdb_list();
       friend const tzdb& get_tzdb();
       friend const tzdb& reload_tzdb();
@@ -2729,12 +2714,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       static shared_ptr<_Node> _S_head_owner;
     };
 
-    // TODO
-    // const tzdb_list& get_tzdb_list();
-    // const tzdb& get_tzdb();
+    const tzdb_list& get_tzdb_list();
+    const tzdb& get_tzdb();
 
-    // const tzdb& reload_tzdb();
-    // string remove_version();
+    const tzdb& reload_tzdb();
+    string remote_version();
 
     template<typename _Duration, typename _TimeZonePtr = const time_zone*>
       class zoned_time; // TODO
@@ -2745,52 +2729,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       leap_second_info
       get_leap_second_info(const utc_time<_Duration>& __ut)
       {
-	if constexpr (is_same_v<_Duration, seconds>)
-	  {
-	    // TODO move this function into the library and get leaps from tzdb.
-	    vector<seconds::rep> __leaps
-	    {
-		78796800, // 1 Jul 1972
-		94694400, // 1 Jan 1973
-	       126230400, // 1 Jan 1974
-	       157766400, // 1 Jan 1975
-	       189302400, // 1 Jan 1976
-	       220924800, // 1 Jan 1977
-	       252460800, // 1 Jan 1978
-	       283996800, // 1 Jan 1979
-	       315532800, // 1 Jan 1980
-	       362793600, // 1 Jul 1981
-	       394329600, // 1 Jul 1982
-	       425865600, // 1 Jul 1983
-	       489024000, // 1 Jul 1985
-	       567993600, // 1 Jan 1988
-	       631152000, // 1 Jan 1990
-	       662688000, // 1 Jan 1991
-	       709948800, // 1 Jul 1992
-	       741484800, // 1 Jul 1993
-	       773020800, // 1 Jul 1994
-	       820454400, // 1 Jan 1996
-	       867715200, // 1 Jul 1997
-	       915148800, // 1 Jan 1999
-	      1136073600, // 1 Jan 2006
-	      1230768000, // 1 Jan 2009
-	      1341100800, // 1 Jul 2012
-	      1435708800, // 1 Jul 2015
-	      1483228800, // 1 Jan 2017
-	    };
+	if (__ut < utc_time<_Duration>{}) [[unlikely]]
+	  return {};
 
-	    auto __s = __ut.time_since_epoch().count();
-	    auto __pos = std::upper_bound(__leaps.begin(), __leaps.end(), __s);
-	    return {
-	      __pos != __leaps.begin() && __pos[-1] == __s,
-	      seconds{__pos - __leaps.begin()}
-	    };
-	  }
+	utc_seconds __us;
+	if constexpr (is_same_v<_Duration, seconds>)
+	  __us = __ut;
 	else
-	  {
-	    auto __s = chrono::time_point_cast<seconds>(__ut);
-	    return chrono::get_leap_second_info(__s);
-	  }
+	  __us = chrono::time_point_cast<seconds>(__ut);
+	return leap_second::_S_get_info(__us);
       }
 
     /// @} group chrono
@@ -2821,106 +2768,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     /// @}
   } // inline namespace chrono_literals
   } // inline namespace literals
-
-  namespace chrono
-  {
-    /// @addtogroup chrono
-    /// @{
-
-    /// @cond undocumented
-    namespace __detail
-    {
-      template<typename _Period>
-	const char*
-	__units_suffix_misc(char* __buf, size_t __n) noexcept
-	{
-	  namespace __tc = std::__detail;
-	  char* __p = __buf;
-	  __p[0] = '[';
-	  unsigned __nlen = __tc::__to_chars_len((uintmax_t)_Period::num);
-	  __tc::__to_chars_10_impl(__p + 1, __nlen, (uintmax_t)_Period::num);
-	  __p += 1 + __nlen;
-	  if constexpr (_Period::den != 1)
-	    {
-	      __p[0] = '/';
-	      unsigned __dlen = __tc::__to_chars_len((uintmax_t)_Period::den);
-	      __tc::__to_chars_10_impl(__p + 1, __dlen, (uintmax_t)_Period::den);
-	      __p += 1 + __dlen;
-	    }
-	  __p[0] = ']';
-	  __p[1] = 's';
-	  __p[2] = '\0';
-	  return __buf;
-	}
-
-      template<typename _Period, typename _CharT>
-	auto
-	__units_suffix(char* __buf, size_t __n) noexcept
-	{
-#define _GLIBCXX_UNITS_SUFFIX(period, suffix) \
-	if constexpr (is_same_v<_Period, period>)	\
-	  {						\
-	    if constexpr (is_same_v<_CharT, wchar_t>)	\
-	      return L##suffix;				\
-	    else					\
-	      return suffix;				\
-	  }						\
-	else
-
-	  _GLIBCXX_UNITS_SUFFIX(atto, "as")
-	  _GLIBCXX_UNITS_SUFFIX(femto, "fs")
-	  _GLIBCXX_UNITS_SUFFIX(pico, "ps")
-	  _GLIBCXX_UNITS_SUFFIX(nano, "ns")
-	  _GLIBCXX_UNITS_SUFFIX(micro, "\u00b5s")
-	  _GLIBCXX_UNITS_SUFFIX(milli, "ms")
-	  _GLIBCXX_UNITS_SUFFIX(centi, "cs")
-	  _GLIBCXX_UNITS_SUFFIX(deci, "ds")
-	  _GLIBCXX_UNITS_SUFFIX(ratio<1>, "s")
-	  _GLIBCXX_UNITS_SUFFIX(deca, "das")
-	  _GLIBCXX_UNITS_SUFFIX(hecto, "hs")
-	  _GLIBCXX_UNITS_SUFFIX(kilo, "ks")
-	  _GLIBCXX_UNITS_SUFFIX(mega, "Ms")
-	  _GLIBCXX_UNITS_SUFFIX(giga, "Gs")
-	  _GLIBCXX_UNITS_SUFFIX(tera, "Ts")
-	  _GLIBCXX_UNITS_SUFFIX(tera, "Ts")
-	  _GLIBCXX_UNITS_SUFFIX(peta, "Ps")
-	  _GLIBCXX_UNITS_SUFFIX(exa, "Es")
-	  _GLIBCXX_UNITS_SUFFIX(ratio<60>, "min")
-	  _GLIBCXX_UNITS_SUFFIX(ratio<3600>, "h")
-	  _GLIBCXX_UNITS_SUFFIX(ratio<86400>, "d")
-#undef _GLIBCXX_UNITS_SUFFIX
-	  return __detail::__units_suffix_misc<_Period>(__buf, __n);
-	}
-    } // namespace __detail
-    /// @endcond
-
-    template<typename _CharT, typename _Traits,
-	     typename _Rep, typename _Period>
-      inline basic_ostream<_CharT, _Traits>&
-      operator<<(std::basic_ostream<_CharT, _Traits>& __os,
-		const duration<_Rep, _Period>& __d)
-      {
-	using period = typename _Period::type;
-	char __buf[sizeof("[/]s") + 2 * numeric_limits<intmax_t>::digits10];
-	std::basic_ostringstream<_CharT, _Traits> __s;
-	__s.flags(__os.flags());
-	__s.imbue(__os.getloc());
-	__s.precision(__os.precision());
-	__s << __d.count();
-	__s << __detail::__units_suffix<period, _CharT>(__buf, sizeof(__buf));
-	__os << std::move(__s).str();
-	return __os;
-      }
-
-    // TODO: from_stream for duration
-
-    /// @} group chrono
-  } // namespace chrono
 #endif // C++20
 
 _GLIBCXX_END_NAMESPACE_VERSION
 } // namespace std
 
+#if __cplusplus >= 202002L
+# include <bits/chrono_io.h>
+#endif
+
 #endif // C++11
 
 #endif //_GLIBCXX_CHRONO
diff --git a/libstdc++-v3/src/c++20/Makefile.am b/libstdc++-v3/src/c++20/Makefile.am
index d4c1e26e40e..a95b8c24d21 100644
--- a/libstdc++-v3/src/c++20/Makefile.am
+++ b/libstdc++-v3/src/c++20/Makefile.am
@@ -36,7 +36,7 @@ else
 inst_sources =
 endif
 
-sources =
+sources = tzdb.cc
 
 vpath % $(top_srcdir)/src/c++20
 
diff --git a/libstdc++-v3/src/c++20/Makefile.in b/libstdc++-v3/src/c++20/Makefile.in
index 9db70a3e7fb..2adc1eb712e 100644
--- a/libstdc++-v3/src/c++20/Makefile.in
+++ b/libstdc++-v3/src/c++20/Makefile.in
@@ -121,7 +121,7 @@ CONFIG_CLEAN_FILES =
 CONFIG_CLEAN_VPATH_FILES =
 LTLIBRARIES = $(noinst_LTLIBRARIES)
 libc__20convenience_la_LIBADD =
-am__objects_1 =
+am__objects_1 = tzdb.lo
 @ENABLE_EXTERN_TEMPLATE_TRUE@am__objects_2 = sstream-inst.lo
 am_libc__20convenience_la_OBJECTS = $(am__objects_1) $(am__objects_2)
 libc__20convenience_la_OBJECTS = $(am_libc__20convenience_la_OBJECTS)
@@ -431,7 +431,7 @@ headers =
 @ENABLE_EXTERN_TEMPLATE_TRUE@inst_sources = \
 @ENABLE_EXTERN_TEMPLATE_TRUE@	sstream-inst.cc
 
-sources = 
+sources = tzdb.cc
 libc__20convenience_la_SOURCES = $(sources)  $(inst_sources)
 
 # AM_CXXFLAGS needs to be in each subdirectory so that it can be
diff --git a/libstdc++-v3/src/c++20/tzdb.cc b/libstdc++-v3/src/c++20/tzdb.cc
new file mode 100644
index 00000000000..1aae398f871
--- /dev/null
+++ b/libstdc++-v3/src/c++20/tzdb.cc
@@ -0,0 +1,512 @@
+#define _GLIBCXX_DEFINING_TZDB 1
+#include <chrono>
+#include <fstream>  // ifstream
+#include <sstream>  // istringstream
+#include <algorithm> // ranges::upper_bound, ranges::lower_bound
+#include <atomic>    // atomic_ref
+#include <memory>    // atomic<shared_ptr<T>>
+#include <mutex>     // mutex
+#include <filesystem>
+
+namespace std::chrono
+{
+  constexpr tzdb_list::tzdb_list(nullptr_t) { }
+
+  namespace
+  {
+    constinit mutex list_mutex;
+    constinit tzdb_list the_list(nullptr);
+  }
+
+  struct tzdb_list::_Node
+  {
+    tzdb db;
+    shared_ptr<_Node> _M_next;
+
+    // static shared_ptr<_Node> _S_load();
+  };
+
+  struct tzdb::_Rule
+  {
+    string name; // the name of the rule set that contains this line
+    year from; // first year in which the rule applies
+    year to;   // final year in which the rule applies
+    month_day on; // the day on which the rule takes effect
+    hh_mm_ss<seconds> at; // the time of day at which the rule takes effect
+    minutes save; // the amount of time to be added when the rule is in effect
+    string letters; // variable part of TZ abbreviations when rule in effect
+  };
+
+  // This is the owning reference to the first tzdb in the list.
+#if USE_ATOMIC_SHARED_PTR
+  constinit atomic<shared_ptr<tzdb_list::_Node>> tzdb_list::_S_head_owner{};
+#else
+  // list_mutex must be locked to access this.
+  constinit shared_ptr<tzdb_list::_Node> tzdb_list::_S_head_owner = nullptr;
+#endif
+  // Lock-free access to the head of the list.
+  // This must be accessed atomically, using atomic_ref<_Node*>.
+  constinit tzdb_list::_Node* tzdb_list::_S_head = nullptr;
+
+  // There are only three ways for users to access the tzdb list.
+  // get_tzdb_list() returns a reference to the list itself.
+  // get_tzdb() returns a reference to the front of the list.
+  // reload_tzdb() returns a reference to the (possibly new) front of the list.
+  // Those are the only functions that need to check whether the list has
+  // been populated already.
+
+  const tzdb_list&
+  get_tzdb_list()
+  {
+    atomic_ref<tzdb_list::_Node*> head(tzdb_list::_S_head);
+    if (head == nullptr) [[unlikely]]
+      reload_tzdb(); // populates list
+    return the_list;
+  }
+
+  // This is
+  const tzdb&
+  get_tzdb()
+  {
+    atomic_ref<tzdb_list::_Node*> head(tzdb_list::_S_head);
+    if (head == nullptr) [[unlikely]]
+      return reload_tzdb(); // populates list
+    return head.load()->db;
+  }
+
+  namespace
+  {
+    string
+    remote_version(istream& zif)
+    {
+      char hash;
+      string label;
+      string version;
+      if (zif >> hash >> label >> version)
+	if (hash == '#' && label == "version" && version.size() == 5)
+	  return version;
+      __throw_runtime_error("tzdb: no version found in tzdata.zi");
+    }
+
+    struct zi_rule
+    {
+    };
+  }
+
+  struct time_zone::_Impl
+  {
+    string name;
+  };
+
+  time_zone::time_zone(unique_ptr<_Impl> __p) : _M_impl(std::move(__p)) { }
+
+  time_zone::~time_zone() = default;
+
+  string remote_version()
+  {
+    ifstream zif("/usr/share/zoneinfo/tzdata.zi");
+    return remote_version(zif);
+  }
+
+  const tzdb&
+  reload_tzdb()
+  {
+    ifstream zif("/usr/share/zoneinfo/tzdata.zi");
+    const string version = remote_version(zif);
+
+#if USE_ATOMIC_SHARED_PTR
+    auto head = tzdb_list::_S_head_owner.load();
+    if (head != nullptr && head->db.version == version)
+      return head->db;
+#else
+    atomic_ref<tzdb_list::_Node*> head(tzdb_list::_S_head);
+    if (head != nullptr) [[likely]]
+    {
+      lock_guard<mutex> l(list_mutex);
+      const tzdb& current = tzdb_list::_S_head_owner->db;
+      if (current.version == version)
+	return current;
+    }
+#endif
+
+    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
+    };
+    ifstream ls("/usr/share/zoneinfo/leapseconds");
+    if (ls)
+      {
+	std::string s;
+	while (std::getline(ls, s))
+	  {
+	    if (!s.starts_with("Leap"))
+	      continue;
+	    istringstream li(std::move(s)); // FIXME: use ispanstream
+	    li.exceptions(ios::failbit);
+	    unsigned yval;
+	    char m;
+	    if (li >> s >> yval >> m)
+	      {
+		if (m != 'J' && m != 'D')
+		  __throw_runtime_error("tzdb: cannot parse leapseconds file");
+		if (yval < 2020)
+		  continue;
+		const int is_december = m == 'D';
+		year_month_day ymd{year(yval),
+				     month(6 + 6 * is_december),
+				     day(30 + is_december)};
+		sys_seconds secs(sys_days(ymd) + days(1));
+		li >> s >> s >> m;
+
+		if (m != '+' && m != '-') throw 1; // XXX
+
+		seconds::rep val = secs.time_since_epoch().count();
+		if (m == '-') [[unlikely]]
+		  val = -val;
+		leaps.push_back(leap_second(val));
+	      }
+	  }
+      }
+
+    // See https://man7.org/linux/man-pages/man8/zic.8.html#FILES
+    // for documentation of the tzdata.zi file.
+
+#if USE_ATOMIC_SHARED_PTR
+    auto node = std::make_shared<tzdb_list::_Node>();
+#else
+    auto node = std::make_unique<tzdb_list::_Node>();
+#endif
+
+    string line;
+    line.reserve(511);
+    string type;
+    istringstream is; // FIXME: use ispanstream
+    is.exceptions(ios::failbit);
+    while (std::getline(zif, line))
+      {
+	if (line.empty() || line.front() == '#')
+	  continue;
+	is.clear();
+	is.str(line);
+	is.ignore(1);
+	switch (line.front())
+	{
+	  case 'R': // Rule  NAME  FROM  TO  TYPE  IN  ON  AT  SAVE  LETTER/S
+	  {
+	    tzdb::_Rule rule;
+	    int from, to;
+	    char type;
+	    string str;
+	    is >> std::quoted(rule.name) >> from >> to >> type >> str;
+	    if (type != '-')
+	      is.setstate(ios::failbit);
+	    rule.from = year(from);
+	    rule.to = year(to);
+	    month m;
+	    day d;
+	    switch (str[0])
+	    {
+	    case 'J':
+	      switch (str[1])
+	      {
+	      case 'a':
+		m = January;
+		break;
+	      case 'u':
+		switch (str[2])
+		{
+		case 'n':
+		  m = June;
+		  break;
+		case 'l':
+		  m = July;
+		  break;
+		default:
+		  is.setstate(ios::failbit);
+		}
+		break;
+	      default:
+		is.setstate(ios::failbit);
+	      }
+	      break;
+	    case 'F':
+	      m = February;
+	      break;
+	    case 'M':
+	      if (str[1] != 'a')
+		is.setstate(ios::failbit);
+	      switch (str[2])
+	      {
+	      case 'r':
+		m = March;
+		break;
+	      case 'y':
+		m = May;
+		break;
+	      default:
+		is.setstate(ios::failbit);
+	      }
+	      break;
+	    case 'A':
+	      switch (str[1])
+	      {
+	      case 'p':
+		m = April;
+		break;
+	      case 'u':
+		m = August;
+		break;
+	      default:
+		is.setstate(ios::failbit);
+	      }
+	      break;
+	    case 'S':
+	      m = September;
+	      break;
+	    case 'O':
+	      m = October;
+	      break;
+	    case 'N':
+	      m = November;
+	      break;
+	    case 'D':
+	      m = December;
+	      break;
+	    default:
+	      is.setstate(ios::failbit);
+	    }
+	    is >> str;
+	    // TODO parse ON into d
+	    rule.on = month_day{m, d};
+	    is >> str;
+	    // TODO parse AT into rule.at
+	    is >> str;
+	    // TODO parse SAVE into rule.save
+	    is >> rule.letters;
+	    node->db._M_rules.push_back(std::move(rule));
+	    break;
+	  }
+	  case 'L': // Link  TARGET           LINK-NAME
+	  {
+	    time_zone_link link(nullptr);
+	    is >> std::quoted(link._M_target) >> std::quoted(link._M_name);
+	    node->db.links.push_back(std::move(link));
+	    break;
+	  }
+	  case 'Z': // Zone  NAME        STDOFF  RULES   FORMAT  [UNTIL]
+	  {
+	    auto tz = std::make_unique<time_zone::_Impl>();
+	    is >> std::quoted(tz->name);
+	    node->db.zones.push_back(time_zone(std::move(tz)));
+	    [[fallthrough]]; // Use default case to parse rest of line ...
+	  }
+	  default: // STDOFF  RULES   FORMAT  [UNTIL]
+	  {
+	    // This is a continuation of the previous Zone line.
+	    time_zone& tz = node->db.zones.back();
+	    // TODO
+	  }
+	}
+
+
+      }
+    // TODO: parse file
+
+    // TODO sort vectors
+    //
+    // sort rules first, then link each time_zone to rules,
+    // by replacing the const char* pointing to the rule name
+    // with const _Rule* pointing to the rule itself.
+    // Implies time_zone impl needs union { const char* name; const _Rule* r; }
+
+#if USE_ATOMIC_SHARED_PTR
+    while (!_M_head_owner.compare_exchange_strong(head, node))
+      if (head->db.version == version)
+	return *head;
+    // XXX small window here where _S_head still points to previous tzdb.
+    _S_head = node.get();
+    return *node;
+#else
+    lock_guard<mutex> l(list_mutex);
+    if (const tzdb_list::_Node* h = head)
+      {
+	if (h->db.version == version)
+	  return h->db;
+      }
+    auto* pnode = node.get();
+    tzdb_list::_S_head_owner = std::move(node);
+    head = pnode;
+    return pnode->db;
+#endif
+  }
+
+  // Any call to tzdb_list::front() or tzdb_list::begin() must follow a call
+  // to get_tzdb_list() so the list has already been populated.
+
+  const tzdb&
+  tzdb_list::front() const noexcept
+  {
+    atomic_ref<_Node*> head(_S_head);
+    return head.load()->db;
+  }
+
+  auto
+  tzdb_list::begin() const noexcept
+  -> const_iterator
+  {
+#if USE_ATOMIC_SHARED_PTR
+    return const_iterator{_S_head_owner.load()};
+#else
+    lock_guard<mutex> l(list_mutex);
+    return const_iterator{_S_head_owner};
+#endif
+  }
+
+  auto
+  tzdb_list::erase_after(const_iterator p)
+  -> const_iterator
+  {
+    if (p._M_node) [[likely]]
+      if (auto next = p._M_node->_M_next) [[likely]]
+	return const_iterator{p._M_node->_M_next = std::move(next->_M_next)};
+
+    // This is undefined, but let's be kind:
+    std::__throw_logic_error("std::tzdb_list::erase_after: iterator is not "
+			     "dereferenceable");
+  }
+
+  auto
+  tzdb_list::const_iterator::operator*() const
+  -> reference
+  {
+    return _M_node->db;
+  }
+
+  auto
+  tzdb_list::const_iterator::operator++()
+  -> const_iterator&
+  {
+    auto cur = std::move(_M_node);
+    _M_node = cur->_M_next;
+    return *this;
+  }
+
+  auto
+  tzdb_list::const_iterator::operator++(int)
+  -> const_iterator
+  {
+    auto tmp = std::move(*this);
+    _M_node = tmp._M_node->_M_next;
+    return tmp;
+  }
+
+  leap_second_info
+  leap_second::_S_get_info(const utc_time<seconds>& ut)
+  {
+    auto begin = the_list.begin();
+    const auto& leaps = begin->leap_seconds;
+    leap_second s{ut.time_since_epoch().count()};
+    auto pos = std::upper_bound(leaps.begin(), leaps.end(), s);
+    return {
+      pos != leaps.begin() && pos[-1] == s,
+      seconds{pos - leaps.begin()}
+    };
+  }
+
+  namespace
+  {
+    const time_zone*
+    do_locate_zone(const vector<time_zone>& zones,
+		   const vector<time_zone_link>& links,
+		   string_view tz_name) noexcept
+    {
+      auto get_name = [](const auto& x) { return x.name(); };
+
+      auto search = [get_name](const auto& v, string_view name) {
+	auto p = ranges::lower_bound(v, name, {}, get_name);
+	if (p != v.end() && p->name() == name)
+	  return p.base();
+	return static_cast<decltype(p.base())>(nullptr);
+      };
+
+      if (auto tz = search(zones, tz_name))
+	return tz;
+
+      if (auto tz_l = search(links, tz_name))
+	return search(zones, tz_l->target());
+
+      return nullptr;
+    }
+  } // namespace
+
+  const time_zone*
+  tzdb::locate_zone(string_view tz_name) const
+  {
+    if (auto tz = do_locate_zone(zones, links, tz_name))
+      return tz;
+    string_view err = "tzdb: cannot locate zone: ";
+    string str;
+    str.reserve(err.size() + tz_name.size());
+    str += err;
+    str += tz_name;
+    __throw_runtime_error(str.c_str());
+  }
+
+  const time_zone*
+  tzdb::current_zone() const
+  {
+    error_code ec;
+    auto path = filesystem::read_symlink("/etc/localtime", ec);
+    if (!ec)
+      {
+	auto first = path.begin(), last = path.end();
+	if (std::distance(first, last) > 2)
+	  {
+	    --last;
+	    string name = std::prev(last)->string() + '/' + last->string();
+	    if (auto tz = do_locate_zone(zones, links, name))
+	      return tz;
+	  }
+      }
+    __throw_runtime_error("tzdb: cannot determine current zone");
+  }
+
+  const time_zone*
+  locate_zone(string_view tz_name)
+  {
+    return get_tzdb_list().begin()->locate_zone(tz_name);
+  }
+
+  const time_zone*
+  current_zone()
+  {
+    return get_tzdb_list().begin()->current_zone();
+  }
+
+} // namespace std
diff --git a/libstdc++-v3/testsuite/std/time/clock/gps/io.cc b/libstdc++-v3/testsuite/std/time/clock/gps/io.cc
new file mode 100644
index 00000000000..29f3148cf14
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/time/clock/gps/io.cc
@@ -0,0 +1,24 @@
+// { dg-options "-std=gnu++20" }
+// { dg-do run { target c++20 } }
+
+#include <chrono>
+#include <format>
+#include <testsuite_hooks.h>
+
+void
+test01()
+{
+  using std::format;
+  using namespace std::chrono;
+
+  auto st = sys_days{2000y/January/1};
+  auto gt = clock_cast<gps_clock>(st);
+
+  auto s = format("{0:%F %T %Z} == {1:%F %T %Z}", st, gt);
+  VERIFY( s == "2000-01-01 00:00:00 UTC == 2000-01-01 00:00:13 GPS" );
+}
+
+int main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/std/time/clock/system/io.cc b/libstdc++-v3/testsuite/std/time/clock/system/io.cc
new file mode 100644
index 00000000000..07ba8c84f40
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/time/clock/system/io.cc
@@ -0,0 +1,37 @@
+// { dg-options "-std=gnu++20" }
+// { dg-do run { target c++20 } }
+
+#include <chrono>
+#include <sstream>
+#include <testsuite_hooks.h>
+
+void
+test01()
+{
+  using namespace std::chrono;
+  std::stringstream ss;
+  ss << sys_seconds{0s} << '\n';                // 1970-01-01 00:00:00
+  ss << sys_seconds{946'684'800s} << '\n';      // 2000-01-01 00:00:00
+  ss << sys_seconds{946'688'523s} << '\n';      // 2000-01-01 01:02:03
+  std::string s1, s2, s3;
+  std::getline(ss, s1);
+  std::getline(ss, s2);
+  std::getline(ss, s3);
+  VERIFY( s1 == "1970-01-01 00:00:00" );
+  VERIFY( s2 == "2000-01-01 00:00:00" );
+  VERIFY( s3 == "2000-01-01 01:02:03" );
+}
+
+template<typename T>
+concept stream_insertable
+  = requires (std::ostream& out, const T& t) { out << t; };
+
+using fp_sys_time = std::chrono::sys_time<std::chrono::duration<float>>;
+static_assert( !stream_insertable<fp_sys_time> );
+
+static_assert( !stream_insertable<std::chrono::sys_days> );
+
+int main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/std/time/clock/tai/io.cc b/libstdc++-v3/testsuite/std/time/clock/tai/io.cc
new file mode 100644
index 00000000000..d0255f5431a
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/time/clock/tai/io.cc
@@ -0,0 +1,24 @@
+// { dg-options "-std=gnu++20" }
+// { dg-do run { target c++20 } }
+
+#include <chrono>
+#include <format>
+#include <testsuite_hooks.h>
+
+void
+test01()
+{
+  using std::format;
+  using namespace std::chrono;
+
+  auto st = sys_days{2000y/January/1};
+  auto tt = clock_cast<tai_clock>(st);
+
+  auto s = format("{0:%F %T %Z} == {1:%F %T %Z}", st, tt);
+  VERIFY( s == "2000-01-01 00:00:00 UTC == 2000-01-01 00:00:32 TAI" );
+}
+
+int main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/std/time/clock/utc/io.cc b/libstdc++-v3/testsuite/std/time/clock/utc/io.cc
new file mode 100644
index 00000000000..75e89fc62bb
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/time/clock/utc/io.cc
@@ -0,0 +1,40 @@
+// { dg-options "-std=gnu++20" }
+// { dg-do run { target c++20 } }
+
+#include <chrono>
+#include <sstream>
+#include <testsuite_hooks.h>
+
+void
+test01()
+{
+  using std::ostringstream;
+  using namespace std::chrono;
+
+  auto t = sys_days{July/1/2015} - 500ms;
+  auto u = clock_cast<utc_clock>(t);
+
+  std::string_view results[] = {
+    "2015-06-30 23:59:59.500 UTC",
+    "2015-06-30 23:59:59.750 UTC",
+    "2015-06-30 23:59:60.000 UTC",
+    "2015-06-30 23:59:60.250 UTC",
+    "2015-06-30 23:59:60.500 UTC",
+    "2015-06-30 23:59:60.750 UTC",
+    "2015-07-01 00:00:00.000 UTC",
+    "2015-07-01 00:00:00.250 UTC",
+  };
+
+  for (auto result : results)
+  {
+    ostringstream out;
+    out << u << " UTC";
+    VERIFY( out.str() == result );
+    u += 250ms;
+  }
+}
+
+int main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/std/time/hh_mm_ss/io.cc b/libstdc++-v3/testsuite/std/time/hh_mm_ss/io.cc
new file mode 100644
index 00000000000..3b50f40c1f6
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/time/hh_mm_ss/io.cc
@@ -0,0 +1,46 @@
+// { dg-options "-std=gnu++20" }
+// { dg-do run { target c++20 } }
+
+#include <chrono>
+#include <sstream>
+#include <testsuite_hooks.h>
+
+void
+test01()
+{
+  using std::ostringstream;
+  using std::chrono::hh_mm_ss;
+  using namespace std::chrono_literals;
+
+  std::locale::global(std::locale::classic());
+
+  {
+    hh_mm_ss hms{-4083007ms};
+    ostringstream out;
+    out << hms;
+    VERIFY( out.str() == "-01:08:03.007" );
+  }
+
+  {
+    hh_mm_ss hms{4083007ms};
+    ostringstream out;
+    out << hms;
+    VERIFY( out.str() == "01:08:03.007" );
+  }
+
+  {
+    hh_mm_ss hms{65745123ms};
+    ostringstream out;
+    out << hms;
+    VERIFY( out.str() == "18:15:45.123" );
+  }
+
+  ostringstream out;
+  out << hh_mm_ss{65745s};
+  VERIFY( out.str() == "18:15:45" );
+}
+
+int main()
+{
+  test01();
+}
  

Patch

diff --git a/libstdc++-v3/include/std/chrono b/libstdc++-v3/include/std/chrono
index c0c3a679609..90b73f8198e 100644
--- a/libstdc++-v3/include/std/chrono
+++ b/libstdc++-v3/include/std/chrono
@@ -39,9 +39,15 @@ 
 #else
 
 #include <bits/chrono.h>
-#if __cplusplus > 201703L
-# include <sstream> // ostringstream
-# include <bits/charconv.h>
+
+#if __cplusplus >= 202002L
+# include <sstream>
+# include <string>
+# include <vector>
+# include <bits/charconv.h> // __to_chars_len, __to_chars_10_impl
+# include <bits/stl_algo.h> // upper_bound TODO: move leap_second_info to .so
+# include <bits/shared_ptr.h>
+# include <bits/unique_ptr.h>
 #endif
 
 namespace std _GLIBCXX_VISIBILITY(default)
@@ -102,6 +108,357 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
       seconds elapsed;
     };
 
+    template<typename _Duration>
+      leap_second_info
+      get_leap_second_info(const utc_time<_Duration>& __ut);
+
+    /** A clock that measures Universal Coordinated Time (UTC).
+     *
+     * The epoch is 1970-01-01 00:00:00.
+     *
+     * @since C++20
+     */
+    class utc_clock
+    {
+    public:
+      using rep                       = system_clock::rep;
+      using period                    = system_clock::period;
+      using duration                  = chrono::duration<rep, period>;
+      using time_point                = chrono::time_point<utc_clock>;
+      static constexpr bool is_steady = false;
+
+      static time_point
+      now()
+      { return from_sys(system_clock::now()); }
+
+      template<typename _Duration>
+	static sys_time<common_type_t<_Duration, seconds>>
+	to_sys(const utc_time<_Duration>& __t)
+	{
+	  using _CDur = common_type_t<_Duration, seconds>;
+	  const auto __li = chrono::get_leap_second_info(__t);
+	  sys_time<_CDur> __s{__t.time_since_epoch() - seconds{__li.elapsed}};
+	  if (__li.is_leap_second)
+	    __s = chrono::floor<seconds>(__s) + seconds{1} - _CDur{1};
+	  return __s;
+	}
+
+      template<typename _Duration>
+	static utc_time<common_type_t<_Duration, seconds>>
+	from_sys(const sys_time<_Duration>& __t)
+	{
+	  using _CDur = common_type_t<_Duration, seconds>;
+	  utc_time<_Duration> __u(__t.time_since_epoch());
+	  const auto __li = chrono::get_leap_second_info(__u);
+	  return utc_time<_CDur>{__u} + seconds{__li.elapsed};
+	}
+    };
+
+    /** A clock that measures International Atomic Time.
+     *
+     * The epoch is 1958-01-01 00:00:00.
+     *
+     * @since C++20
+     */
+    class tai_clock
+    {
+    public:
+      using rep                       = system_clock::rep;
+      using period                    = system_clock::period;
+      using duration                  = chrono::duration<rep, period>;
+      using time_point                = chrono::time_point<tai_clock>;
+      static constexpr bool is_steady = false; // XXX true for CLOCK_TAI?
+
+      // TODO move into lib, use CLOCK_TAI on linux, add extension point.
+      static time_point
+      now()
+      { return from_utc(utc_clock::now()); }
+
+      template<typename _Duration>
+	static utc_time<common_type_t<_Duration, seconds>>
+	to_utc(const tai_time<_Duration>& __t)
+	{
+	  using _CDur = common_type_t<_Duration, seconds>;
+	  return utc_time<_CDur>{__t.time_since_epoch()} - 378691210s;
+	}
+
+      template<typename _Duration>
+	static tai_time<common_type_t<_Duration, seconds>>
+	from_utc(const utc_time<_Duration>& __t)
+	{
+	  using _CDur = common_type_t<_Duration, seconds>;
+	  return tai_time<_CDur>{__t.time_since_epoch()} + 378691210s;
+	}
+    };
+
+    /** A clock that measures GPS time.
+     *
+     * The epoch is 1980-01-06 00:00:00.
+     *
+     * @since C++20
+     */
+    class gps_clock
+    {
+    public:
+      using rep                       = system_clock::rep;
+      using period                    = system_clock::period;
+      using duration                  = chrono::duration<rep, period>;
+      using time_point                = chrono::time_point<gps_clock>;
+      static constexpr bool is_steady = false; // XXX
+
+      // TODO move into lib, add extension point.
+      static time_point
+      now()
+      { return from_utc(utc_clock::now()); }
+
+      template<typename _Duration>
+	static utc_time<common_type_t<_Duration, seconds>>
+	to_utc(const gps_time<_Duration>& __t)
+	{
+	  using _CDur = common_type_t<_Duration, seconds>;
+	  return utc_time<_CDur>{__t.time_since_epoch()} + 315964809s;
+	}
+
+      template<typename _Duration>
+	static gps_time<common_type_t<_Duration, seconds>>
+	from_utc(const utc_time<_Duration>& __t)
+	{
+	  using _CDur = common_type_t<_Duration, seconds>;
+	  return gps_time<_CDur>{__t.time_since_epoch()} - 315964809s;
+	}
+    };
+
+
+    template<typename _DestClock, typename _SourceClock>
+      struct clock_time_conversion
+      { };
+
+    // Identity conversions
+
+    template<typename _Clock>
+      struct clock_time_conversion<_Clock, _Clock>
+      {
+	template<typename _Duration>
+	  time_point<_Clock, _Duration>
+	  operator()(const time_point<_Clock, _Duration>& __t) const
+	  { return __t; }
+      };
+
+    template<>
+      struct clock_time_conversion<system_clock, system_clock>
+      {
+	template<typename _Duration>
+	  sys_time<_Duration>
+	  operator()(const sys_time<_Duration>& __t) const
+	  { return __t; }
+      };
+
+    template<>
+      struct clock_time_conversion<utc_clock, utc_clock>
+      {
+	template<typename _Duration>
+	  utc_time<_Duration>
+	  operator()(const utc_time<_Duration>& __t) const
+	  { return __t; }
+      };
+
+    // Conversions between system_clock and utc_clock
+
+    template<>
+      struct clock_time_conversion<utc_clock, system_clock>
+      {
+	template<typename _Duration>
+	  utc_time<common_type_t<_Duration, seconds>>
+	  operator()(const sys_time<_Duration>& __t) const
+	  { return utc_clock::from_sys(__t); }
+      };
+
+    template<>
+      struct clock_time_conversion<system_clock, utc_clock>
+      {
+	template<typename _Duration>
+	  sys_time<common_type_t<_Duration, seconds>>
+	  operator()(const utc_time<_Duration>& __t) const
+	  { return utc_clock::to_sys(__t); }
+      };
+
+    template<typename _Tp, typename _Clock>
+      inline constexpr bool __is_time_point_for_v = false;
+
+    template<typename _Clock, typename _Duration>
+      inline constexpr bool
+       __is_time_point_for_v<time_point<_Clock, _Duration>, _Clock> = true;
+
+    // Conversions between system_clock and other clocks
+
+    template<typename _SourceClock>
+      struct clock_time_conversion<system_clock, _SourceClock>
+      {
+	template<typename _Duration, typename _Src = _SourceClock>
+	  auto
+	  operator()(const time_point<_SourceClock, _Duration>& __t) const
+	  -> decltype(_Src::to_sys(__t))
+	  {
+	    using _Ret = decltype(_SourceClock::to_sys(__t));
+	    static_assert(__is_time_point_for_v<_Ret, system_clock>);
+	    return _SourceClock::to_sys(__t);
+	  }
+      };
+
+    template<typename _DestClock>
+      struct clock_time_conversion<_DestClock, system_clock>
+      {
+	template<typename _Duration, typename _Dest = _DestClock>
+	  auto
+	  operator()(const sys_time<_Duration>& __t) const
+	  -> decltype(_Dest::from_sys(__t))
+	  {
+	    using _Ret = decltype(_DestClock::from_sys(__t));
+	    static_assert(__is_time_point_for_v<_Ret, _DestClock>);
+	    return _DestClock::from_sys(__t);
+	  }
+      };
+
+    // Conversions between utc_clock and other clocks
+
+    template<typename _SourceClock>
+      struct clock_time_conversion<utc_clock, _SourceClock>
+      {
+	template<typename _Duration, typename _Src = _SourceClock>
+	  auto
+	  operator()(const time_point<_SourceClock, _Duration>& __t) const
+	  -> decltype(_Src::to_utc(__t))
+	  {
+	    using _Ret = decltype(_SourceClock::to_utc(__t));
+	    static_assert(__is_time_point_for_v<_Ret, utc_clock>);
+	    return _SourceClock::to_utc(__t);
+	  }
+      };
+
+    template<typename _DestClock>
+      struct clock_time_conversion<_DestClock, utc_clock>
+      {
+	template<typename _Duration, typename _Dest = _DestClock>
+	  auto
+	  operator()(const utc_time<_Duration>& __t) const
+	  -> decltype(_Dest::from_utc(__t))
+	  {
+	    using _Ret = decltype(_DestClock::from_utc(__t));
+	    static_assert(__is_time_point_for_v<_Ret, _DestClock>);
+	    return _DestClock::from_utc(__t);
+	  }
+      };
+
+    /// @cond undocumented
+    namespace __detail
+    {
+      template<typename _DestClock, typename _SourceClock, typename _Duration>
+       concept __clock_convs
+	  = requires (const time_point<_SourceClock, _Duration>& __t) {
+	    clock_time_conversion<_DestClock, _SourceClock>{}(__t);
+	  };
+
+      template<typename _DestClock, typename _SourceClock, typename _Duration>
+       concept __clock_convs_sys
+	  = requires (const time_point<_SourceClock, _Duration>& __t) {
+	    clock_time_conversion<_DestClock, system_clock>{}(
+	      clock_time_conversion<system_clock, _SourceClock>{}(__t));
+	  };
+
+      template<typename _DestClock, typename _SourceClock, typename _Duration>
+       concept __clock_convs_utc
+	  = requires (const time_point<_SourceClock, _Duration>& __t) {
+	    clock_time_conversion<_DestClock, utc_clock>{}(
+	      clock_time_conversion<utc_clock, _SourceClock>{}(__t));
+	  };
+
+      template<typename _DestClock, typename _SourceClock, typename _Duration>
+	concept __clock_convs_sys_utc
+	  = requires (const time_point<_SourceClock, _Duration>& __t) {
+	    clock_time_conversion<_DestClock, utc_clock>{}(
+	      clock_time_conversion<utc_clock, system_clock>{}(
+		clock_time_conversion<system_clock, _SourceClock>{}(__t)));
+	  };
+
+      template<typename _DestClock, typename _SourceClock, typename _Duration>
+       concept __clock_convs_utc_sys
+	  = requires (const time_point<_SourceClock, _Duration>& __t) {
+	    clock_time_conversion<_DestClock, system_clock>{}(
+	      clock_time_conversion<system_clock, utc_clock>{}(
+		clock_time_conversion<utc_clock, _SourceClock>{}(__t)));
+	  };
+
+    } // namespace __detail
+    /// @endcond
+
+    /// Convert a time point to a different clock.
+    template<typename _DestClock, typename _SourceClock, typename _Duration>
+      inline auto
+      clock_cast(const time_point<_SourceClock, _Duration>& __t)
+      requires __detail::__clock_convs<_DestClock, _SourceClock, _Duration>
+	|| __detail::__clock_convs_sys<_DestClock, _SourceClock, _Duration>
+	|| __detail::__clock_convs_utc<_DestClock, _SourceClock, _Duration>
+	|| __detail::__clock_convs_sys_utc<_DestClock, _SourceClock, _Duration>
+	|| __detail::__clock_convs_utc_sys<_DestClock, _SourceClock, _Duration>
+      {
+       constexpr bool __direct
+	 = __detail::__clock_convs<_DestClock, _SourceClock, _Duration>;
+       if constexpr (__direct)
+	 {
+	   return clock_time_conversion<_DestClock, _SourceClock>{}(__t);
+	 }
+       else
+	 {
+	   constexpr bool __convert_via_sys_clock
+	     = __detail::__clock_convs_sys<_DestClock, _SourceClock, _Duration>;
+	   constexpr bool __convert_via_utc_clock
+	     = __detail::__clock_convs_utc<_DestClock, _SourceClock, _Duration>;
+	   if constexpr (__convert_via_sys_clock)
+	     {
+	       static_assert(!__convert_via_utc_clock,
+		 "clock_cast requires a unique best conversion, but "
+		 "conversion is possible via system_clock and also via"
+		 "utc_clock");
+	       return clock_time_conversion<_DestClock, system_clock>{}(
+			clock_time_conversion<system_clock, _SourceClock>{}(__t));
+	     }
+	   else if constexpr (__convert_via_utc_clock)
+	     {
+	       return clock_time_conversion<_DestClock, utc_clock>{}(
+			clock_time_conversion<utc_clock, _SourceClock>{}(__t));
+	     }
+	   else
+	     {
+	       constexpr bool __convert_via_sys_and_utc_clocks
+		 = __detail::__clock_convs_sys_utc<_DestClock,
+						   _SourceClock,
+						   _Duration>;
+
+	       if constexpr (__convert_via_sys_and_utc_clocks)
+		 {
+		   constexpr bool __convert_via_utc_and_sys_clocks
+		     = __detail::__clock_convs_utc_sys<_DestClock,
+						       _SourceClock,
+						       _Duration>;
+		   static_assert(!__convert_via_utc_and_sys_clocks,
+		     "clock_cast requires a unique best conversion, but "
+		     "conversion is possible via system_clock followed by "
+		     "utc_clock, and also via utc_clock followed by "
+		     "system_clock");
+		   return clock_time_conversion<_DestClock, utc_clock>{}(
+			    clock_time_conversion<utc_clock, system_clock>{}(
+			      clock_time_conversion<system_clock, _SourceClock>{}(__t)));
+		 }
+	       else
+		 {
+		   return clock_time_conversion<_DestClock, system_clock>{}(
+			    clock_time_conversion<system_clock, utc_clock>{}(
+			      clock_time_conversion<utc_clock, _SourceClock>{}(__t)));
+		 }
+	     }
+	 }
+      }
+
     // CALENDRICAL TYPES
 
     // CLASS DECLARATIONS
@@ -2055,6 +2412,387 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	    return __h + 12h;
 	}
     }
+
+    // C++20 [time.zones] Time zones
+
+    struct sys_info
+    {
+      sys_seconds begin;
+      sys_seconds end;
+      seconds offset;
+      minutes save;
+      string abbrev;
+    };
+
+    struct local_info
+    {
+      static constexpr int unique      = 0;
+      static constexpr int nonexistent = 1;
+      static constexpr int ambiguous   = 2;
+
+      int result;
+      sys_info first;
+      sys_info second;
+    };
+
+    class nonexistent_local_time : public runtime_error
+    {
+    public:
+      template<typename _Duration>
+	nonexistent_local_time(const local_time<_Duration>& __tp,
+			       const local_info& __i)
+	: runtime_error(_S_make_what_str(__tp, __i))
+	{ __glibcxx_assert(__i.result == local_info::nonexistent); }
+
+    private:
+      template<typename _Duration> // TODO
+	static string
+	_S_make_what_str(const local_time<_Duration>&, const local_info&);
+    };
+
+    class ambiguous_local_time : public runtime_error
+    {
+    public:
+      template<typename _Duration>
+	ambiguous_local_time(const local_time<_Duration>& __tp,
+			       const local_info& __i)
+	: runtime_error(_S_make_what_str(__tp, __i))
+	{ __glibcxx_assert(__i.result == local_info::nonexistent); }
+
+    private:
+      template<typename _Duration> // TODO
+	static string
+	_S_make_what_str(const local_time<_Duration>&, const local_info&);
+    };
+
+    enum class choose { earliest, latest };
+
+    class time_zone
+    {
+    public:
+      time_zone(time_zone&&) = default;
+      time_zone& operator=(time_zone&&) = default;
+
+      string_view name() const noexcept { return _M_name; }
+
+      template<typename _Duration>
+	sys_info
+	get_info(const sys_time<_Duration>& __st) const;
+
+      template<typename _Duration>
+	local_info
+	get_info(const local_time<_Duration>& __tp) const;
+
+      template<typename _Duration>
+	sys_time<common_type_t<_Duration, seconds>>
+	to_sys(const local_time<_Duration>& __tp) const;
+
+      template<typename _Duration>
+	sys_time<common_type_t<_Duration, seconds>>
+	to_sys(const local_time<_Duration>& __tp, choose __z) const;
+
+      template<typename _Duration>
+	local_time<common_type_t<_Duration, seconds>>
+	to_local(const sys_time<_Duration>& __tp) const;
+
+      friend bool
+      operator==(const time_zone& __x, const time_zone& __y) noexcept
+      { return __x.name() == __y.name(); }
+
+      friend strong_ordering
+      operator<=>(const time_zone& __x, const time_zone& __y) noexcept
+      { return __x.name() <=> __y.name(); }
+
+    private:
+      string _M_name;
+      struct _Impl;
+      unique_ptr<_Impl> _M_impl;
+    };
+
+    struct tzdb;
+    const time_zone* locate_zone(string_view __tz_name);
+    const time_zone* current_zone();
+
+    class time_zone_link
+    {
+    public:
+      time_zone_link(time_zone_link&&) = default;
+      time_zone_link& operator=(time_zone_link&&) = default;
+
+      string_view name() const noexcept { return _M_name; }
+      string_view target() const noexcept { return _M_target; }
+
+      friend bool
+      operator==(const time_zone_link& __x, const time_zone_link& __y) noexcept
+      { return __x.name() == __y.name(); }
+
+      friend strong_ordering
+      operator<=>(const time_zone_link& __x, const time_zone_link& __y) noexcept
+      { return __x.name() <=> __y.name(); }
+
+    private:
+      friend const tzdb& reload_tzdb();
+      // TODO unspecified additional constructors
+      string _M_name;
+      string _M_target;
+    };
+
+    class leap_second
+    {
+    public:
+      leap_second(const leap_second&) = default;
+      leap_second& operator=(const leap_second&) = default;
+
+      constexpr sys_seconds
+      date() const noexcept
+      {
+	if (_M_s >= _M_s.zero()) [[likely]]
+	  return sys_seconds(_M_s);
+	return sys_seconds(-_M_s);
+      }
+
+      constexpr seconds
+      value() const noexcept
+      {
+	if (_M_s >= _M_s.zero()) [[likely]]
+	  return seconds(1);
+	return seconds(-1);
+      }
+
+      // This can be defaulted because the database will never contain two
+      // leap_second objects with the same date but different signs.
+      friend constexpr bool
+      operator==(const leap_second&, const leap_second&) noexcept = default;
+
+      friend constexpr strong_ordering
+      operator<=>(const leap_second& __x, const leap_second& __y) noexcept
+      { return __x.date() <=> __y.date(); }
+
+      template<typename _Duration>
+	friend constexpr bool
+	operator==(const leap_second& __x,
+		   const sys_time<_Duration>& __y) noexcept
+	{ return __x.date() == __y; }
+
+      template<typename _Duration>
+	friend constexpr bool
+	operator<(const leap_second& __x,
+		  const sys_time<_Duration>& __y) noexcept
+	{ return __x.date() < __y; }
+
+      template<typename _Duration>
+	friend constexpr bool
+	operator<(const sys_time<_Duration>& __x,
+		  const leap_second& __y) noexcept
+	{ return __x < __y.date(); }
+
+      template<typename _Duration>
+	friend constexpr bool
+	operator>(const leap_second& __x,
+		  const sys_time<_Duration>& __y) noexcept
+	{ return __y < __x.date(); }
+
+      template<typename _Duration>
+	friend constexpr bool
+	operator>(const sys_time<_Duration>& __x,
+		  const leap_second& __y) noexcept
+	{ return __y.date() < __x; }
+
+      template<typename _Duration>
+	friend constexpr bool
+	operator<=(const leap_second& __x,
+		  const sys_time<_Duration>& __y) noexcept
+	{ return !(__y < __x.date()); }
+
+      template<typename _Duration>
+	friend constexpr bool
+	operator<=(const sys_time<_Duration>& __x,
+		  const leap_second& __y) noexcept
+	{ return !(__y.date() < __x); }
+
+      template<typename _Duration>
+	friend constexpr bool
+	operator>=(const leap_second& __x,
+		  const sys_time<_Duration>& __y) noexcept
+	{ return !(__x.date() < __y); }
+
+      template<typename _Duration>
+	friend constexpr bool
+	operator>=(const sys_time<_Duration>& __x,
+		  const leap_second& __y) noexcept
+	{ return !(__x < __y.date()); }
+
+      template<three_way_comparable_with<seconds> _Duration>
+	friend constexpr auto
+	operator<=>(const leap_second& __x,
+		   const sys_time<_Duration>& __y) noexcept
+	{ return __x.date() <=> __y; }
+
+    private:
+      explicit leap_second(seconds::rep __s) : _M_s(__s) { }
+
+      friend const tzdb& reload_tzdb();
+      template<typename _Dur>
+	friend leap_second_info
+	get_leap_second_info(const utc_time<_Dur>&);
+
+      seconds _M_s; // == date().time_since_epoch() * value().count()
+    };
+
+    template<class _Tp> struct zoned_traits { };
+
+    template<>
+      struct zoned_traits<const time_zone*>
+      {
+	static const time_zone*
+	default_zone()
+	{ return std::chrono::locate_zone("UTC"); }
+
+	static const time_zone*
+	locate_zone(string_view __name)
+	{ return std::chrono::locate_zone(__name); }
+      };
+
+    struct tzdb
+    {
+      string version;
+      vector<time_zone> zones;
+      vector<time_zone_link> links;
+      vector<leap_second> leap_seconds;
+
+      const time_zone*
+      locate_zone(string_view __tz_name) const;
+
+      const time_zone*
+      current_zone() const;
+
+    private:
+      friend const tzdb& reload_tzdb();
+
+      struct _Rule;
+      vector<_Rule> _M_rules;
+    };
+
+    class tzdb_list
+    {
+      struct _Node;
+    public:
+      tzdb_list(const tzdb_list&) = delete;
+      tzdb_list& operator=(const tzdb_list&) = delete;
+
+      class const_iterator
+      {
+      public:
+	using value_type        = tzdb;
+	using reference         = const tzdb&;
+	using pointer           = const tzdb*;
+	using difference_type   = ptrdiff_t;
+	using iterator_category = forward_iterator_tag;
+
+	constexpr const_iterator() = default;
+	const_iterator(const const_iterator&) = default;
+	const_iterator(const_iterator&&) = default;
+	const_iterator& operator=(const const_iterator&) = default;
+	const_iterator& operator=(const_iterator&&) = default;
+
+	reference operator*() const noexcept;
+	pointer operator->() const noexcept { return &**this; }
+	const_iterator& operator++();
+	const_iterator operator++(int);
+
+	bool operator==(const const_iterator&) const noexcept = default;
+
+      private:
+	explicit const_iterator(const shared_ptr<_Node>&) noexcept;
+
+	shared_ptr<_Node> _M_node;
+	void* _M_reserved = nullptr;
+      };
+
+      // TODO const tzdb& front() const noexcept;
+
+      const_iterator erase_after(const_iterator);
+
+      const_iterator begin() const noexcept;
+      const_iterator end() const noexcept { return {}; }
+      const_iterator cbegin() const noexcept { return begin(); }
+      const_iterator cend() const noexcept { return end(); }
+
+    private:
+      constexpr explicit tzdb_list(nullptr_t);
+
+      friend const tzdb_list& get_tzdb_list();
+      friend const tzdb& get_tzdb();
+      friend const tzdb& reload_tzdb();
+
+      static _Node* _S_head;
+      static shared_ptr<_Node> _S_head_owner;
+    };
+
+    // TODO
+    // const tzdb_list& get_tzdb_list();
+    // const tzdb& get_tzdb();
+
+    // const tzdb& reload_tzdb();
+    // string remove_version();
+
+    template<typename _Duration, typename _TimeZonePtr = const time_zone*>
+      class zoned_time; // TODO
+
+    using zoned_seconds = zoned_time<seconds>;
+
+    template<typename _Duration>
+      leap_second_info
+      get_leap_second_info(const utc_time<_Duration>& __ut)
+      {
+	if constexpr (is_same_v<_Duration, seconds>)
+	  {
+	    // TODO move this function into the library and get leaps from tzdb.
+	    vector<seconds::rep> __leaps
+	    {
+		78796800, // 1 Jul 1972
+		94694400, // 1 Jan 1973
+	       126230400, // 1 Jan 1974
+	       157766400, // 1 Jan 1975
+	       189302400, // 1 Jan 1976
+	       220924800, // 1 Jan 1977
+	       252460800, // 1 Jan 1978
+	       283996800, // 1 Jan 1979
+	       315532800, // 1 Jan 1980
+	       362793600, // 1 Jul 1981
+	       394329600, // 1 Jul 1982
+	       425865600, // 1 Jul 1983
+	       489024000, // 1 Jul 1985
+	       567993600, // 1 Jan 1988
+	       631152000, // 1 Jan 1990
+	       662688000, // 1 Jan 1991
+	       709948800, // 1 Jul 1992
+	       741484800, // 1 Jul 1993
+	       773020800, // 1 Jul 1994
+	       820454400, // 1 Jan 1996
+	       867715200, // 1 Jul 1997
+	       915148800, // 1 Jan 1999
+	      1136073600, // 1 Jan 2006
+	      1230768000, // 1 Jan 2009
+	      1341100800, // 1 Jul 2012
+	      1435708800, // 1 Jul 2015
+	      1483228800, // 1 Jan 2017
+	    };
+
+	    auto __s = __ut.time_since_epoch().count();
+	    auto __pos = std::upper_bound(__leaps.begin(), __leaps.end(), __s);
+	    return {
+	      __pos != __leaps.begin() && __pos[-1] == __s,
+	      seconds{__pos - __leaps.begin()}
+	    };
+	  }
+	else
+	  {
+	    auto __s = chrono::time_point_cast<seconds>(__ut);
+	    return chrono::get_leap_second_info(__s);
+	  }
+      }
+
     /// @} group chrono
 #endif // C++20
   } // namespace chrono
diff --git a/libstdc++-v3/testsuite/std/time/clock/gps/1.cc b/libstdc++-v3/testsuite/std/time/clock/gps/1.cc
new file mode 100644
index 00000000000..9403ee1ecca
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/time/clock/gps/1.cc
@@ -0,0 +1,38 @@ 
+// { dg-options "-std=gnu++20" }
+// { dg-do run { target c++20 } }
+
+#include <chrono>
+#include <testsuite_hooks.h>
+
+void
+test01()
+{
+  using namespace std::chrono;
+
+  gps_seconds gps_epoch{0s};
+  utc_seconds gps_as_utc{sys_days{1980y/January/Sunday[1]}.time_since_epoch() + 9s};
+
+  VERIFY( clock_cast<utc_clock>(gps_epoch) == gps_as_utc );
+  VERIFY( gps_epoch == clock_cast<gps_clock>(gps_as_utc) );
+
+  tai_seconds tai_epoch{0s};
+  VERIFY( clock_cast<tai_clock>(clock_cast<gps_clock>(tai_epoch)) == tai_epoch );
+}
+
+void
+test02()
+{
+  using namespace std::chrono;
+
+  sys_days d{2022y/November/12};
+  VERIFY( clock_cast<system_clock>(clock_cast<gps_clock>(d)) == d );
+  gps_seconds t(1234567s);
+  VERIFY( clock_cast<gps_clock>(clock_cast<system_clock>(t)) == t );
+  VERIFY( clock_cast<gps_clock>(clock_cast<utc_clock>(t)) == t );
+}
+
+int main()
+{
+  test01();
+  test02();
+}
diff --git a/libstdc++-v3/testsuite/std/time/clock/tai/1.cc b/libstdc++-v3/testsuite/std/time/clock/tai/1.cc
new file mode 100644
index 00000000000..9b36f023c68
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/time/clock/tai/1.cc
@@ -0,0 +1,41 @@ 
+// { dg-options "-std=gnu++20" }
+// { dg-do run { target c++20 } }
+
+#include <chrono>
+#include <testsuite_hooks.h>
+
+void
+test01()
+{
+  using namespace std::chrono;
+
+  tai_seconds tai_epoch{0s};
+  utc_seconds tai_as_utc{sys_days{1958y/January/1}.time_since_epoch() - 10s};
+
+  VERIFY( clock_cast<utc_clock>(tai_epoch) == tai_as_utc );
+  VERIFY( tai_epoch == clock_cast<tai_clock>(tai_as_utc) );
+
+  sys_days y2k{2000y/January/1};
+  tai_seconds y2k_as_tai{clock_cast<tai_clock>(y2k)};
+  utc_seconds y2k_as_utc = utc_clock::from_sys(y2k);
+  VERIFY( clock_cast<utc_clock>(y2k_as_tai) == y2k_as_utc );
+  VERIFY( y2k_as_tai == clock_cast<tai_clock>(y2k_as_utc) );
+}
+
+void
+test02()
+{
+  using namespace std::chrono;
+
+  sys_days d{2022y/November/12};
+  VERIFY( clock_cast<system_clock>(clock_cast<tai_clock>(d)) == d );
+  tai_seconds t(1234567s);
+  VERIFY( clock_cast<tai_clock>(clock_cast<system_clock>(t)) == t );
+  VERIFY( clock_cast<tai_clock>(clock_cast<utc_clock>(t)) == t );
+}
+
+int main()
+{
+  test01();
+  test02();
+}
diff --git a/libstdc++-v3/testsuite/std/time/clock/utc/1.cc b/libstdc++-v3/testsuite/std/time/clock/utc/1.cc
new file mode 100644
index 00000000000..eef5f3c3a48
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/time/clock/utc/1.cc
@@ -0,0 +1,24 @@ 
+// { dg-options "-std=gnu++20" }
+// { dg-do run { target c++20 } }
+
+#include <chrono>
+#include <testsuite_hooks.h>
+
+void
+test01()
+{
+  using namespace std::chrono;
+
+  auto epoch = sys_seconds{sys_days{1970y/January/1}};
+  auto utc_epoch = clock_cast<utc_clock>(epoch);
+  VERIFY( utc_epoch.time_since_epoch() == 0s );
+
+  auto y2k = sys_seconds{sys_days{2000y/January/1}};
+  auto utc_y2k = clock_cast<utc_clock>(y2k);
+  VERIFY( utc_y2k.time_since_epoch() == 946'684'822s );
+}
+
+int main()
+{
+  test01();
+}