On Sun, 13 Nov 2022 at 01:17, Jonathan Wakely via Libstdc++
<libstd...@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();
+}

Reply via email to