public inbox for libstdc++-cvs@sourceware.org
help / color / mirror / Atom feed
* [gcc r13-4856] libstdc++: Add std::format support to <chrono>
@ 2022-12-22 23:34 Jonathan Wakely
  0 siblings, 0 replies; only message in thread
From: Jonathan Wakely @ 2022-12-22 23:34 UTC (permalink / raw)
  To: gcc-cvs, libstdc++-cvs

https://gcc.gnu.org/g:f99b94865fa629cc2cc937128a812b6a23038446

commit r13-4856-gf99b94865fa629cc2cc937128a812b6a23038446
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Thu Dec 22 01:29:22 2022 +0000

    libstdc++: Add std::format support to <chrono>
    
    This adds the operator<< overloads and std::formatter specializations
    required by C++20 so that <chrono> types can be written to ostreams and
    printed with std::format.
    
    libstdc++-v3/ChangeLog:
    
            * include/Makefile.am: Add new header.
            * include/Makefile.in: Regenerate.
            * include/std/chrono (operator<<): Move to new header.
            (nonexistent_local_time::_M_make_what_str): Define correctly.
            (ambiguous_local_time::_M_make_what_str): Likewise.
            * include/bits/chrono_io.h: New file.
            * src/c++20/tzdb.cc (operator<<(ostream&, const Rule&)): Use
            new ostream output for month and weekday types.
            * testsuite/20_util/duration/io.cc: Test std::format support.
            * testsuite/std/time/exceptions.cc: Check what() strings.
            * testsuite/std/time/syn_c++20.cc: Uncomment local_time_format.
            * testsuite/std/time/time_zone/get_info_local.cc: Enable check
            for formatted output of local_info objects.
            * testsuite/std/time/clock/file/io.cc: New test.
            * testsuite/std/time/clock/gps/io.cc: New test.
            * testsuite/std/time/clock/system/io.cc: New test.
            * testsuite/std/time/clock/tai/io.cc: New test.
            * testsuite/std/time/clock/utc/io.cc: New test.
            * testsuite/std/time/day/io.cc: New test.
            * testsuite/std/time/format.cc: New test.
            * testsuite/std/time/hh_mm_ss/io.cc: New test.
            * testsuite/std/time/month/io.cc: New test.
            * testsuite/std/time/weekday/io.cc: New test.
            * testsuite/std/time/year/io.cc: New test.
            * testsuite/std/time/year_month_day/io.cc: New test.

Diff:
---
 libstdc++-v3/include/Makefile.am                   |    1 +
 libstdc++-v3/include/Makefile.in                   |    1 +
 libstdc++-v3/include/bits/chrono_io.h              | 2469 ++++++++++++++++++++
 libstdc++-v3/include/std/chrono                    |  164 +-
 libstdc++-v3/src/c++20/tzdb.cc                     |   12 +-
 libstdc++-v3/testsuite/20_util/duration/io.cc      |   48 +
 libstdc++-v3/testsuite/std/time/clock/file/io.cc   |   23 +
 libstdc++-v3/testsuite/std/time/clock/gps/io.cc    |   24 +
 libstdc++-v3/testsuite/std/time/clock/system/io.cc |   72 +
 libstdc++-v3/testsuite/std/time/clock/tai/io.cc    |   24 +
 libstdc++-v3/testsuite/std/time/clock/utc/io.cc    |  120 +
 libstdc++-v3/testsuite/std/time/day/io.cc          |   75 +
 libstdc++-v3/testsuite/std/time/exceptions.cc      |    4 +-
 libstdc++-v3/testsuite/std/time/format.cc          |  117 +
 libstdc++-v3/testsuite/std/time/hh_mm_ss/io.cc     |   46 +
 libstdc++-v3/testsuite/std/time/month/io.cc        |   98 +
 libstdc++-v3/testsuite/std/time/syn_c++20.cc       |    3 +-
 .../testsuite/std/time/time_zone/get_info_local.cc |    2 -
 libstdc++-v3/testsuite/std/time/weekday/io.cc      |  101 +
 libstdc++-v3/testsuite/std/time/year/io.cc         |   89 +
 .../testsuite/std/time/year_month_day/io.cc        |  121 +
 21 files changed, 3465 insertions(+), 149 deletions(-)

diff --git a/libstdc++-v3/include/Makefile.am b/libstdc++-v3/include/Makefile.am
index 92b5450fc14..e91f4ddd4de 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 5d00f90a423..06589d53856 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_io.h b/libstdc++-v3/include/bits/chrono_io.h
new file mode 100644
index 00000000000..4e53cd4aa2e
--- /dev/null
+++ b/libstdc++-v3/include/bits/chrono_io.h
@@ -0,0 +1,2469 @@
+// <chrono> 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"); }
+
+  [[noreturn,__gnu__::__always_inline__]]
+  inline void
+  __invalid_chrono_spec()
+  { __throw_format_error("format error: chrono-format-spec not valid for "
+			 "argument type"); }
+
+  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, sys_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,
+    _ZonedDateTime = _DateTime | _TimeZone,
+    _Duration = 128 // special case
+  };
+
+  constexpr _ChronoParts
+  operator|(_ChronoParts __x, _ChronoParts __y)
+  { return static_cast<_ChronoParts>((int)__x | (int)__y); }
+
+  // TODO rename this to chrono::__formatter? or chrono::__detail::__formatter?
+  template<typename _CharT>
+    struct __formatter_chrono
+    {
+      using __string_view = basic_string_view<_CharT>;
+      using __string = basic_string<_CharT>;
+
+      template<typename _ParseContext>
+	constexpr typename _ParseContext::iterator
+	_M_parse(_ParseContext& __pc, _ChronoParts __parts)
+	{
+	  auto __first = __pc.begin();
+	  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;
+
+	  if (__parts & _ChronoParts::_Duration)
+	    {
+	      __first = __spec._M_parse_precision(__first, __last, __pc);
+	      if (__finished())
+		return __first;
+	    }
+
+	  __first = __spec._M_parse_locale(__first, __last);
+	  if (__finished())
+	    return __first;
+
+	  // Everything up to the end of the string or the first '}' is a
+	  // chrono-specs string. Check it is valid.
+	  {
+	    __string_view __str(__first, __last - __first);
+	    auto __end = __str.find('}');
+	    if (__end != __str.npos)
+	      {
+		__str.remove_suffix(__str.length() - __end);
+		__last = __first + __end;
+	      }
+	    if (__str.find('{') != __str.npos)
+	      __throw_format_error("chrono format error: '{' in chrono-specs");
+	  }
+
+	  // Parse chrono-specs in [first,last), checking 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++; // Skip leading '%'
+	  if (*__chrono_specs != '%')
+	    __throw_format_error("chrono format error: no '%' at start of "
+				     "chrono-specs");
+
+	  _CharT __mod{};
+	  bool __conv = true;
+	  int __needed = 0;
+
+	  while (__first != __last)
+	    {
+	      enum _Mods { _Mod_none, _Mod_E, _Mod_O, _Mod_E_O };
+	      _Mods __allowed_mods = _Mod_none;
+
+	      _CharT __c = *__first++;
+	      switch (__c)
+		{
+		case 'a':
+		case 'A':
+		  __needed = _Weekday;
+		  break;
+		case 'b':
+		case 'h':
+		case 'B':
+		  __needed = _Month;
+		  break;
+		case 'c':
+		  __needed = _DateTime;
+		  __allowed_mods = _Mod_E;
+		  break;
+		case 'C':
+		  __needed = _Year;
+		  __allowed_mods = _Mod_E;
+		  break;
+		case 'd':
+		case 'e':
+		  __needed = _Day;
+		  __allowed_mods = _Mod_O;
+		  break;
+		case 'D':
+		case 'F':
+		  __needed = _Date;
+		  break;
+		case 'g':
+		case 'G':
+		  __needed = _Date;
+		  break;
+		case 'H':
+		case 'I':
+		  __needed = _TimeOfDay;
+		  __allowed_mods = _Mod_O;
+		  break;
+		case 'j':
+		  if (!(__parts & _Duration))
+		    __needed = _Date;
+		  break;
+		case 'm':
+		  __needed = _Month;
+		  __allowed_mods = _Mod_O;
+		  break;
+		case 'M':
+		  __needed = _TimeOfDay;
+		  __allowed_mods = _Mod_O;
+		  break;
+		case 'p':
+		case 'r':
+		case 'R':
+		case 'T':
+		  __needed = _TimeOfDay;
+		  break;
+		case 'q':
+		case 'Q':
+		  __needed = _Duration;
+		  break;
+		case 'S':
+		  __needed = _TimeOfDay;
+		  __allowed_mods = _Mod_O;
+		  break;
+		case 'u':
+		case 'w':
+		  __needed = _Weekday;
+		  __allowed_mods = _Mod_O;
+		  break;
+		case 'U':
+		case 'V':
+		case 'W':
+		  __needed = _Date;
+		  __allowed_mods = _Mod_O;
+		  break;
+		case 'x':
+		  __needed = _Date;
+		  __allowed_mods = _Mod_E;
+		  break;
+		case 'X':
+		  __needed = _TimeOfDay;
+		  __allowed_mods = _Mod_E;
+		  break;
+		case 'y':
+		  __needed = _Year;
+		  __allowed_mods = _Mod_E_O;
+		  break;
+		case 'Y':
+		  __needed = _Year;
+		  __allowed_mods = _Mod_E;
+		  break;
+		case 'z':
+		  __needed = _TimeZone;
+		  __allowed_mods = _Mod_E;
+		  break;
+		case 'Z':
+		  __needed = _TimeZone;
+		  __allowed_mods = _Mod_E_O;
+		  break;
+		case 'n':
+		case 't':
+		case '%':
+		  break;
+		case 'O':
+		case 'E':
+		  __mod = __c;
+		  continue;
+		default:
+		  __throw_format_error("chrono format error: invalid "
+				       " specifier in chrono-specs");
+		}
+
+	      if ((__mod == 'E' && !(__allowed_mods & _Mod_E))
+		    || __mod == 'O' && !(__allowed_mods & _Mod_O))
+		__throw_format_error("chrono format error: invalid "
+				     " modifier in chrono-specs");
+	      __mod = _CharT();
+
+	      if ((__parts & __needed) != __needed)
+		__throw_format_error("chrono format error: format argument "
+				     "does not contain the information "
+				     "required by the chrono-specs");
+
+	      // Scan for next '%', ignoring literal-chars before it.
+	      size_t __pos = __string_view(__first, __last - __first).find('%');
+	      if (__pos == 0)
+		++__first;
+	      else
+		{
+		  if (__pos == __string_view::npos)
+		    {
+		      __first = __last;
+		      __conv = false;
+		    }
+		  else
+		    __first += __pos + 1;
+		}
+	    }
+
+	  // Check for a '%' conversion-spec without a type.
+	  if (__conv || __mod != _CharT())
+	    __throw_format_error("chrono format error: unescaped '%' in "
+				 "chrono-specs");
+
+	  _M_spec = __spec;
+	  _M_spec._M_chrono_specs = {__chrono_specs, __first - __chrono_specs};
+
+	  return __first;
+	}
+
+      // TODO this function template is instantiated for every different _Tp.
+      // Consider creating a polymorphic interface for calendar types so
+      // that we instantiate fewer different specializations. Similar to
+      // _Sink_iter for std::format. Replace each _S_year, _S_day etc. with
+      // member functions of that type.
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_format(const _Tp& __t, _FormatContext& __fc,
+		  bool __is_neg = false) const
+	{
+	  if constexpr (__is_specialization_of<_Tp, chrono::hh_mm_ss>)
+	    __is_neg = __t.is_negative();
+	  else if constexpr (!chrono::__is_duration_v<_Tp>)
+	    __is_neg = false;
+
+	  auto __first = _M_spec._M_chrono_specs.begin();
+	  const auto __last = _M_spec._M_chrono_specs.end();
+	  if (__first == __last)
+	    return _M_format_to_ostream(__t, __fc, __is_neg);
+
+	  _Sink_iter<_CharT> __out;
+	  __format::_Str_sink<_CharT> __sink;
+	  bool __write_direct = false;
+	  if constexpr (is_same_v<typename _FormatContext::iterator,
+				  _Sink_iter<_CharT>>)
+	    {
+	      if (_M_spec._M_width_kind == __format::_WP_none)
+		{
+		  __out = __fc.out();
+		  __write_direct = true;
+		}
+	      else
+		__out = __sink.out();
+	    }
+	  else
+	    __out = __sink.out();
+
+	  auto __print_sign = [&__is_neg, &__out] {
+	    if (__is_neg)
+	      {
+		*__out++ = _S_plus_minus[1];
+		__is_neg = false;
+	      }
+	    return std::move(__out);
+	  };
+
+	  // Characters to output for "%n", "%t" and "%%" specifiers.
+	  constexpr const _CharT* __literals = _GLIBCXX_WIDEN("\n\t%");
+
+	  ++__first; // Skip leading '%' at start of chrono-specs.
+
+	  _CharT __mod{};
+	  do
+	    {
+	      _CharT __c = *__first++;
+	      switch (__c)
+		{
+		case 'a':
+		case 'A':
+		  __out = _M_a_A(__t, std::move(__out), __fc, __c == 'A');
+		  break;
+		case 'b':
+		case 'h':
+		case 'B':
+		  __out = _M_b_B(__t, std::move(__out), __fc, __c == 'B');
+		  break;
+		case 'c':
+		  __out = _M_c(__t, std::move(__out), __fc, __mod == 'E');
+		  break;
+		case 'C':
+		case 'y':
+		case 'Y':
+		  __out = _M_C_y_Y(__t, std::move(__out), __fc, __c, __mod);
+		  break;
+		case 'd':
+		  // %d  The day of month as a decimal number.
+		  // %Od Locale's alternative representation.
+		  __out = _S_dd_zero_fill((unsigned)_S_day(__t),
+					  std::move(__out),
+					  __fc, __mod == 'O');
+		  break;
+		case 'D':
+		  __out = _M_D(__t, std::move(__out), __fc);
+		  break;
+		case 'e':
+		  __out = _M_e(__t, std::move(__out), __fc, __mod == 'O');
+		  break;
+		case 'F':
+		  __out = _M_F(__t, std::move(__out), __fc);
+		  break;
+		case 'g':
+		case 'G':
+		  __out = _M_g_G(__t, std::move(__out), __fc, __c == 'G');
+		  break;
+		case 'H':
+		  // %H  The hour (24-hour clock) as a decimal number.
+		  // %OH Locale's alternative representation.
+		  __out = _S_dd_zero_fill(_S_hms(__t).hours().count(),
+					  __print_sign(), __fc, __mod == 'O');
+		  break;
+		case 'I':
+		  __out = _M_I(__t, __print_sign(), __fc, __mod == 'O');
+		  break;
+		case 'j':
+		  __out = _M_j(__t, __print_sign(), __fc);
+		  break;
+		case 'm':
+		  // %m  month as a decimal number.
+		  // %Om Locale's alternative representation.
+		  __out = _S_dd_zero_fill((unsigned)_S_month(__t),
+					  std::move(__out), __fc,
+					  __mod == 'O');
+		  break;
+		case 'M':
+		  // %M  The minute as a decimal number.
+		  // %OM Locale's alternative representation.
+		  __out = _S_dd_zero_fill(_S_hms(__t).minutes().count(),
+					  __print_sign(), __fc, __mod == 'O');
+		  break;
+		case 'p':
+		  __out = _M_p(__t, std::move(__out), __fc);
+		  break;
+		case 'q':
+		  __out = _M_q(__t, std::move(__out), __fc);
+		  break;
+		case 'Q':
+		  // %Q The duration's numeric value.
+		  if constexpr (chrono::__is_duration_v<_Tp>)
+		    __out = std::format_to(__print_sign(), _S_empty_spec,
+					   __t.count());
+		  else
+		    __throw_format_error("chrono format error: argument is "
+					 "not a duration");
+		  break;
+		case 'r':
+		  __out = _M_r(__t, __print_sign(), __fc);
+		  break;
+		case 'R':
+		case 'T':
+		  __out = _M_R_T(__t, __print_sign(), __fc, __c == 'T');
+		  break;
+		case 'S':
+		  __out = _M_S(__t, __print_sign(), __fc, __mod == 'O');
+		  break;
+		case 'u':
+		case 'w':
+		  __out = _M_u_w(__t, std::move(__out), __fc, __c, __mod == 'O');
+		  break;
+		case 'U':
+		case 'V':
+		case 'W':
+		  __out = _M_U_V_W(__t, std::move(__out), __fc, __c,
+				   __mod == 'O');
+		  break;
+		case 'x':
+		  __out = _M_x(__t, std::move(__out), __fc, __mod == 'E');
+		  break;
+		case 'X':
+		  __out = _M_X(__t, __print_sign(), __fc, __mod == 'E');
+		  break;
+		case 'z':
+		  __out = _M_z(__t, std::move(__out), __fc, (bool)__mod);
+		  break;
+		case 'Z':
+		  __out = _M_Z(__t, std::move(__out), __fc);
+		  break;
+		case 'n':
+		  *__out++ = __literals[0];
+		  break;
+		case 't':
+		  *__out++ = __literals[1];
+		  break;
+		case '%':
+		  *__out++ = __literals[2];
+		  break;
+		case 'O':
+		case 'E':
+		  __mod = __c;
+		  continue;
+		case '}':
+		  __first = __last;
+		  break;
+		}
+	      __mod = _CharT();
+	      // Scan for next '%' and write out everything before it.
+	      __string_view __str(__first, __last - __first);
+	      size_t __pos = __str.find('%');
+	      if (__pos == 0)
+		++__first;
+	      else
+		{
+		  if (__pos == __str.npos)
+		    __first = __last;
+		  else
+		    {
+		      __str.remove_suffix(__str.length() - __pos);
+		      __first += __pos + 1;
+		    }
+		  __out = __format::__write(std::move(__out), __str);
+		}
+	    }
+	  while (__first != __last);
+
+	  if constexpr (is_same_v<typename _FormatContext::iterator,
+				  _Sink_iter<_CharT>>)
+	    if (__write_direct)
+	      return __out;
+
+	  auto __str = std::move(__sink).get();
+	  return __format::__write_padded_as_spec(__str, __str.size(),
+						  __fc, _M_spec);
+	}
+
+      _ChronoSpec<_CharT> _M_spec;
+
+    private:
+      // Return the formatting locale.
+      template<typename _FormatContext>
+	std::locale
+	_M_locale(_FormatContext& __fc) const
+	{
+	  if (!_M_spec._M_localized)
+	    return std::locale::classic();
+	  else
+	    return __fc.locale();
+	}
+
+      // TODO: consider moving body of every operator<< into this function
+      // and use std::format("{}", t) to implement those operators. That
+      // would avoid std::format("{}", t) calling operator<< which calls
+      // std::format again.
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_format_to_ostream(const _Tp& __t, _FormatContext& __fc,
+			     bool __is_neg) const
+	{
+	  using ::std::chrono::__detail::__utc_leap_second;
+	  using ::std::chrono::__detail::__local_time_fmt;
+
+	  if constexpr (__is_specialization_of<_Tp, __local_time_fmt>)
+	    return _M_format_to_ostream(__t._M_time, __fc, false);
+	  else
+	    {
+	      basic_ostringstream<_CharT> __os;
+	      __os.imbue(_M_locale(__fc));
+
+	      if constexpr (__is_specialization_of<_Tp, __utc_leap_second>)
+		__os << __t._M_date << ' ' << __t._M_time;
+	      else
+		{
+		  if (__is_neg) [[unlikely]]
+		    __os << _S_plus_minus[1];
+		  __os << __t;
+		}
+
+	      auto __str = std::move(__os).str();
+	      return __format::__write_padded_as_spec(__str, __str.size(),
+						      __fc, _M_spec);
+	    }
+	}
+
+      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];
+      static constexpr _CharT _S_space = _S_chars[14];
+      static constexpr const _CharT* _S_empty_spec = _S_chars + 15;
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_a_A(const _Tp& __t, typename _FormatContext::iterator __out,
+	       _FormatContext& __ctx, bool __full) const
+	{
+	  // %a Locale's abbreviated weekday name.
+	  // %A Locale's full weekday name.
+	  chrono::weekday __wd = _S_weekday(__t);
+	  if (!__wd.ok())
+	    __throw_format_error("format error: invalid weekday");
+
+	  locale __loc = _M_locale(__ctx);
+	  const auto& __tp = use_facet<__timepunct<_CharT>>(__loc);
+	  const _CharT* __days[7];
+	  if (__full)
+	    __tp._M_days(__days);
+	  else
+	    __tp._M_days_abbreviated(__days);
+	  __string_view __str(__days[__wd.c_encoding()]);
+	  return __format::__write(std::move(__out), __str);
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_b_B(const _Tp& __t, typename _FormatContext::iterator __out,
+	       _FormatContext& __ctx, bool __full) const
+	{
+	  // %b Locale's abbreviated month name.
+	  // %B Locale's full month name.
+	  chrono::month __m = _S_month(__t);
+	  if (!__m.ok())
+	    __throw_format_error("format error: invalid month");
+	  locale __loc = _M_locale(__ctx);
+	  const auto& __tp = use_facet<__timepunct<_CharT>>(__loc);
+	  const _CharT* __months[12];
+	  if (__full)
+	    __tp._M_months(__months);
+	  else
+	    __tp._M_months_abbreviated(__months);
+	  __string_view __str(__months[(unsigned)__m - 1]);
+	  return __format::__write(std::move(__out), __str);
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_c(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx, bool __mod = false) const
+	{
+	  // %c  Locale's date and time representation.
+	  // %Ec Locale's alternate date and time representation.
+
+	  locale __loc = _M_locale(__ctx);
+	  const auto& __tp = use_facet<__timepunct<_CharT>>(__loc);
+	  const _CharT* __formats[2];
+	  __tp._M_date_time_formats(__formats);
+	  const _CharT* __rep = __formats[__mod];
+	  if (!*__rep)
+	    __rep = _GLIBCXX_WIDEN("%a %b %e %H:%M:%S %Y");
+	  basic_string<_CharT> __fmt(_S_empty_spec);
+	  __fmt.insert(1u, 1u, _S_colon);
+	  __fmt.insert(2u, __rep);
+	  return std::vformat_to(std::move(__out), __loc, __fmt,
+				 std::make_format_args<_FormatContext>(__t));
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_C_y_Y(const _Tp& __t, typename _FormatContext::iterator __out,
+	       _FormatContext& __ctx, char __conv, char __mod = 0) const
+	{
+	  // %C  Year divided by 100 using floored division.
+	  // %EC Locale's alternative preresentation of the century (era name).
+	  // %y  Last two decimal digits of the year.
+	  // %OY Locale's alternative represenation.
+	  // %Ey Locale's alternative representation of offset from %EC.
+	  // %Y  Year as a decimal number.
+	  // %EY Locale's alternative full year represenation.
+
+	  chrono::year __y = _S_year(__t);
+
+	  if (__mod == 'E')
+	    {
+	      // TODO: %EC, %Ey or %EY
+	      // return __out;
+	    }
+
+	  basic_string<_CharT> __s;
+	  int __yi = (int)__y;
+	  const bool __is_neg = __yi < 0;
+	  __yi = __builtin_abs(__yi);
+
+	  if (__conv == 'Y' || __conv == 'C')
+	    {
+	      if (__is_neg)
+		__s.assign(1, _S_plus_minus[1]);
+	      int __ci = __yi / 100;
+	      if (__ci >= 100) [[unlikely]]
+		{
+		  __s += std::format(_S_empty_spec, __ci / 100);
+		  __ci %= 100;
+		}
+	      __s += _S_two_digits(__ci);
+	    }
+
+	  if (__conv == 'Y' || __conv == 'y')
+	    __s += _S_two_digits(__yi % 100);
+
+	  if (__mod == 'O') // %OY
+	    _S_altnum(_M_locale(__ctx), __s, __is_neg);
+
+	  return __format::__write(std::move(__out), __string_view(__s));
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_D(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext&) const
+	{
+	  auto __ymd = _S_date(__t);
+	  basic_string<_CharT> __s;
+#if ! _GLIBCXX_USE_CXX11_ABI
+	  __s.reserve(8);
+#endif
+	  __s = _S_two_digits((unsigned)__ymd.month());
+	  __s += _S_slash;
+	  __s += _S_two_digits((unsigned)__ymd.day());
+	  __s += _S_slash;
+	  __s += _S_two_digits(__builtin_abs((int)__ymd.year()) % 100);
+	  return __format::__write(std::move(__out), __string_view(__s));
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_e(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx, bool __mod = false) const
+	{
+	  // %e  Day of month as decimal number, padded with space.
+	  // %Oe Locale's alternative digits.
+	  chrono::day __d = _S_day(__t);
+	  unsigned __i = (unsigned)__d;
+	  auto __sv = _S_two_digits(__i);
+	  basic_string<_CharT> __s;
+	  if (__mod)
+	    __sv = _S_altnum(_M_locale(__ctx), __s.assign(__sv));
+	  if (__i < 10)
+	    __sv = __s = {_S_space, __sv[1]};
+	  return __format::__write(std::move(__out), __sv);
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_F(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext&) const
+	{
+	  auto __ymd = _S_date(__t);
+	  basic_string<_CharT> __s;
+#if ! _GLIBCXX_USE_CXX11_ABI
+	  __s.reserve(11);
+#endif
+	  __s += std::format(_GLIBCXX_WIDEN("{:04d}-  -  "), (int)__ymd.year());
+	  auto __sv = _S_two_digits((unsigned)__ymd.month());
+	  __s[__s.size() - 5] = __sv[0];
+	  __s[__s.size() - 4] = __sv[1];
+	  __sv = _S_two_digits((unsigned)__ymd.day());
+	  __s[__s.size() - 2] = __sv[0];
+	  __s[__s.size() - 1] = __sv[1];
+	  __sv = __s;
+	  return __format::__write(std::move(__out), __sv);
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_g_G(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx, bool __full) const
+	{
+	  // %g last two decimal digits of the ISO week-based year.
+	  // %G ISO week-based year.
+	  using namespace chrono;
+	  auto __d = _S_days(__t);
+	  // Move to nearest Thursday:
+	  __d -= (weekday(__d) - Monday) - days(3);
+	  // ISO week-based year is the year that contains that Thursday:
+	  year __y = year_month_day(__d).year();
+	  return _M_C_y_Y(__y, std::move(__out), __ctx, "yY"[__full]);
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_I(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx, bool __mod = false) const
+	{
+	  auto __hms = _S_hms(__t);
+	  int __i = __hms.hours().count();
+	  if (__i == 0)
+	    __i = 12;
+	  else if (__i > 12)
+	    __i -= 12;
+	  auto __sv = _S_two_digits(__i);
+	  basic_string<_CharT> __s;
+	  if (__mod)
+	    __sv = _S_altnum(_M_locale(__ctx), __s.assign(__sv));
+	  return __format::__write(std::move(__out), __sv);
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_j(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx) const
+	{
+	  if constexpr (chrono::__is_duration_v<_Tp>)
+	    {
+	      // Decimal number of days, without padding.
+	      unsigned __d = chrono::duration_cast<chrono::days>(__t).count();
+	      return std::format_to(std::move(__out), _S_empty_spec, __d);
+	    }
+	  else
+	    {
+	      // Day of the year as a decimal number, padding with zero.
+	      using namespace chrono;
+	      auto __day = _S_days(__t);
+	      auto __ymd = _S_date(__t);
+	      days __d;
+	      // See "Calculating Ordinal Dates" at
+	      // https://github.com/HowardHinnant/date/wiki/Examples-and-Recipes
+	      if constexpr (is_same_v<typename decltype(__day)::clock, local_t>)
+		__d = __day - local_days(__ymd.year()/January/0);
+	      else
+		__d = __day - sys_days(__ymd.year()/January/0);
+	      return std::format_to(std::move(__out), _GLIBCXX_WIDEN("{:03d}"),
+				    __d.count());
+	    }
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_p(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx) const
+	{
+	  // %p The locale's equivalent of the AM/PM designations.
+	  auto __hms = _S_hms(__t);
+	  locale __loc = _M_locale(__ctx);
+	  const auto& __tp = use_facet<__timepunct<_CharT>>(__loc);
+	  const _CharT* __ampm[2];
+	  __tp._M_am_pm(__ampm);
+	  return std::format_to(std::move(__out), _S_empty_spec,
+				__ampm[__hms.hours().count() >= 12]);
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_q(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx) const
+	{
+	  // %q The duration's unit suffix
+	  if constexpr (!chrono::__is_duration_v<_Tp>)
+	    __throw_format_error("format error: argument is not a duration");
+	  else
+	    {
+	      using period = typename _Tp::period;
+	      char __buf[sizeof("[/]s") + 2 * numeric_limits<intmax_t>::digits10];
+	      constexpr size_t __n = sizeof(__buf);
+	      auto __s = chrono::__detail::__units_suffix<period, _CharT>(__buf,
+									  __n);
+	      if constexpr (is_same_v<decltype(__s), const _CharT*>)
+		return std::format_to(std::move(__out), _S_empty_spec, __s);
+	      else
+		{
+		  // Suffix was written to __buf as narrow string.
+		  _CharT __wbuf[__n];
+		  size_t __len = __builtin_strlen(__buf);
+		  locale __loc = _M_locale(__ctx);
+		  auto& __ct = use_facet<ctype<_CharT>>(__loc);
+		  __ct.widen(__buf, __len, __wbuf);
+		  __wbuf[__len] = 0;
+		  return std::format_to(std::move(__out), _S_empty_spec,
+					__wbuf);
+		}
+	    }
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_r(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx) const
+	{
+	  // %r locale's 12-hour clock time.
+	  locale __loc = _M_locale(__ctx);
+	  const auto& __tp = use_facet<__timepunct<_CharT>>(__loc);
+	  const _CharT* __ampm_fmt;
+	  __tp._M_am_pm_format(&__ampm_fmt);
+	  basic_string<_CharT> __fmt(_S_empty_spec);
+	  __fmt.insert(1u, 1u, _S_colon);
+	  __fmt.insert(2u, __ampm_fmt);
+	  return std::vformat_to(std::move(__out), __fmt,
+				 std::make_format_args<_FormatContext>(__t));
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_R_T(const _Tp& __t, typename _FormatContext::iterator __out,
+	       _FormatContext& __ctx, bool __secs) const
+	{
+	  // %R Equivalent to %H:%M
+	  // %T Equivalent to %H:%M:%S
+	  auto __hms = _S_hms(__t);
+
+	  basic_string<_CharT> __s;
+#if ! _GLIBCXX_USE_CXX11_ABI
+	  __s.reserve(11);
+#endif
+	  __s = std::format(_GLIBCXX_WIDEN("{:02d}:00"), __hms.hours().count());
+	  auto __sv = _S_two_digits(__hms.minutes().count());
+	  __s[__s.size() - 2] = __sv[0];
+	  __s[__s.size() - 1] = __sv[1];
+	  __sv = __s;
+	  __out = __format::__write(std::move(__out), __sv);
+	  if (__secs)
+	    {
+	      *__out++ = _S_colon;
+	      __out = _M_S(__hms, std::move(__out), __ctx);
+	    }
+	  return __out;
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_S(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx, bool __mod = false) const
+	{
+	  // %S  Seconds as a decimal number.
+	  // %OS (TODO) The locale's alternative representation.
+	  auto __hms = _S_hms(__t);
+	  __out = _S_dd_zero_fill(__hms.seconds().count(),
+				  std::move(__out), __ctx, __mod);
+	  using rep = typename decltype(__hms)::precision::rep;
+	  if constexpr (__hms.fractional_width != 0)
+	    {
+	      locale __loc = _M_locale(__ctx);
+	      auto __ss = __hms.subseconds();
+	      if constexpr (is_floating_point_v<rep>)
+		{
+		  __out = std::format_to(__loc, std::move(__out),
+					 _GLIBCXX_WIDEN("{:.{}Lg}"),
+					 __ss.count(),
+					 __hms.fractional_width);
+		}
+	      else if constexpr (is_integral_v<rep>)
+		{
+		  const auto& __np
+		    = use_facet<numpunct<_CharT>>(__loc);
+		  __out = std::format_to(std::move(__out),
+					 _GLIBCXX_WIDEN("{}{:0{}}"),
+					 __np.decimal_point(),
+					 __ss.count(),
+					 __hms.fractional_width);
+		}
+	      else
+		{
+		  const auto& __np
+		    = use_facet<numpunct<_CharT>>(__loc);
+		  *__out++ = __np.decimal_point();
+		  auto __str = std::format(_S_empty_spec, __ss.count());
+		  __out = std::format_to(_GLIBCXX_WIDEN("{:0>{}s}"),
+							__str,
+							__hms.fractional_width);
+		}
+	    }
+	  return __out;
+	}
+
+      // %t handled in _M_format
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_u_w(const _Tp& __t, typename _FormatContext::iterator __out,
+	       _FormatContext& __ctx, _CharT __conv, bool __mod = false) const
+	{
+	  // %u  ISO weekday as a decimal number (1-7), where Monday is 1.
+	  // %Ou Locale's alternative numeric rep.
+	  // %w  Weekday as a decimal number (0-6), where Sunday is 0.
+	  // %Ow Locale's alternative numeric rep.
+	  chrono::weekday __wd = _S_weekday(__t);
+	  unsigned __wdi = __conv == 'u' ? __wd.iso_encoding()
+					 : __wd.c_encoding();
+	  basic_string<_CharT> __s(1, _S_digit(__wdi));
+	  if (__mod)
+	    _S_altnum(_M_locale(__ctx), __s);
+	  return __format::__write(std::move(__out), __string_view(__s));
+	  return __out;
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_U_V_W(const _Tp& __t, typename _FormatContext::iterator __out,
+		 _FormatContext& __ctx, _CharT __conv, bool __mod = false) const
+	{
+	  // %U  Week number of the year as a decimal number, from first Sunday.
+	  // %OU Locale's alternative numeric rep.
+	  // %V  ISO week-based week number as a decimal number.
+	  // %OV Locale's alternative numeric rep.
+	  // %W  Week number of the year as a decimal number, from first Monday.
+	  // %OW Locale's alternative numeric rep.
+	  using namespace chrono;
+	  auto __d = _S_days(__t);
+	  using _TDays = decltype(__d); // Either sys_days or local_days.
+
+	  _TDays __first; // First day of week 1.
+	  if (__conv == 'V') // W01 begins on Monday before first Thursday.
+	    {
+	      // Move to nearest Thursday:
+	      __d -= (weekday(__d) - Monday) - days(3);
+	      // ISO week of __t is number of weeks since January 1 of the
+	      // same year as that nearest Thursday.
+	      __first = _TDays(year_month_day(__d).year()/January/1);
+	    }
+	  else
+	    {
+	      year __y;
+	      if constexpr (requires { __t.year(); })
+		__y = __t.year();
+	      else
+		__y = year_month_day(__d).year();
+	      const weekday __weekstart = __conv == 'U' ? Sunday : Monday;
+	      __first = _TDays(__y/January/__weekstart[1]);
+	    }
+	  auto __weeks = chrono::floor<weeks>(__d - __first);
+	  __string_view __sv = _S_two_digits(__weeks.count() + 1);
+	  basic_string<_CharT> __s;
+	  if (__mod)
+	    __sv = _S_altnum(_M_locale(__ctx), __s.assign(__sv));
+	  return __format::__write(std::move(__out), __sv);
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_x(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx, bool __mod = false) const
+	{
+	  // %x  Locale's date rep
+	  // %Ex Locale's alternative date representation.
+	  locale __loc = _M_locale(__ctx);
+	  const auto& __tp = use_facet<__timepunct<_CharT>>(__loc);
+	  const _CharT* __date_reps[2];
+	  __tp._M_date_formats(__date_reps);
+	  const _CharT* __rep = __date_reps[__mod];
+	  if (!*__rep)
+	    return _M_D(__t, std::move(__out), __ctx);
+
+	  basic_string<_CharT> __fmt(_S_empty_spec);
+	  __fmt.insert(1u, 1u, _S_colon);
+	  __fmt.insert(2u, __rep);
+	  return std::vformat_to(std::move(__out), __fmt,
+				 std::make_format_args<_FormatContext>(__t));
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_X(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx, bool __mod = false) const
+	{
+	  // %X  Locale's time rep
+	  // %EX Locale's alternative time representation.
+	  locale __loc = _M_locale(__ctx);
+	  const auto& __tp = use_facet<__timepunct<_CharT>>(__loc);
+	  const _CharT* __time_reps[2];
+	  __tp._M_time_formats(__time_reps);
+	  const _CharT* __rep = __time_reps[__mod];
+	  if (!*__rep)
+	    return _M_R_T(__t, std::move(__out), __ctx, true);
+
+	  basic_string<_CharT> __fmt(_S_empty_spec);
+	  __fmt.insert(1u, 1u, _S_colon);
+	  __fmt.insert(2u, __rep);
+	  return std::vformat_to(std::move(__out), __fmt,
+				 std::make_format_args<_FormatContext>(__t));
+	}
+
+      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::__utc_leap_second;
+	  using ::std::chrono::__detail::__local_time_fmt;
+
+	  auto __utc = __mod ? __string_view(_GLIBCXX_WIDEN("+00:00"), 6)
+			     : __string_view(_GLIBCXX_WIDEN("+0000"), 5);
+
+	  if constexpr (chrono::__is_time_point_v<_Tp>)
+	    {
+	      if constexpr (is_same_v<typename _Tp::clock,
+				      chrono::system_clock>)
+		return __format::__write(std::move(__out), __utc);
+	    }
+	  else if constexpr (__is_specialization_of<_Tp, __local_time_fmt>)
+	    {
+	      if (__t._M_offset_sec)
+		{
+		  auto __sv = __utc;
+		  basic_string<_CharT> __s;
+		  if (*__t._M_offset_sec != 0s)
+		    {
+		      chrono:: hh_mm_ss __hms(*__t._M_offset_sec);
+		      __s = _S_plus_minus[__hms.is_negative()];
+		      __s += _S_two_digits(__hms.hours().count());
+		      if (__mod)
+			__s += _S_colon;
+		      __s += _S_two_digits(__hms.minutes().count());
+		      __sv = __s;
+		    }
+		  return __format::__write(std::move(__out), __sv);
+		}
+	    }
+	  else if constexpr (__is_specialization_of<_Tp, __utc_leap_second>)
+	    return __format::__write(std::move(__out), __utc);
+
+	  __no_timezone_available();
+	}
+
+      template<typename _Tp, typename _FormatContext>
+	typename _FormatContext::iterator
+	_M_Z(const _Tp& __t, typename _FormatContext::iterator __out,
+	     _FormatContext& __ctx) const
+	{
+	  using ::std::chrono::__detail::__utc_leap_second;
+	  using ::std::chrono::__detail::__local_time_fmt;
+
+	  __string_view __utc(_GLIBCXX_WIDEN("UTC"), 3);
+	  if constexpr (chrono::__is_time_point_v<_Tp>)
+	    {
+	      if constexpr (is_same_v<typename _Tp::clock,
+				      chrono::system_clock>)
+		return __format::__write(std::move(__out), __utc);
+	    }
+	  else if constexpr (__is_specialization_of<_Tp, __local_time_fmt>)
+	    {
+	      if (__t._M_abbrev)
+		{
+		  __string_view __wsv;
+		  if constexpr (is_same_v<_CharT, char>)
+		    __wsv = *__t._M_abbrev;
+		  else
+		    {
+		      string_view __sv = *__t._M_abbrev;
+		      basic_string<_CharT> __ws(__sv.size());
+		      auto& __ct = use_facet<ctype<_CharT>>(_M_locale(__ctx));
+		      __ct.widen(__sv.data(), __sv.size(), __ws.data());
+		      __wsv = __ws;
+		    }
+		  return __format::__write(std::move(__out), __wsv);
+		}
+	    }
+	  else if constexpr (__is_specialization_of<_Tp, __utc_leap_second>)
+	    return __format::__write(std::move(__out), __utc);
+
+	  __no_timezone_available();
+	}
+
+      // %% handled in _M_format
+
+      // A single digit character in the range '0'..'9'.
+      static _CharT
+      _S_digit(int __n) noexcept
+      {
+	// Extra 9s avoid past-the-end read on bad input.
+	return _GLIBCXX_WIDEN("0123456789999999")[__n & 0xf];
+      }
+
+      // A string view of two digit characters, "00".."99".
+      static basic_string_view<_CharT>
+      _S_two_digits(int __n) noexcept
+      {
+	return {
+	  _GLIBCXX_WIDEN("0001020304050607080910111213141516171819"
+			 "2021222324252627282930313233343536373839"
+			 "4041424344454647484950515253545556575859"
+			 "6061626364656667686970717273747576777879"
+			 "8081828384858687888990919293949596979899"
+			 "9999999999999999999999999999999999999999"
+			 "9999999999999999") + 2 * (__n & 0x7f),
+	  2
+	};
+      }
+
+      // Convert a numeric string to the locale's alternative numeric symbols.
+      static basic_string_view<_CharT>
+      _S_altnum(const locale& __loc, basic_string<_CharT>& __s,
+		bool __is_neg = false)
+      {
+	if (__loc == locale::classic())
+	  return __s;
+
+#if 0 // TODO how can we access numpunct_cache?! Need to go via std::time_put?
+	auto& __np = use_facet<__numpunct_cache<_CharT>>(__loc);
+	auto __nums = __np._M_atoms_out; // alts for "-+xX01234..."
+	if (__is_neg)
+	  __s[0] = __nums[0];
+	__nums += 4; // now points to alternate digits
+	for (int __i = __is_neg; __i < __s.size(); ++__i)
+	  __s[__i] = __nums[__s[__i] - '0'];
+#endif
+	return __s;
+      }
+
+      // Write two digits, zero-filled.
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	_S_dd_zero_fill(int __val, typename _FormatContext::iterator __out,
+			_FormatContext& __ctx, bool __alt_num) const
+	{
+	  auto __sv = _S_two_digits(__val);
+	  basic_string<_CharT> __s;
+	  if (__alt_num)
+	    __sv = _S_altnum(_M_locale(__ctx), __s.assign(__sv));
+	  return __format::__write(std::move(__out), __sv);
+	}
+
+      // Accessors for the components of chrono types:
+
+      // Returns a hh_mm_ss.
+      template<typename _Tp>
+	static decltype(auto)
+	_S_hms(const _Tp& __t)
+	{
+	  using ::std::chrono::__detail::__utc_leap_second;
+	  using ::std::chrono::__detail::__local_time_fmt;
+
+	  if constexpr (__is_specialization_of<_Tp, chrono::hh_mm_ss>)
+	    return __t;
+	  else if constexpr (__is_specialization_of<_Tp, __utc_leap_second>)
+	    return __t._M_time;
+	  else if constexpr (chrono::__is_duration_v<_Tp>)
+	    return chrono::hh_mm_ss<_Tp>(__t);
+	  else if constexpr (chrono::__is_time_point_v<_Tp>)
+	    return chrono::hh_mm_ss(__t - chrono::floor<chrono::days>(__t));
+	  else if constexpr (__is_specialization_of<_Tp, __local_time_fmt>)
+	    return _S_hms(__t._M_time);
+	  else
+	    {
+	      __invalid_chrono_spec();
+	      return chrono::hh_mm_ss<chrono::seconds>();
+	    }
+	}
+
+      // Returns a sys_days or local_days.
+      template<typename _Tp>
+	static auto
+	_S_days(const _Tp& __t)
+	{
+	  using namespace chrono;
+	  using ::std::chrono::__detail::__utc_leap_second;
+	  using ::std::chrono::__detail::__local_time_fmt;
+
+	  if constexpr (__is_time_point_v<_Tp>)
+	    return chrono::floor<days>(__t);
+	  else if constexpr (__is_specialization_of<_Tp, __utc_leap_second>)
+	    return __t._M_date;
+	  else if constexpr (__is_specialization_of<_Tp, __local_time_fmt>)
+	    return chrono::floor<days>(__t._M_time);
+	  else if constexpr (is_same_v<_Tp, year_month_day>
+			       || is_same_v<_Tp, year_month_day_last>
+			       || is_same_v<_Tp, year_month_weekday>
+			       || is_same_v<_Tp, year_month_weekday_last>)
+	    return sys_days(__t);
+	  else
+	    {
+	      if constexpr (__is_duration_v<_Tp>)
+		__not_valid_for_duration();
+	      else
+		__invalid_chrono_spec();
+	      return chrono::sys_days();
+	    }
+	}
+
+      // Returns a year_month_day.
+      template<typename _Tp>
+	static chrono::year_month_day
+	_S_date(const _Tp& __t)
+	{
+	  if constexpr (is_same_v<_Tp, chrono::year_month_day>)
+	    return __t;
+	  else
+	    return chrono::year_month_day(_S_days(__t));
+	}
+
+      template<typename _Tp>
+	static chrono::day
+	_S_day(const _Tp& __t)
+	{
+	  using namespace chrono;
+
+	  if constexpr (is_same_v<_Tp, day>)
+	    return __t;
+	  else if constexpr (requires { __t.day(); })
+	    return __t.day();
+	  else
+	    return _S_date(__t).day();
+	}
+
+      template<typename _Tp>
+	static chrono::month
+	_S_month(const _Tp& __t)
+	{
+	  using namespace chrono;
+
+	  if constexpr (is_same_v<_Tp, month>)
+	    return __t;
+	  else if constexpr (requires { __t.month(); })
+	    return __t.month();
+	  else
+	    return _S_date(__t).month();
+	}
+
+      template<typename _Tp>
+	static chrono::year
+	_S_year(const _Tp& __t)
+	{
+	  using namespace chrono;
+
+	  if constexpr (is_same_v<_Tp, year>)
+	    return __t;
+	  else if constexpr (requires { __t.year(); })
+	    return __t.year();
+	  else
+	    return _S_date(__t).year();
+	}
+
+      template<typename _Tp>
+	static chrono::weekday
+	_S_weekday(const _Tp& __t)
+	{
+	  using namespace ::std::chrono;
+	  using ::std::chrono::__detail::__local_time_fmt;
+
+	  if constexpr (is_same_v<_Tp, weekday>)
+	    return __t;
+	  else if constexpr (requires { __t.weekday(); })
+	    return __t.weekday();
+	  else if constexpr (is_same_v<_Tp, month_weekday>)
+	    return __t.weekday_indexed().weekday();
+	  else if constexpr (is_same_v<_Tp, month_weekday_last>)
+	    return __t.weekday_last().weekday();
+	  else
+	    return weekday(_S_days(__t));
+	}
+    };
+
+} // 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)
+      {
+	using namespace __format;
+	auto __it = _M_f._M_parse(__pc, _Duration|_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(chrono::abs(__d), __fc, __d < __d.zero());
+	}
+
+    private:
+      __format::__formatter_chrono<_CharT> _M_f;
+    };
+
+  template<typename _CharT>
+    struct formatter<chrono::day, _CharT>
+    {
+      template<typename _ParseContext>
+	constexpr typename _ParseContext::iterator
+	parse(_ParseContext& __pc)
+	{ return _M_f._M_parse(__pc, __format::_Day); }
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::day& __t, _FormatContext& __fc) const
+	{ return _M_f._M_format(__t, __fc); }
+
+    private:
+      __format::__formatter_chrono<_CharT> _M_f;
+    };
+
+  template<typename _CharT>
+    struct formatter<chrono::month, _CharT>
+    {
+      template<typename _ParseContext>
+	constexpr typename _ParseContext::iterator
+	parse(_ParseContext& __pc)
+	{ return _M_f._M_parse(__pc, __format::_Month); }
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::month& __t, _FormatContext& __fc) const
+	{ return _M_f._M_format(__t, __fc); }
+
+    private:
+      __format::__formatter_chrono<_CharT> _M_f;
+    };
+
+  template<typename _CharT>
+    struct formatter<chrono::year, _CharT>
+    {
+      template<typename _ParseContext>
+	constexpr typename _ParseContext::iterator
+	parse(_ParseContext& __pc)
+	{ return _M_f._M_parse(__pc, __format::_Year); }
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::year& __t, _FormatContext& __fc) const
+	{ return _M_f._M_format(__t, __fc); }
+
+    private:
+      __format::__formatter_chrono<_CharT> _M_f;
+    };
+
+  template<typename _CharT>
+    struct formatter<chrono::weekday, _CharT>
+    {
+      template<typename _ParseContext>
+	constexpr typename _ParseContext::iterator
+	parse(_ParseContext& __pc)
+	{ return _M_f._M_parse(__pc, __format::_Weekday); }
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::weekday& __t, _FormatContext& __fc) const
+	{ return _M_f._M_format(__t, __fc); }
+
+    private:
+      __format::__formatter_chrono<_CharT> _M_f;
+    };
+
+  template<typename _CharT>
+    struct formatter<chrono::weekday_indexed, _CharT>
+    {
+      template<typename _ParseContext>
+	constexpr typename _ParseContext::iterator
+	parse(_ParseContext& __pc)
+	{ return _M_f._M_parse(__pc, __format::_Weekday); }
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::weekday_indexed& __t, _FormatContext& __fc) const
+	{ return _M_f._M_format(__t, __fc); }
+
+    private:
+      __format::__formatter_chrono<_CharT> _M_f;
+    };
+
+  template<typename _CharT>
+    struct formatter<chrono::weekday_last, _CharT>
+    {
+      template<typename _ParseContext>
+	constexpr typename _ParseContext::iterator
+	parse(_ParseContext& __pc)
+	{ return _M_f._M_parse(__pc, __format::_Weekday); }
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::weekday_last& __t, _FormatContext& __fc) const
+	{ return _M_f._M_format(__t, __fc); }
+
+    private:
+      __format::__formatter_chrono<_CharT> _M_f;
+    };
+
+  template<typename _CharT>
+    struct formatter<chrono::month_day, _CharT>
+    {
+      template<typename _ParseContext>
+	constexpr typename _ParseContext::iterator
+	parse(_ParseContext& __pc)
+	{ return _M_f._M_parse(__pc, __format::_Month|__format::_Day); }
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::month_day& __t, _FormatContext& __fc) const
+	{ return _M_f._M_format(__t, __fc); }
+
+    private:
+      __format::__formatter_chrono<_CharT> _M_f;
+    };
+
+  template<typename _CharT>
+    struct formatter<chrono::month_day_last, _CharT>
+    {
+      template<typename _ParseContext>
+	constexpr typename _ParseContext::iterator
+	parse(_ParseContext& __pc)
+	{ return _M_f._M_parse(__pc, __format::_Month|__format::_Day); }
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::month_day_last& __t, _FormatContext& __fc) const
+	{ return _M_f._M_format(__t, __fc); }
+
+    private:
+      __format::__formatter_chrono<_CharT> _M_f;
+    };
+
+  template<typename _CharT>
+    struct formatter<chrono::month_weekday, _CharT>
+    {
+      template<typename _ParseContext>
+	constexpr typename _ParseContext::iterator
+	parse(_ParseContext& __pc)
+	{ return _M_f._M_parse(__pc, __format::_Month|__format::_Weekday); }
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::month_weekday& __t, _FormatContext& __fc) const
+	{ return _M_f._M_format(__t, __fc); }
+
+    private:
+      __format::__formatter_chrono<_CharT> _M_f;
+    };
+
+  template<typename _CharT>
+    struct formatter<chrono::month_weekday_last, _CharT>
+    {
+      template<typename _ParseContext>
+	constexpr typename _ParseContext::iterator
+	parse(_ParseContext& __pc)
+	{ return _M_f._M_parse(__pc, __format::_Month|__format::_Weekday); }
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::month_weekday_last& __t,
+	       _FormatContext& __fc) const
+	{ return _M_f._M_format(__t, __fc); }
+
+    private:
+      __format::__formatter_chrono<_CharT> _M_f;
+    };
+
+  template<typename _CharT>
+    struct formatter<chrono::year_month, _CharT>
+    {
+      template<typename _ParseContext>
+	constexpr typename _ParseContext::iterator
+	parse(_ParseContext& __pc)
+	{ return _M_f._M_parse(__pc, __format::_Year|__format::_Month); }
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::year_month& __t, _FormatContext& __fc) const
+	{ return _M_f._M_format(__t, __fc); }
+
+    private:
+      __format::__formatter_chrono<_CharT> _M_f;
+    };
+
+  template<typename _CharT>
+    struct formatter<chrono::year_month_day, _CharT>
+    {
+      template<typename _ParseContext>
+	constexpr typename _ParseContext::iterator
+	parse(_ParseContext& __pc)
+	{ return _M_f._M_parse(__pc, __format::_Date); }
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::year_month_day& __t, _FormatContext& __fc) const
+	{ return _M_f._M_format(__t, __fc); }
+
+    private:
+      __format::__formatter_chrono<_CharT> _M_f;
+    };
+
+  template<typename _CharT>
+    struct formatter<chrono::year_month_day_last, _CharT>
+    {
+      template<typename _ParseContext>
+	constexpr typename _ParseContext::iterator
+	parse(_ParseContext& __pc)
+	{ return _M_f._M_parse(__pc, __format::_Date); }
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::year_month_day_last& __t,
+	       _FormatContext& __fc) const
+	{ return _M_f._M_format(__t, __fc); }
+
+    private:
+      __format::__formatter_chrono<_CharT> _M_f;
+    };
+
+  template<typename _CharT>
+    struct formatter<chrono::year_month_weekday, _CharT>
+    {
+      template<typename _ParseContext>
+	constexpr typename _ParseContext::iterator
+	parse(_ParseContext& __pc)
+	{ return _M_f._M_parse(__pc, __format::_Date); }
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::year_month_weekday& __t,
+	       _FormatContext& __fc) const
+	{ return _M_f._M_format(__t, __fc); }
+
+    private:
+      __format::__formatter_chrono<_CharT> _M_f;
+    };
+
+  template<typename _CharT>
+    struct formatter<chrono::year_month_weekday_last, _CharT>
+    {
+      template<typename _ParseContext>
+	constexpr typename _ParseContext::iterator
+	parse(_ParseContext& __pc)
+	{ return _M_f._M_parse(__pc, __format::_Date); }
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::year_month_weekday_last& __t,
+	       _FormatContext& __fc) const
+	{ return _M_f._M_format(__t, __fc); }
+
+    private:
+      __format::__formatter_chrono<_CharT> _M_f;
+    };
+
+  template<typename _Rep, typename _Period, typename _CharT>
+    struct formatter<chrono::hh_mm_ss<chrono::duration<_Rep, _Period>>, _CharT>
+    {
+      template<typename _ParseContext>
+	constexpr typename _ParseContext::iterator
+	parse(_ParseContext& __pc)
+	{ return _M_f._M_parse(__pc, __format::_TimeOfDay); }
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::hh_mm_ss<chrono::duration<_Rep, _Period>>& __t,
+	       _FormatContext& __fc) const
+	{ return _M_f._M_format(__t, __fc); }
+
+    private:
+      __format::__formatter_chrono<_CharT> _M_f;
+    };
+
+#if _GLIBCXX_USE_CXX11_ABI || ! _GLIBCXX_USE_DUAL_ABI
+  template<typename _CharT>
+    struct formatter<chrono::sys_info, _CharT>
+    {
+      template<typename _ParseContext>
+	constexpr typename _ParseContext::iterator
+	parse(_ParseContext& __pc)
+	{ return _M_f._M_parse(__pc, __format::_ChronoParts{}); }
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::sys_info& __i, _FormatContext& __fc) const
+	{ return _M_f._M_format(__i, __fc); }
+
+    private:
+      __format::__formatter_chrono<_CharT> _M_f;
+    };
+
+  template<typename _CharT>
+    struct formatter<chrono::local_info, _CharT>
+    {
+      template<typename _ParseContext>
+	constexpr typename _ParseContext::iterator
+	parse(_ParseContext& __pc)
+	{ return _M_f._M_parse(__pc, __format::_ChronoParts{}); }
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::local_info& __i, _FormatContext& __fc) const
+	{ return _M_f._M_format(__i, __fc); }
+
+    private:
+      __format::__formatter_chrono<_CharT> _M_f;
+    };
+#endif
+
+  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::_ZonedDateTime); }
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::sys_time<_Duration>& __t,
+	       _FormatContext& __fc) const
+	{ return _M_f._M_format(__t, __fc); }
+
+    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::_ZonedDateTime); }
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::utc_time<_Duration>& __t,
+	       _FormatContext& __fc) const
+	{
+	  // Adjust by removing leap seconds to get equivalent sys_time.
+	  // We can't just use clock_cast because we want to know if the time
+	  // falls within a leap second insertion, and format seconds as "60".
+	  using chrono::__detail::__utc_leap_second;
+	  using chrono::seconds;
+	  using chrono::sys_time;
+	  using _CDur = common_type_t<_Duration, seconds>;
+	  const auto __li = chrono::get_leap_second_info(__t);
+	  sys_time<_CDur> __s{__t.time_since_epoch() - __li.elapsed};
+	  if (!__li.is_leap_second) [[likely]]
+	    return _M_f._M_format(__s, __fc);
+	  else
+	    return _M_f._M_format(__utc_leap_second(__s), __fc);
+	}
+
+    private:
+      friend formatter<chrono::__detail::__utc_leap_second<_Duration>, _CharT>;
+
+      __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::_ZonedDateTime); }
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::tai_time<_Duration>& __t,
+	       _FormatContext& __fc) const
+	{
+	  // Convert to __local_time_fmt with abbrev "TAI" and offset 0s.
+
+	  // Offset is 1970y/January/1 - 1958y/January/1
+	  constexpr chrono::days __tai_offset = chrono::days(4383);
+	  using _CDur = common_type_t<_Duration, chrono::days>;
+	  chrono::local_time<_CDur> __lt(__t.time_since_epoch() - __tai_offset);
+	  const string __abbrev("TAI", 3);
+	  const chrono::seconds __off = 0s;
+	  const auto __lf = chrono::local_time_format(__lt, &__abbrev, &__off);
+	  return _M_f._M_format(__lf, __fc);
+	}
+
+    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::_ZonedDateTime); }
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::gps_time<_Duration>& __t,
+	       _FormatContext& __fc) const
+	{
+	  // Convert to __local_time_fmt with abbrev "GPS" and offset 0s.
+
+	  // Offset is 1980y/January/Sunday[1] - 1970y/January/1
+	  constexpr chrono::days __gps_offset = chrono::days(3657);
+	  using _CDur = common_type_t<_Duration, chrono::days>;
+	  chrono::local_time<_CDur> __lt(__t.time_since_epoch() + __gps_offset);
+	  const string __abbrev("GPS", 3);
+	  const chrono::seconds __off = 0s;
+	  const auto __lf = chrono::local_time_format(__lt, &__abbrev, &__off);
+	  return _M_f._M_format(__lf, __fc);
+	}
+
+    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::_ZonedDateTime); }
+
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::file_time<_Duration>& __t,
+	       _FormatContext& __ctx) const
+	{
+	  using namespace chrono;
+	  return _M_f._M_format(chrono::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::_DateTime); }
+
+      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::_ZonedDateTime); }
+
+      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;
+    };
+
+#if _GLIBCXX_USE_CXX11_ABI || ! _GLIBCXX_USE_DUAL_ABI
+  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();
+	  const auto __lf = chrono::local_time_format(__tp.get_local_time(),
+						      &__info.abbrev,
+						      &__info.offset);
+	  return _Base::format(__lf, __ctx);
+	}
+    };
+#endif
+
+  // Partial specialization needed for %c formatting of __utc_leap_second.
+  template<typename _Duration, typename _CharT>
+    struct formatter<chrono::__detail::__utc_leap_second<_Duration>, _CharT>
+    : formatter<chrono::utc_time<_Duration>, _CharT>
+    {
+      template<typename _FormatContext>
+	typename _FormatContext::iterator
+	format(const chrono::__detail::__utc_leap_second<_Duration>& __t,
+	       _FormatContext& __fc) const
+	{ return this->_M_f._M_format(__t, __fc); }
+    };
+
+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, 6);
+      __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);
+      int __i = (int)__y;
+      if (__i >= 0) [[likely]]
+	__s.remove_prefix(1);
+      else
+	__i = -__i;
+      __os << std::vformat(__s, make_format_args<_Ctx>(__i));
+      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>(__wd.c_encoding()));
+      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,
+			   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);
+    }
+
+#if _GLIBCXX_USE_CXX11_ABI || ! _GLIBCXX_USE_DUAL_ABI
+  /// Writes a sys_info object to an ostream in an unspecified format.
+  template<typename _CharT, typename _Traits>
+    basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os, const sys_info& __i)
+    {
+      __os << '[' << __i.begin << ',' << __i.end
+	   << ',' << hh_mm_ss(__i.offset) << ',' << __i.save
+	   << ',' << __i.abbrev << ']';
+      return __os;
+    }
+
+  /// Writes a local_info object to an ostream in an unspecified format.
+  template<typename _CharT, typename _Traits>
+    basic_ostream<_CharT, _Traits>&
+    operator<<(basic_ostream<_CharT, _Traits>& __os, const local_info& __li)
+    {
+      __os << '[';
+      if (__li.result == local_info::unique)
+	__os << __li.first;
+      else
+	{
+	  if (__li.result == local_info::nonexistent)
+	    __os << "nonexistent";
+	  else
+	    __os << "ambiguous";
+	  __os << " local time between " << __li.first;
+	  __os << " and " << __li.second;
+	}
+      __os << ']';
+      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;
+    }
+#endif
+
+  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>
+    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 aeb8f6f462f..27f391a1455 100644
--- a/libstdc++-v3/include/std/chrono
+++ b/libstdc++-v3/include/std/chrono
@@ -45,7 +45,6 @@
 # include <sstream>
 # include <string>
 # include <vector>
-# include <bits/charconv.h> // __to_chars_len, __to_chars_10_impl
 # include <bits/stl_algo.h> // upper_bound
 # include <bits/shared_ptr.h>
 # include <bits/unique_ptr.h>
@@ -627,8 +626,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
@@ -751,8 +748,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};
@@ -929,8 +924,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
@@ -1052,8 +1045,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};
@@ -1110,8 +1101,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
@@ -1151,8 +1140,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
@@ -1224,8 +1211,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
@@ -1278,8 +1263,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
@@ -1339,8 +1322,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
       friend constexpr year_month_weekday
       operator/(const month_weekday& __mwd, int __y) noexcept;
-
-      // TODO: Implement operator<<.
     };
 
     // MONTH_WEEKDAY_LAST
@@ -1401,8 +1382,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
@@ -1544,8 +1523,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
@@ -1697,8 +1674,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.
@@ -1928,8 +1903,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
@@ -2118,8 +2091,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
@@ -2267,8 +2238,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
@@ -2284,6 +2253,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	  __r *= 10;
 	return __r;
       }
+
+      template<typename _Duration> struct __utc_leap_second;
     }
     /// @endcond
 
@@ -2389,8 +2360,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	  return _M_h + _M_m + _M_s + subseconds();
 	}
 
-	// TODO: Implement operator<<.
-
       private:
 	static constexpr bool _S_is_unsigned
 	  = __and_v<is_integral<typename _Duration::rep>,
@@ -2459,8 +2428,30 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	__byte_duration<ratio<1>>   _M_s{};
 	bool			    _M_is_neg{};
 	__subseconds<precision>	    _M_ss{};
+
+	template<typename> friend struct __detail::__utc_leap_second;
       };
 
+    /// @cond undocumented
+    namespace __detail
+    {
+      // Represents a time that is within a leap second insertion.
+      template<typename _Duration>
+	struct __utc_leap_second
+	{
+	  explicit
+	  __utc_leap_second(const sys_time<_Duration>& __s)
+	  : _M_date(chrono::floor<days>(__s)), _M_time(__s - _M_date)
+	  {
+	    ++_M_time._M_s;
+	  }
+
+	  sys_days _M_date;
+	  hh_mm_ss<common_type_t<_Duration, days>> _M_time;
+	};
+    }
+    /// @endcond
+
     // 12/24 HOURS FUNCTIONS
 
     constexpr bool
@@ -2540,9 +2531,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	_S_make_what_str(const local_time<_Duration>& __tp,
 			 const local_info& __i)
 	{
-#if 1
-	  return "local time is non-existent";
-#else
 	  std::ostringstream __os;
 	  __os << __tp << " is in a gap between\n"
 	       << local_seconds(__i.first.end.time_since_epoch())
@@ -2552,7 +2540,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	       << " which are both equivalent to\n"
 	       << __i.first.end << " UTC";
 	  return std::move(__os).str();
-#endif
 	}
     };
 
@@ -2571,9 +2558,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	_S_make_what_str(const local_time<_Duration>& __tp,
 			 const local_info& __i)
 	{
-#if 1
-	  return "local time is ambiguous";
-#else
 	  std::ostringstream __os;
 	  __os << __tp << " is ambiguous.  It could be\n"
 	       << __tp << ' ' << __i.first.abbrev << " == "
@@ -2581,7 +2565,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	       << __tp << ' ' << __i.second.abbrev << " == "
 	       << __tp - __i.second.offset << " UTC";
 	  return std::move(__os).str();
-#endif
 	}
     };
 
@@ -3329,106 +3312,15 @@ namespace __detail
     /// @}
   } // 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/tzdb.cc b/libstdc++-v3/src/c++20/tzdb.cc
index dcff021d9d4..a0bb03173a9 100644
--- a/libstdc++-v3/src/c++20/tzdb.cc
+++ b/libstdc++-v3/src/c++20/tzdb.cc
@@ -511,27 +511,25 @@ namespace std::chrono
       friend ostream& operator<<(ostream& out, const Rule& r)
       {
 	out << "Rule " << r.name << ' ' << (int)r.from << ' ' << (int)r.to
-	    << ' ' << (unsigned)r.when.day.get_month() << ' ';
+	    << ' ' << r.when.day.get_month() << ' ';
 	switch (r.when.day.kind)
 	{
 	case on_day::DayOfMonth:
 	  out << (unsigned)r.when.day.get_day();
 	  break;
 	case on_day::LastWeekday:
-	  out << "last" << weekday(r.when.day.day_of_week).c_encoding();
+	  out << "last" << weekday(r.when.day.day_of_week);
 	  break;
 	case on_day::LessEq:
-	  out << weekday(r.when.day.day_of_week).c_encoding() << " <= "
+	  out << weekday(r.when.day.day_of_week) << " <= "
 	    << r.when.day.day_of_month;
 	  break;
 	case on_day::GreaterEq:
-	  out << weekday(r.when.day.day_of_week).c_encoding() << " >= "
+	  out << weekday(r.when.day.day_of_week) << " >= "
 	    << r.when.day.day_of_month;
 	  break;
 	}
-	hh_mm_ss hms(r.when.time);
-	out << ' ' << hms.hours().count() << ':' << hms.minutes().count()
-	    << ':' << hms.seconds().count() << "wusd"[r.when.indicator];
+	out << ' ' << hh_mm_ss(r.when.time) << "wusd"[r.when.indicator];
 	out << ' ' << r.save.count();
 	if (!r.letters.empty())
 	  out << ' ' << r.letters;
diff --git a/libstdc++-v3/testsuite/20_util/duration/io.cc b/libstdc++-v3/testsuite/20_util/duration/io.cc
index 405e1afa440..27586b54392 100644
--- a/libstdc++-v3/testsuite/20_util/duration/io.cc
+++ b/libstdc++-v3/testsuite/20_util/duration/io.cc
@@ -47,8 +47,56 @@ test02()
 #endif
 }
 
+void
+test_format()
+{
+  using namespace std::chrono_literals;
+  auto s = std::format("{} {}", 1h + 23min + 45s, -42min);
+  VERIFY( s == "5025s -42min" );
+  s = std::format("{:%j} {:%j} {:%j}", 1h + 23min + 45s, 75h, -99h);
+  VERIFY( s == "0 3 -4" );
+  s = std::format("{:%T = %H:%M:%S}", 1h + 23min + 45s);
+  VERIFY( s == "01:23:45 = 01:23:45" );
+  s = std::format("{:%Q} {:%q} {:%Q%q}", 6min + 1s, 44min, -22h);
+  VERIFY( s == "361 min -22h" );
+
+  std::wstring ws = std::format(L"{:%Q%q}", 81s);
+  VERIFY( ws == L"81s" );
+
+  // Only print '-' on numeric fields for negative durations:
+  s = std::format("{:%Q} {:%q} {:%q%Q}", -21h, -20h, -19h);
+  VERIFY( s == "-21 h h-19" );
+  s = std::format("{:%p} {:%p%H}", -2h, -13h);
+  VERIFY( s == "AM PM-13" );
+  s = std::format("{:%t} {:%t%M}", -2h, -123s);
+  VERIFY( s == "\t \t-02" );
+
+  std::string_view specs = "aAbBcCdDeFgGhHIjmMpqQrRSTuUVwWxXyYzZ";
+  std::string_view my_specs = "HIjMpqQrRSTX";
+  for (char c : specs)
+  {
+    char fmt[] = { '{', ':', '%', c, '}' };
+    try
+    {
+      (void) std::vformat(std::string_view(fmt, 5), std::make_format_args(1s));
+      // The call above should throw for any conversion-spec not in my_specs:
+      VERIFY(my_specs.find(c) != my_specs.npos);
+    }
+    catch (const std::format_error& e)
+    {
+      VERIFY(my_specs.find(c) == my_specs.npos);
+      std::string_view s = e.what();
+      // Libstdc++-specific message:
+      VERIFY(s.find("format argument does not contain the information "
+		    "required by the chrono-specs") != s.npos);
+    }
+  }
+}
+
 int main()
 {
   test01();
   test02();
+  test_format();
+  // TODO: test_parse();
 }
diff --git a/libstdc++-v3/testsuite/std/time/clock/file/io.cc b/libstdc++-v3/testsuite/std/time/clock/file/io.cc
new file mode 100644
index 00000000000..c8e82bb111c
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/time/clock/file/io.cc
@@ -0,0 +1,23 @@
+// { dg-options "-std=gnu++20" }
+// { dg-do run { target c++20 } }
+
+#include <chrono>
+#include <sstream>
+#include <testsuite_hooks.h>
+
+void
+test_ostream()
+{
+  using namespace std::chrono;
+
+  file_time<file_clock::duration> t = file_clock::now();
+  std::ostringstream ss1, ss2;
+  ss1 << floor<seconds>(t);
+  ss2 << floor<seconds>(clock_cast<system_clock>(t));
+  VERIFY( ss1.str() == ss2.str() );
+}
+
+int main()
+{
+  test_ostream();
+}
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..e7feebc9a3c
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/time/clock/system/io.cc
@@ -0,0 +1,72 @@
+// { dg-options "-std=gnu++20" }
+// { dg-do run { target c++20 } }
+
+#include <chrono>
+#include <sstream>
+#include <testsuite_hooks.h>
+
+void
+test_ostream()
+{
+  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; };
+
+// operator<<(ostream&, const sys_time<D>&) is constrained to not
+// allow floating-point types or periods of days or greater.
+using fp_sys_time = std::chrono::sys_time<std::chrono::duration<float>>;
+static_assert( !stream_insertable<fp_sys_time> );
+
+// But there is an overload for sys_days.
+static_assert( stream_insertable<std::chrono::sys_days> );
+
+void
+test_format()
+{
+  using namespace std::chrono_literals;
+  std::chrono::sys_time<std::chrono::milliseconds> t(1671470785708ms);
+
+  // Every conversion specifier is valid for a sys_time except %q and %Q.
+
+  std::string s = std::format("{:%a | %A | %b | %B | %c"
+			      " | %C | %d | %D | %e | %F | %g | %G | %h"
+			      " | %H | %I | %j | %m | %M | %p | %r | %R"
+			      " | %S | %T | %u | %U | %V | %w | %W | %x"
+			      " | %X | %y | %Y | %z | %Z}", t);
+  VERIFY( s == "Mon | Monday | Dec | December | Mon Dec 19 17:26:25.708 2022"
+	       " | 20 | 19 | 12/19/22 | 19 | 2022-12-19 | 22 | 2022 | Dec"
+	       " | 17 | 05 | 353 | 12 | 26 | PM | 05:26:25.708 PM | 17:26"
+	       " | 25.708 | 17:26:25.708 | 1 | 51 | 51 | 1 | 51 | 12/19/22"
+	       " | 17:26:25.708 | 22 | 2022 | +0000 | UTC" );
+
+  std::wstring ws = std::format(L"{:%a | %A | %b | %B | %c"
+				 " | %C | %d | %D | %e | %F | %g | %G | %h"
+				 " | %H | %I | %j | %m | %M | %p | %r | %R"
+				 " | %S | %T | %u | %U | %V | %w | %W | %x"
+				 " | %X | %y | %Y | %z | %Z}", t);
+  VERIFY( ws == L"Mon | Monday | Dec | December | Mon Dec 19 17:26:25.708 2022"
+		 " | 20 | 19 | 12/19/22 | 19 | 2022-12-19 | 22 | 2022 | Dec"
+		 " | 17 | 05 | 353 | 12 | 26 | PM | 05:26:25.708 PM | 17:26"
+		 " | 25.708 | 17:26:25.708 | 1 | 51 | 51 | 1 | 51 | 12/19/22"
+		 " | 17:26:25.708 | 22 | 2022 | +0000 | UTC" );
+}
+
+int main()
+{
+  test_ostream();
+  test_format();
+}
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..b327c7f50c7
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/time/clock/utc/io.cc
@@ -0,0 +1,120 @@
+// { dg-options "-std=gnu++20" }
+// { dg-do run { target c++20 } }
+
+#include <chrono>
+#include <sstream>
+#include <testsuite_hooks.h>
+
+void
+test_ostream()
+{
+  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;
+  }
+}
+
+void
+test_format()
+{
+  using namespace std::chrono_literals;
+  std::chrono::utc_time<std::chrono::milliseconds> t(1671470812708ms);
+
+  // Every conversion specifier is valid for a utc_time except %q and %Q.
+  std::string_view specs = "aAbBcCdDeFgGhHIjmMpqQrRSTuUVwWxXyYzZ";
+  std::string_view badspecs = "qQ";
+
+  std::ostringstream ss;
+  std::wostringstream wss;
+
+  for (char c : specs)
+  {
+    char fmt[] = { '{', ':', '%', c, '}' };
+    try
+    {
+      ss << std::vformat(std::string_view(fmt, 5),
+			 std::make_format_args(t));
+      ss << " | ";
+      // The call above should throw for any conversion-spec in badspecs:
+      VERIFY(badspecs.find(c) == badspecs.npos);
+
+    }
+    catch (const std::format_error& e)
+    {
+      VERIFY(badspecs.find(c) != badspecs.npos);
+      std::string_view s = e.what();
+      // Libstdc++-specific message:
+      VERIFY(s.find("format argument does not contain the information "
+		    "required by the chrono-specs") != s.npos);
+    }
+
+    wchar_t wfmt[] = { L'{', L':', L'%', c, L'}' };
+    try
+    {
+      wss << std::vformat(std::wstring_view(wfmt, 5),
+			  std::make_wformat_args(t));
+      wss << L" | ";
+      // The call above should throw for any conversion-spec in badspecs:
+      VERIFY(badspecs.find(c) == badspecs.npos);
+    }
+    catch (const std::format_error& e)
+    {
+      VERIFY(badspecs.find(c) != badspecs.npos);
+      std::string_view s = e.what();
+      // Libstdc++-specific message:
+      VERIFY(s.find("format argument does not contain the information "
+		    "required by the chrono-specs") != s.npos);
+    }
+  }
+
+  std::string s = ss.str();
+  VERIFY( s == "Mon | Monday | Dec | December | Mon Dec 19 17:26:25.708 2022"
+	       " | 20 | 19 | 12/19/22 | 19 | 2022-12-19 | 22 | 2022 | Dec"
+	       " | 17 | 05 | 353 | 12 | 26 | PM | 05:26:25.708 PM | 17:26"
+	       " | 25.708 | 17:26:25.708 | 1 | 51 | 51 | 1 | 51 | 12/19/22"
+	       " | 17:26:25.708 | 22 | 2022 | +0000 | UTC | " );
+
+  std::wstring ws = wss.str();
+  VERIFY( ws == L"Mon | Monday | Dec | December | Mon Dec 19 17:26:25.708 2022"
+		 " | 20 | 19 | 12/19/22 | 19 | 2022-12-19 | 22 | 2022 | Dec"
+		 " | 17 | 05 | 353 | 12 | 26 | PM | 05:26:25.708 PM | 17:26"
+		 " | 25.708 | 17:26:25.708 | 1 | 51 | 51 | 1 | 51 | 12/19/22"
+		 " | 17:26:25.708 | 22 | 2022 | +0000 | UTC | " );
+
+  std::chrono::utc_seconds leap(1483228800s + 26s); // 1 Jan 2017
+  s = std::format("{:%T}", leap - 1s);
+  VERIFY( s == "23:59:59" );
+  s = std::format("{:%T}", leap);
+  VERIFY( s == "23:59:60" );
+  s = std::format("{:%T}", leap + 10ms);
+  VERIFY( s == "23:59:60.010" );
+
+  s = std::format("{:%T}", leap + 1s);
+  VERIFY( s == "00:00:00" );
+}
+
+int main()
+{
+  test_ostream();
+  test_format();
+}
diff --git a/libstdc++-v3/testsuite/std/time/day/io.cc b/libstdc++-v3/testsuite/std/time/day/io.cc
new file mode 100644
index 00000000000..6158230f288
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/time/day/io.cc
@@ -0,0 +1,75 @@
+// { dg-options "-std=gnu++20" }
+// { dg-do run { target c++20 } }
+// { dg-require-namedlocale "fr_FR.ISO8859-15" }
+
+#include <chrono>
+#include <sstream>
+#include <testsuite_hooks.h>
+
+void
+test_ostream()
+{
+  using std::ostringstream;
+  using namespace std::chrono;
+
+  ostringstream ss;
+  ss << day(1) << ' ' << day(11) << ' ' << day(21) << ' ' << day(31)
+    << ' ' << day(41);
+  auto s = ss.str();
+  VERIFY( s == "01 11 21 31 41 is not a valid day" );
+
+  ss.str("");
+  ss.imbue(std::locale(ISO_8859(15,fr_FR)));
+  ss << day(1);
+  VERIFY( ss.str() == "01" );
+}
+
+void
+test_format()
+{
+  using std::chrono::day;
+
+  auto s = std::format("{:%d%%%e%t}{:%d%%%e%n}", day(1), day(11));
+  VERIFY( s == "01% 1\t11%11\n" );
+  auto ws = std::format(L"{:%d%%%e%t}{:%d%%%e%n}", day(1), day(11));
+  VERIFY( ws == L"01% 1\t11%11\n" );
+
+  VERIFY( std::format("{} {}", day(8), day(0)) == "08 00 is not a valid day" );
+
+  s = std::format("{:%Od}", day(1));
+  VERIFY( s == "01" );
+  s = std::format(std::locale::classic(), "{:%Od}", day(1));
+  VERIFY( s == "01" );
+  s = std::format(std::locale::classic(), "{:L%Od}", day(1));
+  VERIFY( s == "01" );
+  // TODO test "{:L%Od}" with locale that has alternative numeric rep.
+
+  std::string_view specs = "aAbBcCdDeFgGhHIjmMpqQrRSTuUVwWxXyYzZ";
+  std::string_view my_specs = "de";
+  for (char c : specs)
+  {
+    char fmt[] = { '{', ':', '%', c, '}' };
+    try
+    {
+      (void) std::vformat(std::string_view(fmt, 5),
+			  std::make_format_args(day(1)));
+      // The call above should throw for any conversion-spec not in my_specs:
+      VERIFY(my_specs.find(c) != my_specs.npos);
+    }
+    catch (const std::format_error& e)
+    {
+      VERIFY(my_specs.find(c) == my_specs.npos);
+      std::string_view s = e.what();
+      // Libstdc++-specific message:
+      VERIFY(s.find("format argument does not contain the information "
+		    "required by the chrono-specs") != s.npos);
+    }
+  }
+}
+
+int main()
+{
+  test_ostream();
+  test_format();
+  // TODO: test_parse();
+}
diff --git a/libstdc++-v3/testsuite/std/time/exceptions.cc b/libstdc++-v3/testsuite/std/time/exceptions.cc
index 1b81d5ee27a..650b1fe7a37 100644
--- a/libstdc++-v3/testsuite/std/time/exceptions.cc
+++ b/libstdc++-v3/testsuite/std/time/exceptions.cc
@@ -20,7 +20,7 @@ test_nonexistent()
 			 local_days{Sunday[2]/March/2016} + 2h + 30min};
     VERIFY(false);
   } catch (const nonexistent_local_time& e) {
-    // VERIFY( e.what() == expected );
+    VERIFY( e.what() == expected );
   }
 }
 
@@ -38,7 +38,7 @@ test_ambiguous()
 			 local_days{Sunday[1]/November/2016} + 1h + 30min};
     VERIFY(false);
   } catch (const ambiguous_local_time& e) {
-    // VERIFY( e.what() == expected );
+    VERIFY( e.what() == expected );
   }
 }
 
diff --git a/libstdc++-v3/testsuite/std/time/format.cc b/libstdc++-v3/testsuite/std/time/format.cc
new file mode 100644
index 00000000000..b05e5da1af8
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/time/format.cc
@@ -0,0 +1,117 @@
+// { dg-options "-std=gnu++20" }
+// { dg-do run { target c++20 } }
+
+#include <chrono>
+#include <testsuite_hooks.h>
+
+void
+test_format_strings()
+{
+  using namespace std::chrono_literals;
+
+  // valid format strings
+  VERIFY( std::format("{}", 1s) == "1s" );
+  VERIFY( std::format("{:}", 1s) == "1s" );
+  VERIFY( std::format("{:L}", 1s) == "1s" );
+  VERIFY( std::format("{:%%%n%t}", 1s) == "%\n\t" );
+  VERIFY( std::format("{:L%%%n%t}", 1s) == "%\n\t" );
+  VERIFY( std::format("{:4%%}", 1s) == "%   " );
+  VERIFY( std::format("{:4L%%}", 1s) == "%   " );
+  VERIFY( std::format("{: >4}", 1s) == "  1s" );
+  VERIFY( std::format("{: <4}", 1s) == "1s  " );
+  VERIFY( std::format("{: <4L}", 1s) == "1s  " );
+  VERIFY( std::format("{: >4%%}", 1s) == "   %" );
+  VERIFY( std::format("{: >4L%%}", 1s) == "   %" );
+  VERIFY( std::format("{: ^4%%}", 1s) == " %  " );
+}
+
+template<typename... Args>
+bool
+is_format_string_for(const char* str, Args&&... args)
+{
+  try {
+    (void) std::vformat(str, std::make_format_args(args...));
+    return true;
+  } catch (const std::format_error&) {
+    return false;
+  }
+}
+
+void
+test_bad_format_strings()
+{
+  std::chrono::sys_seconds t{};
+
+  // literal '%' must be formatted as "%%"
+  VERIFY( not is_format_string_for("{:%}", t) );
+
+  // chrono-specs must start with '%'
+  VERIFY( not is_format_string_for("{:a%}", t) );
+  VERIFY( not is_format_string_for("{:La%}", t) );
+
+  // '{' not valid in chrono-specs
+  VERIFY( not is_format_string_for("{:%%{{%%}", t) );
+
+  // padding with leading zero not valid for chrono types
+  VERIFY( not is_format_string_for("{:04%T}", t) );
+
+  // precision only valid for chrono::duration types with floating-point rep.
+  VERIFY( not is_format_string_for("{:.4}", t) );
+
+  // unfinished format string
+  VERIFY( not is_format_string_for("{:", t) );
+
+  // dangling modifiers
+  VERIFY( not is_format_string_for("{:%E}", t) );
+  VERIFY( not is_format_string_for("{:%O}", t) );
+
+  // modifier not valid for conversion specifier
+  VERIFY( not is_format_string_for("{:%Ea}", t) );
+  VERIFY( not is_format_string_for("{:%Oa}", t) );
+}
+
+template<typename I>
+struct move_only_iterator
+{
+  using iterator = I;
+  using value_type = iterator::value_type;
+  using difference_type = iterator::difference_type;
+  using iterator_category = std::output_iterator_tag;
+
+  move_only_iterator(iterator b) : base_(b) { }
+  move_only_iterator(move_only_iterator&&) = default;
+  move_only_iterator& operator=(move_only_iterator&&) = default;
+
+  move_only_iterator& operator++() { ++base_; return *this; }
+  move_only_iterator operator++(int) { auto tmp = *this; ++base_; return tmp; }
+
+  decltype(auto) operator*() { return *base_; }
+
+private:
+  iterator base_;
+};
+
+void
+test_move_only_iterator()
+{
+  using namespace std::chrono;
+  utc_seconds ut(1671543754s);
+  sys_seconds st(1671543727s);
+
+  std::string str;
+  move_only_iterator mo(std::back_inserter(str));
+  std::format_to(std::move(mo), "{:%F} {:%T} {:%Q}", ut, st, 1s);
+  VERIFY( str == "2022-12-20 13:42:07 1" );
+
+  std::vector<wchar_t> vec;
+  move_only_iterator wmo(std::back_inserter(vec));
+  std::format_to(std::move(wmo), L"{:%F} {:%T} {:%Q}", ut, st, 2s);
+  VERIFY( std::wstring_view(vec.data(), vec.size()) == L"2022-12-20 13:42:07 2" );
+}
+
+int main()
+{
+  test_format_strings();
+  test_bad_format_strings();
+  test_move_only_iterator();
+}
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();
+}
diff --git a/libstdc++-v3/testsuite/std/time/month/io.cc b/libstdc++-v3/testsuite/std/time/month/io.cc
new file mode 100644
index 00000000000..7ceeafd725a
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/time/month/io.cc
@@ -0,0 +1,98 @@
+// { dg-options "-std=gnu++20" }
+// { dg-do run { target c++20 } }
+// { dg-require-namedlocale "fr_FR.ISO8859-15" }
+
+#include <chrono>
+#include <sstream>
+#include <testsuite_hooks.h>
+
+void
+test_ostream()
+{
+  using std::ostringstream;
+  using namespace std::chrono;
+
+  ostringstream ss;
+  for (int i = 1; i <= 12; ++i)
+    ss << month(i);
+  VERIFY( ss.str() == "JanFebMarAprMayJunJulAugSepOctNovDec" );
+  ss.str("");
+  ss << month(0) << '|' << month(13);
+  VERIFY( ss.str() == "0 is not a valid month|13 is not a valid month" );
+
+  ss.str("");
+  ss.imbue(std::locale(ISO_8859(15,fr_FR)));
+  ss << month(1);
+  VERIFY( ss.str() == "janv." );
+}
+
+void
+test_format()
+{
+  using std::chrono::month;
+
+  auto s = std::format("{:%b%%%B%t%m%n}", month(1));
+  VERIFY( s == "Jan%January\t01\n" );
+  auto ws = std::format(L"{:%b%%%B%t%m%n}", month(12));
+  VERIFY( ws == L"Dec%December\t12\n" );
+
+  try
+  {
+    (void) std::format("{:%b}", month(13));
+    VERIFY(false);
+  }
+  catch (const std::format_error&)
+  {
+  }
+
+  s = std::format("{} is OK, but {:L}", month(2), month(13));
+  VERIFY( s == "Feb is OK, but 13 is not a valid month" );
+
+  std::locale loc_fr(ISO_8859(15,fr_FR));
+
+  s = std::format("{:%Om}", month(1));
+  VERIFY( s == "01" );
+  s = std::format(std::locale::classic(), "{:%Om}", month(1));
+  VERIFY( s == "01" );
+  s = std::format(std::locale::classic(), "{:L%Om}", month(1));
+  VERIFY( s == "01" );
+  s = std::format(loc_fr, "{:%Om}", month(1));
+  VERIFY( s == "01" );
+  s = std::format(loc_fr, "{:L%Om}", month(1));
+  VERIFY( s == "01" );
+  // TODO test "{:L%Om}" with locale that has alternative numeric rep.
+
+  s = std::format(loc_fr, "{:%b}", month(1));
+  VERIFY( s == "Jan" );
+  s = std::format(loc_fr, "{:L%b}", month(1));
+  VERIFY( s == "janv." );
+
+  std::string_view specs = "aAbBcCdDeFgGhHIjmMpqQrRSTuUVwWxXyYzZ";
+  std::string_view my_specs = "bBhm";
+  for (char c : specs)
+  {
+    char fmt[] = { '{', ':', '%', c, '}' };
+    try
+    {
+      (void) std::vformat(std::string_view(fmt, 5),
+			  std::make_format_args(month(1)));
+      // The call above should throw for any conversion-spec not in my_specs:
+      VERIFY(my_specs.find(c) != my_specs.npos);
+    }
+    catch (const std::format_error& e)
+    {
+      VERIFY(my_specs.find(c) == my_specs.npos);
+      std::string_view s = e.what();
+      // Libstdc++-specific message:
+      VERIFY(s.find("format argument does not contain the information "
+		    "required by the chrono-specs") != s.npos);
+    }
+  }
+}
+
+int main()
+{
+  test_ostream();
+  test_format();
+  // TODO: test_parse();
+}
diff --git a/libstdc++-v3/testsuite/std/time/syn_c++20.cc b/libstdc++-v3/testsuite/std/time/syn_c++20.cc
index c91723bebd6..ad6f58bc9a1 100644
--- a/libstdc++-v3/testsuite/std/time/syn_c++20.cc
+++ b/libstdc++-v3/testsuite/std/time/syn_c++20.cc
@@ -124,8 +124,7 @@ namespace __gnu_test
   using std::chrono::time_zone_link;
 #endif
 
-  // FIXME
-  // using std::chrono::local_time_format;
+  using std::chrono::local_time_format;
 
   // FIXME
   // using std::chrono::parse;
diff --git a/libstdc++-v3/testsuite/std/time/time_zone/get_info_local.cc b/libstdc++-v3/testsuite/std/time/time_zone/get_info_local.cc
index d15e1c7036e..46ca34b71fd 100644
--- a/libstdc++-v3/testsuite/std/time/time_zone/get_info_local.cc
+++ b/libstdc++-v3/testsuite/std/time/time_zone/get_info_local.cc
@@ -148,7 +148,6 @@ test_egypt()
   VERIFY( info.second.save == 1h );
   VERIFY( info.second.abbrev == "EEST" );
 
-#if 0
   std::ostringstream out;
   local_seconds lt(local_days(2001y/January/1));
   const local_days end(2021y/January/1);
@@ -209,7 +208,6 @@ test_egypt()
 [[2014-09-25 21:00:00,32767-12-31 00:00:00,02:00:00,0min,EET]]
 )";
   VERIFY( out.str() == expected );
-#endif
 }
 
 int main()
diff --git a/libstdc++-v3/testsuite/std/time/weekday/io.cc b/libstdc++-v3/testsuite/std/time/weekday/io.cc
new file mode 100644
index 00000000000..6cdb98467b1
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/time/weekday/io.cc
@@ -0,0 +1,101 @@
+// { dg-options "-std=gnu++20" }
+// { dg-do run { target c++20 } }
+// { dg-require-namedlocale "fr_FR.ISO8859-15" }
+
+#include <chrono>
+#include <sstream>
+#include <testsuite_hooks.h>
+
+void
+test_ostream()
+{
+  using std::ostringstream;
+  using namespace std::chrono;
+
+  ostringstream ss;
+  for (int i = 0; i <= 7; ++i)
+    ss << weekday(i);
+  VERIFY( ss.str() == "SunMonTueWedThuFriSatSun" );
+  ss.str("");
+  ss << weekday(8) << '|' << weekday(99);
+  VERIFY( ss.str() == "8 is not a valid weekday|99 is not a valid weekday" );
+
+  ss.str("");
+  ss.imbue(std::locale(ISO_8859(15,fr_FR)));
+  ss << weekday(6);
+  VERIFY( ss.str() == "sam." );
+}
+
+void
+test_format()
+{
+  using std::chrono::weekday;
+
+  auto s = std::format("{:%a%%%A%t%u%n%w}", std::chrono::Monday);
+  VERIFY( s == "Mon%Monday\t1\n1" );
+  auto ws = std::format(L"{:%a%%%A%t%u%n%w}", weekday(7));
+  VERIFY( ws == L"Sun%Sunday\t7\n0" );
+
+  s = std::format("{:%w}", weekday(8));
+  VERIFY( s == "8" );
+
+  try
+  {
+    (void) std::format("{:%a}", weekday(8));
+    VERIFY(false);
+  }
+  catch (const std::format_error&)
+  {
+  }
+
+  s = std::format("{} is OK, but {:L}", weekday(2), weekday(13));
+  VERIFY( s == "Tue is OK, but 13 is not a valid weekday" );
+
+  std::locale loc_fr(ISO_8859(15,fr_FR));
+
+  s = std::format("{:%Ow}", weekday(1));
+  VERIFY( s == "1" );
+  s = std::format(std::locale::classic(), "{:%Ow}", weekday(1));
+  VERIFY( s == "1" );
+  s = std::format(std::locale::classic(), "{:L%Ow}", weekday(1));
+  VERIFY( s == "1" );
+  s = std::format(loc_fr, "{:%Ow}", weekday(1));
+  VERIFY( s == "1" );
+  s = std::format(loc_fr, "{:L%Ow}", weekday(1));
+  VERIFY( s == "1" );
+  // TODO test "{:L%Ow}" with locale that has alternative numeric rep.
+
+  s = std::format(loc_fr, "{:%a}", weekday(1));
+  VERIFY( s == "Mon" );
+  s = std::format(loc_fr, "{:L%a}", weekday(1));
+  VERIFY( s == "lun." );
+
+  std::string_view specs = "aAbBcCdDeFgGhHIjmMpqQrRSTuUVwWxXyYzZ";
+  std::string_view my_specs = "aAuw";
+  for (char c : specs)
+  {
+    char fmt[] = { '{', ':', '%', c, '}' };
+    try
+    {
+      (void) std::vformat(std::string_view(fmt, 5),
+			  std::make_format_args(weekday(1)));
+      // The call above should throw for any conversion-spec not in my_specs:
+      VERIFY(my_specs.find(c) != my_specs.npos);
+    }
+    catch (const std::format_error& e)
+    {
+      VERIFY(my_specs.find(c) == my_specs.npos);
+      std::string_view s = e.what();
+      // Libstdc++-specific message:
+      VERIFY(s.find("format argument does not contain the information "
+		    "required by the chrono-specs") != s.npos);
+    }
+  }
+}
+
+int main()
+{
+  test_ostream();
+  test_format();
+  // TODO: test_parse();
+}
diff --git a/libstdc++-v3/testsuite/std/time/year/io.cc b/libstdc++-v3/testsuite/std/time/year/io.cc
new file mode 100644
index 00000000000..07316e98aa5
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/time/year/io.cc
@@ -0,0 +1,89 @@
+// { dg-options "-std=gnu++20" }
+// { dg-do run { target c++20 } }
+// { dg-require-namedlocale "fr_FR.ISO8859-15" }
+
+#include <chrono>
+#include <sstream>
+#include <testsuite_hooks.h>
+
+void
+test_ostream()
+{
+  using std::ostringstream;
+  using namespace std::chrono;
+
+  ostringstream ss;
+  for (int y : {-1234, -44, -1, 0, 5, 32, 325, 1066, 2022})
+    ss << year(y) << ' ';
+  VERIFY( ss.str() == "-1234 -0044 -0001 0000 0005 0032 0325 1066 2022 " );
+  ss.str("");
+  ss << year::min() << ' ' << year::max() << ' ' << --year::min();
+  VERIFY( ss.str() == "-32767 32767 -32768 is not a valid year" );
+
+  ss.str("");
+  ss.imbue(std::locale(ISO_8859(15,fr_FR)));
+  ss << 1789y;
+  VERIFY( ss.str() == "1789" );
+}
+
+void
+test_format()
+{
+  using std::chrono::year;
+  using namespace std::chrono_literals;
+
+  auto s = std::format("{:%y%%%Y%t%C%n}", 2022y);
+  VERIFY( s == "22%2022\t20\n" );
+  auto ws = std::format(L"{:%y%%%Y%t%C%n}", 2023y);
+  VERIFY( ws == L"23%2023\t20\n" );
+
+  s = std::format("{:%Y}", --year::min());
+  VERIFY( s == "-32768" );
+
+  s = std::format("{}", --year::min()); // formatted via ostream
+  VERIFY( s == "-32768 is not a valid year" );
+
+  s = std::format("{:%y} {:%y}", 1976y, -1976y);
+  VERIFY( s == "76 76" ); // LWG 3831
+
+  s = std::format("{0:%EC}{0:%Ey} = {0:%EY}", 1642y);
+  VERIFY( s == "1642 = 1642" );
+  s = std::format("{0:L%EC}{0:L%Ey} = {0:L%EY}", 1642y);
+  VERIFY( s == "1642 = 1642" );
+  s = std::format(std::locale::classic(), "{0:L%EC}{0:L%Ey} = {0:L%EY}", 1642y);
+  VERIFY( s == "1642 = 1642" );
+
+  // TODO test "{:L%EC}" with locale that has alternative era rep.
+  // TODO test "{:L%Ey}" with locale that has alternative year rep.
+  // TODO test "{:L%EY}" with locale that has alternative year rep.
+  // TODO test "{:L%Oy}" with locale that has alternative numeric rep.
+
+  std::string_view specs = "aAbBcCdDeFgGhHIjmMpqQrRSTuUVwWxXyYzZ";
+  std::string_view my_specs = "CyY";
+  for (char c : specs)
+  {
+    char fmt[] = { '{', ':', '%', c, '}' };
+    try
+    {
+      (void) std::vformat(std::string_view(fmt, 5),
+			  std::make_format_args(year(2022)));
+      // The call above should throw for any conversion-spec not in my_specs:
+      VERIFY(my_specs.find(c) != my_specs.npos);
+    }
+    catch (const std::format_error& e)
+    {
+      VERIFY(my_specs.find(c) == my_specs.npos);
+      std::string_view s = e.what();
+      // Libstdc++-specific message:
+      VERIFY(s.find("format argument does not contain the information "
+		    "required by the chrono-specs") != s.npos);
+    }
+  }
+}
+
+int main()
+{
+  test_ostream();
+  test_format();
+  // TODO: test_parse();
+}
diff --git a/libstdc++-v3/testsuite/std/time/year_month_day/io.cc b/libstdc++-v3/testsuite/std/time/year_month_day/io.cc
new file mode 100644
index 00000000000..688885b37a1
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/time/year_month_day/io.cc
@@ -0,0 +1,121 @@
+// { dg-options "-std=gnu++20" }
+// { dg-do run { target c++20 } }
+// { dg-require-namedlocale "fr_FR.ISO8859-15" }
+
+#include <chrono>
+#include <sstream>
+#include <testsuite_hooks.h>
+
+void
+test_ostream()
+{
+  using std::ostringstream;
+  using namespace std::chrono;
+
+  ostringstream ss;
+  ss << 2022y/December/19 << ' ' << 2022y/November/31;
+  VERIFY( ss.str() == "2022-12-19 2022-11-31 is not a valid date" );
+
+  ss.str("");
+  ss.imbue(std::locale(ISO_8859(15,fr_FR)));
+  ss << 1789y/July/14;
+  VERIFY( ss.str() == "1789-07-14" );
+}
+
+void
+test_format()
+{
+  using std::chrono::year_month_day;
+  using std::chrono::December;
+  using std::chrono::January;
+  using namespace std::chrono_literals;
+
+  auto s = std::format("{:%y%%%Y%t%C%n%j %a %b}", 2022y/December/19);
+  VERIFY( s == "22%2022\t20\n353 Mon Dec" );
+  auto ws = std::format(L"{:%y%%%Y%t%C%n%d}", 2023y/January/32);
+  VERIFY( ws == L"23%2023\t20\n32" );
+
+  s = std::format("{:%F} {}", 2023y/January/32, 2023y/January/32);
+  VERIFY( s == "2023-01-32 2023-01-32 is not a valid date" );
+
+  s = std::format("{:%C%g-W%V-%u}", 2022y/January/1);
+  VERIFY( s == "2021-W52-6" );
+  s = std::format("{:%G-W%V-%u}", 2022y/January/3);
+  VERIFY( s == "2022-W01-1" );
+
+  // %U: Week number for weeks starting on Sunday
+  s = std::format("Day {:%w (%a) of Week %U of %Y}", 2022y/January/1);
+  VERIFY( s == "Day 6 (Sat) of Week 00 of 2022" );
+  s = std::format("Day {:%w (%a) of Week %U of %Y}", 2022y/January/2);
+  VERIFY( s == "Day 0 (Sun) of Week 01 of 2022" );
+  // %W: Week number for weeks starting on Monday
+  s = std::format("Day {:%u (%a) of Week %W of %Y}", 2022y/January/2);
+  VERIFY( s == "Day 7 (Sun) of Week 00 of 2022" );
+  s = std::format("Day {:%u (%a) of Week %W of %Y}", 2022y/January/3);
+  VERIFY( s == "Day 1 (Mon) of Week 01 of 2022" );
+
+  // %V: ISO week number (ISO 8601).
+  s = std::format("W{:%V}", 1977y/1/1);
+  VERIFY( s == "W53" );
+  s = std::format("W{:%V}", 1977y/1/2);
+  VERIFY( s == "W53" );
+  s = std::format("W{:%V}", 1977y/12/31);
+  VERIFY( s == "W52" );
+  s = std::format("W{:%V}", 1978y/1/1);
+  VERIFY( s == "W52" );
+  s = std::format("W{:%V}", 1978y/1/2);
+  VERIFY( s == "W01" );
+  s = std::format("W{:%V}", 1978y/12/31);
+  VERIFY( s == "W52" );
+  s = std::format("W{:%V}", 1979y/1/1);
+  VERIFY( s == "W01" );
+  s = std::format("W{:%V}", 1979y/12/30);
+  VERIFY( s == "W52" );
+  s = std::format("W{:%V}", 1979y/12/31);
+  VERIFY( s == "W01" );
+  s = std::format("W{:%V}", 1980y/1/1);
+  VERIFY( s == "W01" );
+
+  s = std::format("{:%x}", 2022y/December/19);
+  VERIFY( s == "12/19/22" );
+  s = std::format("{:L%x}", 2022y/December/19);
+  VERIFY( s == "12/19/22" );
+  std::locale loc_fr(ISO_8859(15,fr_FR));
+  s = std::format(loc_fr, "{:%x}", 2022y/December/19);
+  VERIFY( s == "12/19/22" );
+  s = std::format(loc_fr, "{:L%x}", 2022y/December/19);
+  VERIFY( s == "19/12/2022" );
+  s = std::format(loc_fr, "{}", 2022y/December/19);
+  VERIFY( s == "2022-12-19" );
+  s = std::format(loc_fr, "{:L%F}", 2022y/December/19);
+  VERIFY( s == "2022-12-19" );
+
+  std::string_view specs = "aAbBcCdDeFgGhHIjmMpqQrRSTuUVwWxXyYzZ";
+  std::string_view my_specs = "aAbBCdDeFgGhjmuUVwWxyY";
+  for (char c : specs)
+  {
+    char fmt[] = { '{', ':', '%', c, '}' };
+    try
+    {
+      (void) std::vformat(std::string_view(fmt, 5),
+			  std::make_format_args(2022y/December/19));
+      // The call above should throw for any conversion-spec not in my_specs:
+      VERIFY(my_specs.find(c) != my_specs.npos);
+    }
+    catch (const std::format_error& e)
+    {
+      VERIFY(my_specs.find(c) == my_specs.npos);
+      std::string_view s = e.what();
+      // Libstdc++-specific message:
+      VERIFY(s.find("format argument does not contain the information "
+		    "required by the chrono-specs") != s.npos);
+    }
+  }
+}
+
+int main()
+{
+  test_ostream();
+  test_format();
+  // TODO: test_parse();
+}

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2022-12-22 23:34 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-12-22 23:34 [gcc r13-4856] libstdc++: Add std::format support to <chrono> Jonathan Wakely

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).