[libcxx-commits] [libcxx] 719c3dc - [libc++][chrono] Implements formatter duration.

Mark de Wever via libcxx-commits libcxx-commits at lists.llvm.org
Tue Oct 18 11:39:45 PDT 2022


Author: Mark de Wever
Date: 2022-10-18T20:39:39+02:00
New Revision: 719c3dc6f2f7c7dd01b190496acd8ddecacabe01

URL: https://github.com/llvm/llvm-project/commit/719c3dc6f2f7c7dd01b190496acd8ddecacabe01
DIFF: https://github.com/llvm/llvm-project/commit/719c3dc6f2f7c7dd01b190496acd8ddecacabe01.diff

LOG: [libc++][chrono] Implements formatter duration.

Partially implements:
- P1361 Integration of chrono with text formatting
- P2372 Fixing locale handling in chrono formatters
- LWG3270 Parsing and formatting %j with durations

Completes:
- P1650R0 std::chrono::days with 'd' suffix
- LWG3262 Formatting of negative durations is not specified
- LWG3314 Is stream insertion behavior locale dependent when Period::type is micro?

Reviewed By: ldionne, #libc

Differential Revision: https://reviews.llvm.org/D134742

Added: 
    libcxx/test/std/time/time.duration/time.duration.nonmember/ostream.pass.cpp
    libcxx/test/std/time/time.syn/formatter.duration.pass.cpp

Modified: 
    libcxx/docs/Status/Cxx20Issues.csv
    libcxx/docs/Status/Cxx20Papers.csv
    libcxx/docs/Status/FormatPaper.csv
    libcxx/include/__chrono/convert_to_tm.h
    libcxx/include/__chrono/formatter.h
    libcxx/include/__chrono/ostream.h
    libcxx/include/__chrono/parser_std_format_spec.h
    libcxx/include/chrono
    libcxx/test/std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/Status/Cxx20Issues.csv b/libcxx/docs/Status/Cxx20Issues.csv
index b15142420fa2b..f695e17331105 100644
--- a/libcxx/docs/Status/Cxx20Issues.csv
+++ b/libcxx/docs/Status/Cxx20Issues.csv
@@ -213,10 +213,10 @@
 "`3254 <https://wg21.link/LWG3254>`__","Strike ``stop_token``\ 's ``operator!=``\ ","Prague","",""
 "`3255 <https://wg21.link/LWG3255>`__","``span``\ 's ``array``\  constructor is too strict","Prague","|Complete|",""
 "`3260 <https://wg21.link/LWG3260>`__","``year_month*``\  arithmetic rejects durations convertible to years","Prague","","","|chrono|"
-"`3262 <https://wg21.link/LWG3262>`__","Formatting of negative durations is not specified","Prague","","","|chrono| |format|"
+"`3262 <https://wg21.link/LWG3262>`__","Formatting of negative durations is not specified","Prague","|Complete|","16.0","|chrono| |format|"
 "`3264 <https://wg21.link/LWG3264>`__","``sized_range``\  and ``ranges::size``\  redundantly use ``disable_sized_range``\ ","Prague","|Complete|","15.0","|ranges|"
 "`3269 <https://wg21.link/LWG3269>`__","Parse manipulators do not specify the result of the extraction from stream","Prague","","","|chrono|"
-"`3270 <https://wg21.link/LWG3270>`__","Parsing and formatting ``%j``\  with ``duration``\ s","Prague","","","|chrono| |format|"
+"`3270 <https://wg21.link/LWG3270>`__","Parsing and formatting ``%j``\  with ``duration``\ s","Prague","|Partial|","","|chrono| |format|"
 "`3280 <https://wg21.link/LWG3280>`__","View converting constructors can cause constraint recursion and are unneeded","Prague","|Complete|","15.0","|ranges|"
 "`3281 <https://wg21.link/LWG3281>`__","Conversion from ``*pair-like*``\  types to ``subrange``\  is a silent semantic promotion","Prague","|Complete|","15.0","|ranges|"
 "`3282 <https://wg21.link/LWG3282>`__","``subrange``\  converting constructor should disallow derived to base conversions","Prague","|Complete|","15.0","|ranges|"
@@ -236,7 +236,7 @@
 "`3307 <https://wg21.link/LWG3307>`__","``std::allocator<void>().allocate(n)``\ ","Prague","",""
 "`3310 <https://wg21.link/LWG3310>`__","Replace ``SIZE_MAX``\  with ``numeric_limits<size_t>::max()``\ ","Prague","",""
 "`3313 <https://wg21.link/LWG3313>`__","``join_view::iterator::operator--``\  is incorrectly constrained","Prague","|Complete|","14.0","|ranges|"
-"`3314 <https://wg21.link/LWG3314>`__","Is stream insertion behavior locale dependent when ``Period::type``\  is ``micro``\ ?","Prague","","","|chrono|"
+"`3314 <https://wg21.link/LWG3314>`__","Is stream insertion behavior locale dependent when ``Period::type``\  is ``micro``\ ?","Prague","|Complete|","16.0","|chrono|"
 "`3315 <https://wg21.link/LWG3315>`__","Correct Allocator Default Behavior","Prague","",""
 "`3316 <https://wg21.link/LWG3316>`__","Correctly define epoch for ``utc_clock``\  / ``utc_timepoint``\ ","Prague","","","|chrono|"
 "`3317 <https://wg21.link/LWG3317>`__","Incorrect ``operator<<``\  for floating-point durations","Prague","","","|chrono|"

diff  --git a/libcxx/docs/Status/Cxx20Papers.csv b/libcxx/docs/Status/Cxx20Papers.csv
index b6ce160e36822..7c26646df3086 100644
--- a/libcxx/docs/Status/Cxx20Papers.csv
+++ b/libcxx/docs/Status/Cxx20Papers.csv
@@ -127,7 +127,7 @@
 "`P1638R1 <https://wg21.link/P1638R1>`__","LWG","basic_istream_view::iterator should not be copyable","Cologne","|Complete|","16.0"
 "`P1643R1 <https://wg21.link/P1643R1>`__","LWG","Add wait/notify to atomic_ref","Cologne","",""
 "`P1644R0 <https://wg21.link/P1644R0>`__","LWG","Add wait/notify to atomic<shared_ptr>","Cologne","",""
-"`P1650R0 <https://wg21.link/P1650R0>`__","LWG","Output std::chrono::days with 'd' suffix","Cologne","",""
+"`P1650R0 <https://wg21.link/P1650R0>`__","LWG","Output std::chrono::days with 'd' suffix","Cologne","","|Complete|","16.0"
 "`P1651R0 <https://wg21.link/P1651R0>`__","LWG","bind_front should not unwrap reference_wrapper","Cologne","|Complete|","13.0"
 "`P1652R1 <https://wg21.link/P1652R1>`__","LWG","Printf corner cases in std::format","Cologne","|Complete|","14.0"
 "`P1661R1 <https://wg21.link/P1661R1>`__","LWG","Remove dedicated precalculated hash lookup interface","Cologne","|Nothing To Do|",""
@@ -201,7 +201,7 @@
 "`P2328R1 <https://wg21.link/P2328R1>`__","LWG",join_view should join all views of ranges,"June 2021","",""
 "`P2367R0 <https://wg21.link/P2367R0>`__","LWG",Remove misuses of list-initialization from Clause 24,"June 2021","",""
 "","","","","",""
-"`P2372R3 <https://wg21.link/P2372R3>`__","LWG","Fixing locale handling in chrono formatters","October 2021","",""
+"`P2372R3 <https://wg21.link/P2372R3>`__","LWG","Fixing locale handling in chrono formatters","October 2021","|In Progress|",""
 "`P2415R2 <https://wg21.link/P2415R2>`__","LWG","What is a ``view``","October 2021","|Complete|","14.0"
 "`P2418R2 <https://wg21.link/P2418R2>`__","LWG","Add support for ``std::generator``-like types to ``std::format``","October 2021","|Complete|","15.0"
 "`P2432R1 <https://wg21.link/P2432R1>`__","LWG","Fix ``istream_view``","October 2021","|Complete|","16.0"

diff  --git a/libcxx/docs/Status/FormatPaper.csv b/libcxx/docs/Status/FormatPaper.csv
index 3339728b3a375..50bc570cc0957 100644
--- a/libcxx/docs/Status/FormatPaper.csv
+++ b/libcxx/docs/Status/FormatPaper.csv
@@ -1,6 +1,6 @@
 Section,Description,Dependencies,Assignee,Status,First released version
 `P1361 <https://wg21.link/P1361>`__ `P2372 <https://wg21.link/P2372>`__,"Formatting chrono"
-`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::duration<Rep, Period>``",,Mark de Wever,|In Progress|,
+`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::duration<Rep, Period>``",,Mark de Wever,|Complete|, Clang 16
 `[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::sys_time<Duration>``",,Mark de Wever,|In Progress|,
 `[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::utc_time<Duration>``",A ``<chrono>`` implementation,Not assigned,,,
 `[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::tai_time<Duration>``",A ``<chrono>`` implementation,Not assigned,,,

diff  --git a/libcxx/include/__chrono/convert_to_tm.h b/libcxx/include/__chrono/convert_to_tm.h
index 86ea1f39a6a18..5d34eacc95e75 100644
--- a/libcxx/include/__chrono/convert_to_tm.h
+++ b/libcxx/include/__chrono/convert_to_tm.h
@@ -11,10 +11,12 @@
 #define _LIBCPP___CHRONO_CONVERT_TO_TM_H
 
 #include <__chrono/day.h>
+#include <__chrono/duration.h>
 #include <__chrono/month.h>
 #include <__chrono/year.h>
 #include <__concepts/same_as.h>
 #include <__config>
+#include <cstdint>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -24,23 +26,34 @@ _LIBCPP_BEGIN_NAMESPACE_STD
 
 #if _LIBCPP_STD_VER > 17
 
-// Convert a chrono calendar time point to the given tm type,
+// Convert a chrono (calendar) time point, or dururation to the given _Tm type,
 // which must have the same properties as std::tm.
-template <class _Tm, class _ChronoCalendarTimePoint>
-_LIBCPP_HIDE_FROM_ABI _Tm __convert_to_tm(const _ChronoCalendarTimePoint& __value) {
+template <class _Tm, class _ChronoT>
+_LIBCPP_HIDE_FROM_ABI _Tm __convert_to_tm(const _ChronoT& __value) {
   _Tm __result = {};
 #  ifdef __GLIBC__
   __result.tm_zone = "UTC";
 #  endif
 
-  if constexpr (same_as<_ChronoCalendarTimePoint, chrono::day>)
+  if constexpr (chrono::__is_duration<_ChronoT>::value) {
+    // [time.format]/6
+    //   ...  However, if a flag refers to a "time of day" (e.g. %H, %I, %p,
+    //   etc.), then a specialization of duration is interpreted as the time of
+    //   day elapsed since midnight.
+    uint64_t __sec = chrono::duration_cast<chrono::seconds>(__value).count();
+    __sec %= 24 * 3600;
+    __result.tm_hour = __sec / 3600;
+    __sec %= 3600;
+    __result.tm_min = __sec / 60;
+    __result.tm_sec = __sec % 60;
+  } else if constexpr (same_as<_ChronoT, chrono::day>)
     __result.tm_mday = static_cast<unsigned>(__value);
-  else if constexpr (same_as<_ChronoCalendarTimePoint, chrono::month>)
+  else if constexpr (same_as<_ChronoT, chrono::month>)
     __result.tm_mon = static_cast<unsigned>(__value) - 1;
-  else if constexpr (same_as<_ChronoCalendarTimePoint, chrono::year>)
+  else if constexpr (same_as<_ChronoT, chrono::year>)
     __result.tm_year = static_cast<int>(__value) - 1900;
   else
-    static_assert(sizeof(_ChronoCalendarTimePoint) == 0, "Add the missing type specialization");
+    static_assert(sizeof(_ChronoT) == 0, "Add the missing type specialization");
 
   return __result;
 }

diff  --git a/libcxx/include/__chrono/formatter.h b/libcxx/include/__chrono/formatter.h
index be277dcc79137..5c0ce2bba4297 100644
--- a/libcxx/include/__chrono/formatter.h
+++ b/libcxx/include/__chrono/formatter.h
@@ -12,10 +12,15 @@
 
 #include <__chrono/convert_to_tm.h>
 #include <__chrono/day.h>
+#include <__chrono/duration.h>
+#include <__chrono/hh_mm_ss.h>
 #include <__chrono/month.h>
+#include <__chrono/ostream.h>
 #include <__chrono/parser_std_format_spec.h>
 #include <__chrono/statically_widen.h>
+#include <__chrono/time_point.h>
 #include <__chrono/year.h>
+#include <__concepts/arithmetic.h>
 #include <__concepts/same_as.h>
 #include <__config>
 #include <__format/concepts.h>
@@ -60,6 +65,47 @@ namespace __formatter {
 ///
 /// When no chrono-specs are provided it uses the stream formatter.
 
+// For tiny ratios it's not possible to convert a duration to a hh_mm_ss. This
+// fails compile-time due to the limited precision of the ratio (64-bit is too
+// small). Therefore a duration uses its own conversion.
+template <class _CharT, class _Tp>
+  requires(chrono::__is_duration<_Tp>::value)
+_LIBCPP_HIDE_FROM_ABI void __format_sub_seconds(const _Tp& __value, basic_stringstream<_CharT>& __sstr) {
+  __sstr << std::use_facet<numpunct<_CharT>>(__sstr.getloc()).decimal_point();
+
+  auto __fraction = __value - chrono::duration_cast<chrono::seconds>(__value);
+  if constexpr (chrono::treat_as_floating_point_v<typename _Tp::rep>)
+    // When the floating-point value has digits itself they are ignored based
+    // on the wording in [tab:time.format.spec]
+    //   If the precision of the input cannot be exactly represented with
+    //   seconds, then the format is a decimal floating-point number with a
+    //   fixed format and a precision matching that of the precision of the
+    //   input (or to a microseconds precision if the conversion to
+    //   floating-point decimal seconds cannot be made within 18 fractional
+    //   digits).
+    //
+    // This matches the behaviour of MSVC STL, fmtlib interprets this
+    // 
diff erently and uses 3 decimals.
+    // https://godbolt.org/z/6dsbnW8ba
+    std::format_to(std::ostreambuf_iterator<_CharT>{__sstr},
+                   _LIBCPP_STATICALLY_WIDEN(_CharT, "{:0{}.0f}"),
+                   __fraction.count(),
+                   chrono::hh_mm_ss<_Tp>::fractional_width);
+  else
+    std::format_to(std::ostreambuf_iterator<_CharT>{__sstr},
+                   _LIBCPP_STATICALLY_WIDEN(_CharT, "{:0{}}"),
+                   __fraction.count(),
+                   chrono::hh_mm_ss<_Tp>::fractional_width);
+}
+
+template <class _Tp>
+consteval bool __use_fraction() {
+  if constexpr (chrono::__is_duration<_Tp>::value)
+    return chrono::hh_mm_ss<_Tp>::fractional_width;
+  else
+    return false;
+}
+
 template <class _CharT>
 _LIBCPP_HIDE_FROM_ABI void __format_year(int __year, basic_stringstream<_CharT>& __sstr) {
   if (__year < 0) {
@@ -119,32 +165,73 @@ _LIBCPP_HIDE_FROM_ABI void __format_chrono_using_chrono_specs(
           __facet.put({__sstr}, __sstr, _CharT(' '), std::addressof(__t), __s, __it + 1);
       } break;
 
-      // Unlike time_put and strftime the formatting library requires %Y
-      //
-      // [tab:time.format.spec]
-      //   The year as a decimal number. If the result is less than four digits
-      //   it is left-padded with 0 to four digits.
-      //
-      // This means years in the range (-1000, 1000) need manual formatting.
-      // It's unclear whether %EY needs the same treatment. For example the
-      // Japanese EY contains the era name and year. This is zero-padded to 2
-      // digits in time_put (note that older glibc versions didn't do
-      // padding.) However most eras won't reach 100 years, let alone 1000.
-      // So padding to 4 digits seems unwanted for Japanese.
-      //
-      // The same applies to %Ex since that too depends on the era.
-      //
-      // %x the locale's date representation is currently doesn't handle the
-      // zero-padding too.
-      //
-      // The 4 digits can be implemented better at a later time. On POSIX
-      // systems the required information can be extracted by nl_langinfo
-      // https://man7.org/linux/man-pages/man3/nl_langinfo.3.html
-      //
-      // Note since year < -1000 is expected to be rare it uses the more
-      // expensive year routine.
-      //
-      // TODO FMT evaluate the comment above.
+      case _CharT('j'):
+        if constexpr (chrono::__is_duration<_Tp>::value)
+          // Converting a duration where the period has a small ratio to days
+          // may fail to compile. This due to loss of precision in the
+          // conversion. In order to avoid that issue convert to seconds as
+          // an intemediate step.
+          __sstr << chrono::duration_cast<chrono::days>(chrono::duration_cast<chrono::seconds>(__value)).count();
+        else
+          __facet.put({__sstr}, __sstr, _CharT(' '), std::addressof(__t), __s, __it + 1);
+        break;
+
+      case _CharT('q'):
+        if constexpr (chrono::__is_duration<_Tp>::value) {
+          __sstr << chrono::__units_suffix<_CharT, typename _Tp::period>();
+          break;
+        }
+        __builtin_unreachable();
+
+      case _CharT('Q'):
+        // TODO FMT Determine the proper ideas
+        // - Should it honour the precision?
+        // - Shoult it honour the locale setting for the separators?
+        // The wording for Q doesn't use the word locale and the effect of
+        // precision is unspecified.
+        //
+        // MSVC STL ignores precision but uses separator
+        // FMT honours precision and has a bug for separator
+        // https://godbolt.org/z/78b7sMxns
+        if constexpr (chrono::__is_duration<_Tp>::value) {
+          __sstr << format(_LIBCPP_STATICALLY_WIDEN(_CharT, "{}"), __value.count());
+          break;
+        }
+        __builtin_unreachable();
+
+      case _CharT('S'):
+      case _CharT('T'):
+        __facet.put({__sstr}, __sstr, _CharT(' '), std::addressof(__t), __s, __it + 1);
+        if constexpr (__use_fraction<_Tp>())
+          __formatter::__format_sub_seconds(__value, __sstr);
+        break;
+
+        // Unlike time_put and strftime the formatting library requires %Y
+        //
+        // [tab:time.format.spec]
+        //   The year as a decimal number. If the result is less than four digits
+        //   it is left-padded with 0 to four digits.
+        //
+        // This means years in the range (-1000, 1000) need manual formatting.
+        // It's unclear whether %EY needs the same treatment. For example the
+        // Japanese EY contains the era name and year. This is zero-padded to 2
+        // digits in time_put (note that older glibc versions didn't do
+        // padding.) However most eras won't reach 100 years, let alone 1000.
+        // So padding to 4 digits seems unwanted for Japanese.
+        //
+        // The same applies to %Ex since that too depends on the era.
+        //
+        // %x the locale's date representation is currently doesn't handle the
+        // zero-padding too.
+        //
+        // The 4 digits can be implemented better at a later time. On POSIX
+        // systems the required information can be extracted by nl_langinfo
+        // https://man7.org/linux/man-pages/man3/nl_langinfo.3.html
+        //
+        // Note since year < -1000 is expected to be rare it uses the more
+        // expensive year routine.
+        //
+        // TODO FMT evaluate the comment above.
 
 #  if defined(__GLIBC__) || defined(_AIX)
       case _CharT('y'):
@@ -162,6 +249,18 @@ _LIBCPP_HIDE_FROM_ABI void __format_chrono_using_chrono_specs(
       } break;
 
       case _CharT('O'):
+        if constexpr (__use_fraction<_Tp>()) {
+          // Handle OS using the normal representation for the non-fractional
+          // part. There seems to be no locale information regarding how the
+          // fractional part should be formatted.
+          if (*(__it + 1) == 'S') {
+            ++__it;
+            __facet.put({__sstr}, __sstr, _CharT(' '), std::addressof(__t), __s, __it + 1);
+            __formatter::__format_sub_seconds(__value, __sstr);
+            break;
+          }
+        }
+        [[fallthrough]];
       case _CharT('E'):
         ++__it;
         [[fallthrough]];
@@ -207,10 +306,19 @@ __format_chrono(const _Tp& __value,
   if (__chrono_specs.empty())
     __sstr << __value;
   else {
-    if (__specs.__chrono_.__month_name_ && !__formatter::__month_name_ok(__value))
-      std::__throw_format_error("formatting a month name from an invalid month number");
+    if constexpr (chrono::__is_duration<_Tp>::value) {
+      if (__value < __value.zero())
+        __sstr << _CharT('-');
+      __formatter::__format_chrono_using_chrono_specs(chrono::abs(__value), __sstr, __chrono_specs);
+      // TODO FMT When keeping the precision it will truncate the string.
+      // Note that the behaviour what the precision does isn't specified.
+      __specs.__precision_ = -1;
+    } else {
+      if (__specs.__chrono_.__month_name_ && !__formatter::__month_name_ok(__value))
+        std::__throw_format_error("formatting a month name from an invalid month number");
 
-    __formatter::__format_chrono_using_chrono_specs(__value, __sstr, __chrono_specs);
+      __formatter::__format_chrono_using_chrono_specs(__value, __sstr, __chrono_specs);
+    }
   }
 
   // TODO FMT Use the stringstream's view after P0408R7 has been implemented.
@@ -238,6 +346,28 @@ struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT __formatter_chrono {
   __format_spec::__parser_chrono<_CharT> __parser_;
 };
 
+template <class _Rep, class _Period, __fmt_char_type _CharT>
+struct formatter<chrono::duration<_Rep, _Period>, _CharT> : public __formatter_chrono<_CharT> {
+public:
+  using _Base = __formatter_chrono<_CharT>;
+
+  _LIBCPP_HIDE_FROM_ABI constexpr auto parse(basic_format_parse_context<_CharT>& __parse_ctx)
+      -> decltype(__parse_ctx.begin()) {
+    // [time.format]/1
+    // Giving a precision specification in the chrono-format-spec is valid only
+    // for std::chrono::duration types where the representation type Rep is a
+    // floating-point type. For all other Rep types, an exception of type
+    // format_error is thrown if the chrono-format-spec contains a precision
+    // specification.
+    //
+    // Note this doesn't refer to chrono::treat_as_floating_point_v<_Rep>.
+    if constexpr (std::floating_point<_Rep>)
+      return _Base::__parse(__parse_ctx, __format_spec::__fields_chrono_fractional, __format_spec::__flags::__duration);
+    else
+      return _Base::__parse(__parse_ctx, __format_spec::__fields_chrono, __format_spec::__flags::__duration);
+  }
+};
+
 template <__fmt_char_type _CharT>
 struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter<chrono::day, _CharT>
     : public __formatter_chrono<_CharT> {

diff  --git a/libcxx/include/__chrono/ostream.h b/libcxx/include/__chrono/ostream.h
index 21951cf3d569e..36558ca8867de 100644
--- a/libcxx/include/__chrono/ostream.h
+++ b/libcxx/include/__chrono/ostream.h
@@ -11,12 +11,15 @@
 #define _LIBCPP___CHRONO_OSTREAM_H
 
 #include <__chrono/day.h>
+#include <__chrono/duration.h>
 #include <__chrono/month.h>
 #include <__chrono/statically_widen.h>
 #include <__chrono/year.h>
+#include <__concepts/same_as.h>
 #include <__config>
 #include <__format/format_functions.h>
 #include <ostream>
+#include <ratio>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -28,6 +31,71 @@ _LIBCPP_BEGIN_NAMESPACE_STD
 
 namespace chrono {
 
+// Depending on the type the return is a const _CharT* or a basic_string<_CharT>
+template <class _CharT, class _Period>
+_LIBCPP_HIDE_FROM_ABI auto __units_suffix() {
+  // TODO FMT LWG issue the suffixes are always char and not STATICALLY-WIDEN'ed.
+  if constexpr (same_as<typename _Period::type, atto>)
+    return _LIBCPP_STATICALLY_WIDEN(_CharT, "as");
+  else if constexpr (same_as<typename _Period::type, femto>)
+    return _LIBCPP_STATICALLY_WIDEN(_CharT, "fs");
+  else if constexpr (same_as<typename _Period::type, pico>)
+    return _LIBCPP_STATICALLY_WIDEN(_CharT, "ps");
+  else if constexpr (same_as<typename _Period::type, nano>)
+    return _LIBCPP_STATICALLY_WIDEN(_CharT, "ns");
+  else if constexpr (same_as<typename _Period::type, micro>)
+#  ifndef _LIBCPP_HAS_NO_UNICODE
+    return _LIBCPP_STATICALLY_WIDEN(_CharT, "\u00b5s");
+#  else
+    return _LIBCPP_STATICALLY_WIDEN(_CharT, "us");
+#  endif
+  else if constexpr (same_as<typename _Period::type, milli>)
+    return _LIBCPP_STATICALLY_WIDEN(_CharT, "ms");
+  else if constexpr (same_as<typename _Period::type, centi>)
+    return _LIBCPP_STATICALLY_WIDEN(_CharT, "cs");
+  else if constexpr (same_as<typename _Period::type, deci>)
+    return _LIBCPP_STATICALLY_WIDEN(_CharT, "ds");
+  else if constexpr (same_as<typename _Period::type, ratio<1>>)
+    return _LIBCPP_STATICALLY_WIDEN(_CharT, "s");
+  else if constexpr (same_as<typename _Period::type, deca>)
+    return _LIBCPP_STATICALLY_WIDEN(_CharT, "das");
+  else if constexpr (same_as<typename _Period::type, hecto>)
+    return _LIBCPP_STATICALLY_WIDEN(_CharT, "hs");
+  else if constexpr (same_as<typename _Period::type, kilo>)
+    return _LIBCPP_STATICALLY_WIDEN(_CharT, "ks");
+  else if constexpr (same_as<typename _Period::type, mega>)
+    return _LIBCPP_STATICALLY_WIDEN(_CharT, "Ms");
+  else if constexpr (same_as<typename _Period::type, giga>)
+    return _LIBCPP_STATICALLY_WIDEN(_CharT, "Gs");
+  else if constexpr (same_as<typename _Period::type, tera>)
+    return _LIBCPP_STATICALLY_WIDEN(_CharT, "Ts");
+  else if constexpr (same_as<typename _Period::type, peta>)
+    return _LIBCPP_STATICALLY_WIDEN(_CharT, "Ps");
+  else if constexpr (same_as<typename _Period::type, exa>)
+    return _LIBCPP_STATICALLY_WIDEN(_CharT, "Es");
+  else if constexpr (same_as<typename _Period::type, ratio<60>>)
+    return _LIBCPP_STATICALLY_WIDEN(_CharT, "min");
+  else if constexpr (same_as<typename _Period::type, ratio<3600>>)
+    return _LIBCPP_STATICALLY_WIDEN(_CharT, "h");
+  else if constexpr (same_as<typename _Period::type, ratio<86400>>)
+    return _LIBCPP_STATICALLY_WIDEN(_CharT, "d");
+  else if constexpr (_Period::den == 1)
+    return std::format(_LIBCPP_STATICALLY_WIDEN(_CharT, "[{}]s"), _Period::num);
+  else
+    return std::format(_LIBCPP_STATICALLY_WIDEN(_CharT, "[{}/{}]s"), _Period::num, _Period::den);
+}
+
+template <class _CharT, class _Traits, class _Rep, class _Period>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_AVAILABILITY_FORMAT basic_ostream<_CharT, _Traits>&
+operator<<(basic_ostream<_CharT, _Traits>& __os, const duration<_Rep, _Period>& __d) {
+  basic_ostringstream<_CharT, _Traits> __s;
+  __s.flags(__os.flags());
+  __s.imbue(__os.getloc());
+  __s.precision(__os.precision());
+  __s << __d.count() << chrono::__units_suffix<_CharT, _Period>();
+  return __os << __s.str();
+}
+
 template <class _CharT, class _Traits>
 _LIBCPP_HIDE_FROM_ABI _LIBCPP_AVAILABILITY_FORMAT basic_ostream<_CharT, _Traits>&
 operator<<(basic_ostream<_CharT, _Traits>& __os, const day& __d) {

diff  --git a/libcxx/include/__chrono/parser_std_format_spec.h b/libcxx/include/__chrono/parser_std_format_spec.h
index 3734cddceee87..cdf7b9b15fd80 100644
--- a/libcxx/include/__chrono/parser_std_format_spec.h
+++ b/libcxx/include/__chrono/parser_std_format_spec.h
@@ -29,6 +29,8 @@ _LIBCPP_BEGIN_NAMESPACE_STD
 namespace __format_spec {
 
 // By not placing this constant in the formatter class it's not duplicated for char and wchar_t
+inline constexpr __fields __fields_chrono_fractional{
+    .__precision_ = true, .__locale_specific_form_ = true, .__type_ = false};
 inline constexpr __fields __fields_chrono{.__locale_specific_form_ = true, .__type_ = false};
 
 /// Flags available or required in a chrono type.

diff  --git a/libcxx/include/chrono b/libcxx/include/chrono
index 6c3a51b2b3dc6..96dd23b6a62fb 100644
--- a/libcxx/include/chrono
+++ b/libcxx/include/chrono
@@ -207,7 +207,11 @@ template <class ToDuration, class Rep, class Period>
 template <class ToDuration, class Rep, class Period>
     constexpr ToDuration round(const duration<Rep, Period>& d);    // C++17
 
-// duration I/O is elsewhere
+// duration I/O
+template<class charT, class traits, class Rep, class Period>       // C++20
+  basic_ostream<charT, traits>&
+    operator<<(basic_ostream<charT, traits>& os,
+               const duration<Rep, Period>& d);
 
 // time_point arithmetic (all constexpr in C++14)
 template <class Clock, class Duration1, class Rep2, class Period2>
@@ -625,6 +629,8 @@ bool operator>=(const time_zone& x, const time_zone& y) noexcept;
 }  // chrono
 
 namespace std {
+  template<class Rep, class Period, class charT>
+    struct formatter<chrono::duration<Rep, Period>, charT>;                       // C++20
   template<class charT> struct formatter<chrono::day, charT>;                     // C++20
   template<class charT> struct formatter<chrono::month, charT>;                   // C++20
   template<class charT> struct formatter<chrono::year, charT>;                    // C++20

diff  --git a/libcxx/test/std/time/time.duration/time.duration.nonmember/ostream.pass.cpp b/libcxx/test/std/time/time.duration/time.duration.nonmember/ostream.pass.cpp
new file mode 100644
index 0000000000000..3b80abbbfa08a
--- /dev/null
+++ b/libcxx/test/std/time/time.duration/time.duration.nonmember/ostream.pass.cpp
@@ -0,0 +1,232 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-incomplete-format
+
+// TODO FMT Investigate Windows issues.
+// UNSUPPORTED: msvc, target={{.+}}-windows-gnu
+
+// REQUIRES: locale.fr_FR.UTF-8
+// REQUIRES: locale.ja_JP.UTF-8
+
+// <chrono>
+
+// template<class Rep, class Period = ratio<1>> class duration;
+
+// template<class charT, class traits, class Rep, class Period>
+//   basic_ostream<charT, traits>&
+//     operator<<(basic_ostream<charT, traits>& os,
+//                const duration<Rep, Period>& d);
+
+#include <chrono>
+
+#include <cassert>
+#include <concepts>
+#include <sstream>
+
+#include "make_string.h"
+#include "platform_support.h" // locale name macros
+#include "test_macros.h"
+
+#define SV(S) MAKE_STRING_VIEW(CharT, S)
+
+template <class CharT, class Rep, class Period>
+static std::basic_string<CharT> stream_c_locale(std::chrono::duration<Rep, Period> duration) {
+  std::basic_stringstream<CharT> sstr;
+  sstr.precision(4);
+  sstr << std::fixed << duration;
+  return sstr.str();
+}
+
+template <class CharT, class Rep, class Period>
+static std::basic_string<CharT> stream_fr_FR_locale(std::chrono::duration<Rep, Period> duration) {
+  std::basic_stringstream<CharT> sstr;
+  const std::locale locale(LOCALE_fr_FR_UTF_8);
+  sstr.imbue(locale);
+  sstr.precision(4);
+  sstr << std::fixed << duration;
+  return sstr.str();
+}
+
+template <class CharT, class Rep, class Period>
+static std::basic_string<CharT> stream_ja_JP_locale(std::chrono::duration<Rep, Period> duration) {
+  std::basic_stringstream<CharT> sstr;
+  const std::locale locale(LOCALE_ja_JP_UTF_8);
+  sstr.imbue(locale);
+  sstr.precision(4);
+  sstr << std::fixed << duration;
+  return sstr.str();
+}
+
+template <class CharT>
+static void test_values() {
+  using namespace std::literals::chrono_literals;
+
+  assert(stream_c_locale<CharT>(-1'000'000s) == SV("-1000000s"));
+  assert(stream_c_locale<CharT>(1'000'000s) == SV("1000000s"));
+  assert(stream_c_locale<CharT>(-1'000.123456s) == SV("-1000.1235s"));
+  assert(stream_c_locale<CharT>(1'000.123456s) == SV("1000.1235s"));
+
+  if constexpr (std::same_as<CharT, char>) {
+#if defined(__APPLE__)
+    assert(stream_fr_FR_locale<CharT>(-1'000'000s) == SV("-1000000s"));
+    assert(stream_fr_FR_locale<CharT>(1'000'000s) == SV("1000000s"));
+    assert(stream_fr_FR_locale<CharT>(-1'000.123456s) == SV("-1000,1235s"));
+    assert(stream_fr_FR_locale<CharT>(1'000.123456s) == SV("1000,1235s"));
+#else
+    assert(stream_fr_FR_locale<CharT>(-1'000'000s) == SV("-1 000 000s"));
+    assert(stream_fr_FR_locale<CharT>(1'000'000s) == SV("1 000 000s"));
+    assert(stream_fr_FR_locale<CharT>(-1'000.123456s) == SV("-1 000,1235s"));
+    assert(stream_fr_FR_locale<CharT>(1'000.123456s) == SV("1 000,1235s"));
+#endif
+  } else {
+#ifdef _WIN32
+    assert(stream_fr_FR_locale<CharT>(-1'000'000s) == SV("-1\u00A0000\u00A0000s"));
+    assert(stream_fr_FR_locale<CharT>(1'000'000s) == SV("1\u00A0000\u00A0000s"));
+    assert(stream_fr_FR_locale<CharT>(-1'000.123456s) == SV("-1\u00A0000,1235s"));
+    assert(stream_fr_FR_locale<CharT>(1'000.123456s) == SV("1\u00A0000,1235s"));
+#elif defined(__APPLE__)
+    assert(stream_fr_FR_locale<CharT>(-1'000'000s) == SV("-1000000s"));
+    assert(stream_fr_FR_locale<CharT>(1'000'000s) == SV("1000000s"));
+    assert(stream_fr_FR_locale<CharT>(-1'000.123456s) == SV("-1000,1235s"));
+    assert(stream_fr_FR_locale<CharT>(1'000.123456s) == SV("1000,1235s"));
+#else
+    assert(stream_fr_FR_locale<CharT>(-1'000'000s) == SV("-1\u202f000\u202f000s"));
+    assert(stream_fr_FR_locale<CharT>(1'000'000s) == SV("1\u202f000\u202f000s"));
+    assert(stream_fr_FR_locale<CharT>(-1'000.123456s) == SV("-1\u202f000,1235s"));
+    assert(stream_fr_FR_locale<CharT>(1'000.123456s) == SV("1\u202f000,1235s"));
+#endif
+  }
+
+  assert(stream_ja_JP_locale<CharT>(-1'000'000s) == SV("-1,000,000s"));
+  assert(stream_ja_JP_locale<CharT>(1'000'000s) == SV("1,000,000s"));
+  assert(stream_ja_JP_locale<CharT>(-1'000.123456s) == SV("-1,000.1235s"));
+  assert(stream_ja_JP_locale<CharT>(1'000.123456s) == SV("1,000.1235s"));
+}
+
+template <class CharT>
+static void test_units() {
+  using namespace std::literals::chrono_literals;
+
+  // C locale
+  assert(stream_c_locale<CharT>(std::chrono::duration<int, std::atto>(0)) == SV("0as"));
+  assert(stream_c_locale<CharT>(std::chrono::duration<int, std::femto>(0)) == SV("0fs"));
+  assert(stream_c_locale<CharT>(std::chrono::duration<int, std::pico>(0)) == SV("0ps"));
+  assert(stream_c_locale<CharT>(0ns) == SV("0ns"));
+#ifndef TEST_HAS_NO_UNICODE
+  assert(stream_c_locale<CharT>(0us) == SV("0\u00b5s"));
+#else
+  assert(stream_c_locale<CharT>(0us) == SV("0us"));
+#endif
+  assert(stream_c_locale<CharT>(0ms) == SV("0ms"));
+  assert(stream_c_locale<CharT>(std::chrono::duration<int, std::centi>(0)) == SV("0cs"));
+  assert(stream_c_locale<CharT>(std::chrono::duration<int, std::deci>(0)) == SV("0ds"));
+
+  assert(stream_c_locale<CharT>(0s) == SV("0s"));
+
+  assert(stream_c_locale<CharT>(std::chrono::duration<int, std::deca>(0)) == SV("0das"));
+  assert(stream_c_locale<CharT>(std::chrono::duration<int, std::hecto>(0)) == SV("0hs"));
+  assert(stream_c_locale<CharT>(std::chrono::duration<int, std::kilo>(0)) == SV("0ks"));
+  assert(stream_c_locale<CharT>(std::chrono::duration<int, std::mega>(0)) == SV("0Ms"));
+  assert(stream_c_locale<CharT>(std::chrono::duration<int, std::giga>(0)) == SV("0Gs"));
+  assert(stream_c_locale<CharT>(std::chrono::duration<int, std::tera>(0)) == SV("0Ts"));
+  assert(stream_c_locale<CharT>(std::chrono::duration<int, std::peta>(0)) == SV("0Ps"));
+  assert(stream_c_locale<CharT>(std::chrono::duration<int, std::exa>(0)) == SV("0Es"));
+
+  assert(stream_c_locale<CharT>(0min) == SV("0min"));
+  assert(stream_c_locale<CharT>(0h) == SV("0h"));
+  assert(stream_c_locale<CharT>(std::chrono::duration<int, std::ratio<86400>>(0)) == SV("0d"));
+
+  assert(stream_c_locale<CharT>(std::chrono::duration<int, std::ratio<42>>(0)) == SV("0[42]s"));
+  assert(stream_c_locale<CharT>(std::chrono::duration<int, std::ratio<33, 3>>(0)) == SV("0[11]s"));
+  assert(stream_c_locale<CharT>(std::chrono::duration<int, std::ratio<11, 9>>(0)) == SV("0[11/9]s"));
+
+  // fr_FR locale
+  assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::atto>(0)) == SV("0as"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::femto>(0)) == SV("0fs"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::pico>(0)) == SV("0ps"));
+  assert(stream_fr_FR_locale<CharT>(0ns) == SV("0ns"));
+#ifndef TEST_HAS_NO_UNICODE
+  assert(stream_fr_FR_locale<CharT>(0us) == SV("0\u00b5s"));
+#else
+  assert(stream_fr_FR_locale<CharT>(0us) == SV("0us"));
+#endif
+  assert(stream_fr_FR_locale<CharT>(0ms) == SV("0ms"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::centi>(0)) == SV("0cs"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::deci>(0)) == SV("0ds"));
+
+  assert(stream_fr_FR_locale<CharT>(0s) == SV("0s"));
+
+  assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::deca>(0)) == SV("0das"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::hecto>(0)) == SV("0hs"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::kilo>(0)) == SV("0ks"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::mega>(0)) == SV("0Ms"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::giga>(0)) == SV("0Gs"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::tera>(0)) == SV("0Ts"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::peta>(0)) == SV("0Ps"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::exa>(0)) == SV("0Es"));
+
+  assert(stream_fr_FR_locale<CharT>(0min) == SV("0min"));
+  assert(stream_fr_FR_locale<CharT>(0h) == SV("0h"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::ratio<86400>>(0)) == SV("0d"));
+
+  assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::ratio<42>>(0)) == SV("0[42]s"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::ratio<33, 3>>(0)) == SV("0[11]s"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::duration<int, std::ratio<11, 9>>(0)) == SV("0[11/9]s"));
+
+  // ja_JP locale
+  assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::atto>(0)) == SV("0as"));
+  assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::femto>(0)) == SV("0fs"));
+  assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::pico>(0)) == SV("0ps"));
+  assert(stream_ja_JP_locale<CharT>(0ns) == SV("0ns"));
+#ifndef TEST_HAS_NO_UNICODE
+  assert(stream_ja_JP_locale<CharT>(0us) == SV("0\u00b5s"));
+#else
+  assert(stream_ja_JP_locale<CharT>(0us) == SV("0us"));
+#endif
+  assert(stream_ja_JP_locale<CharT>(0ms) == SV("0ms"));
+  assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::centi>(0)) == SV("0cs"));
+  assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::deci>(0)) == SV("0ds"));
+
+  assert(stream_ja_JP_locale<CharT>(0s) == SV("0s"));
+
+  assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::deca>(0)) == SV("0das"));
+  assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::hecto>(0)) == SV("0hs"));
+  assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::kilo>(0)) == SV("0ks"));
+  assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::mega>(0)) == SV("0Ms"));
+  assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::giga>(0)) == SV("0Gs"));
+  assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::tera>(0)) == SV("0Ts"));
+  assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::peta>(0)) == SV("0Ps"));
+  assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::exa>(0)) == SV("0Es"));
+
+  assert(stream_ja_JP_locale<CharT>(0min) == SV("0min"));
+  assert(stream_ja_JP_locale<CharT>(0h) == SV("0h"));
+  assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::ratio<86400>>(0)) == SV("0d"));
+
+  assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::ratio<42>>(0)) == SV("0[42]s"));
+  assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::ratio<33, 3>>(0)) == SV("0[11]s"));
+  assert(stream_ja_JP_locale<CharT>(std::chrono::duration<int, std::ratio<11, 9>>(0)) == SV("0[11/9]s"));
+}
+
+template <class CharT>
+static void test() {
+  test_values<CharT>();
+  test_units<CharT>();
+}
+
+int main(int, char**) {
+  test<char>();
+
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test<wchar_t>();
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/time/time.syn/formatter.duration.pass.cpp b/libcxx/test/std/time/time.syn/formatter.duration.pass.cpp
new file mode 100644
index 0000000000000..e461c72c2ba7a
--- /dev/null
+++ b/libcxx/test/std/time/time.syn/formatter.duration.pass.cpp
@@ -0,0 +1,1091 @@
+//===----------------------------------------------------------------------===//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-incomplete-format
+
+// TODO FMT Investigate Windows issues.
+// UNSUPPORTED: msvc, target={{.+}}-windows-gnu
+
+// REQUIRES: locale.fr_FR.UTF-8
+// REQUIRES: locale.ja_JP.UTF-8
+
+// <chrono>
+
+//  template<class Rep, class Period, class charT>
+//    struct formatter<chrono::duration<Rep, Period>, charT>;
+
+#include <chrono>
+#include <format>
+
+#include <cassert>
+#include <concepts>
+#include <locale>
+#include <iostream>
+#include <type_traits>
+
+#include "formatter_tests.h"
+#include "make_string.h"
+#include "platform_support.h" // locale name macros
+#include "test_macros.h"
+
+template <class CharT>
+static void test_no_chrono_specs() {
+  using namespace std::literals::chrono_literals;
+
+  check(SV("1as"), SV("{}"), std::chrono::duration<int, std::atto>(1));
+  check(SV("1fs"), SV("{}"), std::chrono::duration<int, std::femto>(1));
+  check(SV("1ps"), SV("{}"), std::chrono::duration<int, std::pico>(1));
+  check(SV("1ns"), SV("{}"), 1ns);
+#ifndef TEST_HAS_NO_UNICODE
+  check(SV("1\u00b5s"), SV("{}"), 1us);
+#else
+  check(SV("1us"), SV("{}"), 1us);
+#endif
+  check(SV("1ms"), SV("{}"), 1ms);
+  check(SV("1cs"), SV("{}"), std::chrono::duration<int, std::centi>(1));
+  check(SV("1ds"), SV("{}"), std::chrono::duration<int, std::deci>(1));
+
+  check(SV("1s"), SV("{}"), 1s);
+
+  check(SV("1das"), SV("{}"), std::chrono::duration<int, std::deca>(1));
+  check(SV("1hs"), SV("{}"), std::chrono::duration<int, std::hecto>(1));
+  check(SV("1ks"), SV("{}"), std::chrono::duration<int, std::kilo>(1));
+  check(SV("1Ms"), SV("{}"), std::chrono::duration<int, std::mega>(1));
+  check(SV("1Gs"), SV("{}"), std::chrono::duration<int, std::giga>(1));
+  check(SV("1Ts"), SV("{}"), std::chrono::duration<int, std::tera>(1));
+  check(SV("1Ps"), SV("{}"), std::chrono::duration<int, std::peta>(1));
+  check(SV("1Es"), SV("{}"), std::chrono::duration<int, std::exa>(1));
+
+  check(SV("1min"), SV("{}"), 1min);
+  check(SV("1h"), SV("{}"), 1h);
+  check(SV("1d"), SV("{}"), std::chrono::duration<int, std::ratio<86400>>(1));
+
+  check(SV("1[42]s"), SV("{}"), std::chrono::duration<int, std::ratio<42>>(1));
+  check(SV("1[11]s"), SV("{}"), std::chrono::duration<int, std::ratio<33, 3>>(1));
+  check(SV("1[11/9]s"), SV("{}"), std::chrono::duration<int, std::ratio<11, 9>>(1));
+}
+
+template <class CharT>
+static void test_valid_positive_integral_values() {
+  using namespace std::literals::chrono_literals;
+
+  constexpr std::basic_string_view<CharT> fmt = SV(
+      "{:"
+      "%%H='%H'%t"
+      "%%OH='%OH'%t"
+      "%%I='%I'%t"
+      "%%OI='%OI'%t"
+      "%%M='%M'%t"
+      "%%OM='%OM'%t"
+      "%%S='%S'%t"
+      "%%OS='%OS'%t"
+      "%%p='%p'%t"
+      "%%R='%R'%t"
+      "%%T='%T'%t"
+      "%%r='%r'%t"
+      "%%X='%X'%t"
+      "%%EX='%EX'%t"
+      "%%j='%j'%t"
+      "%%Q='%Q'%t"
+      "%%q='%q'%t"
+      "%n}");
+  constexpr std::basic_string_view<CharT> lfmt = SV(
+      "{:L"
+      "%%H='%H'%t"
+      "%%OH='%OH'%t"
+      "%%I='%I'%t"
+      "%%OI='%OI'%t"
+      "%%M='%M'%t"
+      "%%OM='%OM'%t"
+      "%%S='%S'%t"
+      "%%OS='%OS'%t"
+      "%%p='%p'%t"
+      "%%R='%R'%t"
+      "%%T='%T'%t"
+      "%%r='%r'%t"
+      "%%X='%X'%t"
+      "%%EX='%EX'%t"
+      "%%j='%j'%t"
+      "%%Q='%Q'%t"
+      "%%q='%q'%t"
+      "%n}");
+
+  const std::locale loc(LOCALE_ja_JP_UTF_8);
+  std::locale::global(std::locale(LOCALE_fr_FR_UTF_8));
+
+  // Non localized output using C-locale
+  check(SV("%H='00'\t"
+           "%OH='00'\t"
+           "%I='12'\t"
+           "%OI='12'\t"
+           "%M='00'\t"
+           "%OM='00'\t"
+           "%S='00'\t"
+           "%OS='00'\t"
+           "%p='AM'\t"
+           "%R='00:00'\t"
+           "%T='00:00:00'\t"
+           "%r='12:00:00 AM'\t"
+           "%X='00:00:00'\t"
+           "%EX='00:00:00'\t"
+           "%j='0'\t"
+           "%Q='0'\t"
+           "%q='s'\t"
+           "\n"),
+        fmt,
+        0s);
+
+  check(SV("%H='11'\t"
+           "%OH='11'\t"
+           "%I='11'\t"
+           "%OI='11'\t"
+           "%M='59'\t"
+           "%OM='59'\t"
+           "%S='59'\t"
+           "%OS='59'\t"
+           "%p='AM'\t"
+           "%R='11:59'\t"
+           "%T='11:59:59'\t"
+           "%r='11:59:59 AM'\t"
+           "%X='11:59:59'\t"
+           "%EX='11:59:59'\t"
+           "%j='0'\t"
+           "%Q='43199'\t"
+           "%q='s'\t"
+           "\n"),
+        fmt,
+        11h + 59min + 59s);
+
+  check(SV("%H='12'\t"
+           "%OH='12'\t"
+           "%I='12'\t"
+           "%OI='12'\t"
+           "%M='00'\t"
+           "%OM='00'\t"
+           "%S='00'\t"
+           "%OS='00'\t"
+           "%p='PM'\t"
+           "%R='12:00'\t"
+           "%T='12:00:00'\t"
+           "%r='12:00:00 PM'\t"
+           "%X='12:00:00'\t"
+           "%EX='12:00:00'\t"
+           "%j='0'\t"
+           "%Q='12'\t"
+           "%q='h'\t"
+           "\n"),
+        fmt,
+        12h);
+
+  check(SV("%H='23'\t"
+           "%OH='23'\t"
+           "%I='11'\t"
+           "%OI='11'\t"
+           "%M='59'\t"
+           "%OM='59'\t"
+           "%S='59'\t"
+           "%OS='59'\t"
+           "%p='PM'\t"
+           "%R='23:59'\t"
+           "%T='23:59:59'\t"
+           "%r='11:59:59 PM'\t"
+           "%X='23:59:59'\t"
+           "%EX='23:59:59'\t"
+           "%j='0'\t"
+           "%Q='86399'\t"
+           "%q='s'\t"
+           "\n"),
+        fmt,
+        23h + 59min + 59s);
+
+  check(SV("%H='00'\t"
+           "%OH='00'\t"
+           "%I='12'\t"
+           "%OI='12'\t"
+           "%M='00'\t"
+           "%OM='00'\t"
+           "%S='00'\t"
+           "%OS='00'\t"
+           "%p='AM'\t"
+           "%R='00:00'\t"
+           "%T='00:00:00'\t"
+           "%r='12:00:00 AM'\t"
+           "%X='00:00:00'\t"
+           "%EX='00:00:00'\t"
+           "%j='7'\t"
+           "%Q='7'\t"
+           "%q='d'\t"
+           "\n"),
+        fmt,
+        std::chrono::duration<int, std::ratio<86400>>(7));
+
+  // Use the global locale (fr_FR)
+  check(SV("%H='00'\t"
+           "%OH='00'\t"
+           "%I='12'\t"
+           "%OI='12'\t"
+           "%M='00'\t"
+           "%OM='00'\t"
+           "%S='00'\t"
+           "%OS='00'\t"
+#if defined(_AIX)
+           "%p='AM'\t"
+#else
+           "%p=''\t"
+#endif
+           "%R='00:00'\t"
+           "%T='00:00:00'\t"
+#ifdef _WIN32
+           "%r='12:00:00'\t"
+#elif defined(_AIX)
+           "%r='12:00:00 AM'\t"
+#elif defined(__APPLE__)
+           "%r=''\t"
+#else
+           "%r='12:00:00 '\t"
+#endif
+           "%X='00:00:00'\t"
+           "%EX='00:00:00'\t"
+           "%j='0'\t"
+           "%Q='0'\t"
+           "%q='s'\t"
+           "\n"),
+        lfmt,
+        0s);
+
+  check(SV("%H='11'\t"
+           "%OH='11'\t"
+           "%I='11'\t"
+           "%OI='11'\t"
+           "%M='59'\t"
+           "%OM='59'\t"
+           "%S='59'\t"
+           "%OS='59'\t"
+#if defined(_AIX)
+           "%p='AM'\t"
+#else
+           "%p=''\t"
+#endif
+           "%R='11:59'\t"
+           "%T='11:59:59'\t"
+#ifdef _WIN32
+           "%r='11:59:59'\t"
+#elif defined(_AIX)
+           "%r='11:59:59 AM'\t"
+#elif defined(__APPLE__)
+           "%r=''\t"
+#else
+           "%r='11:59:59 '\t"
+#endif
+           "%X='11:59:59'\t"
+           "%EX='11:59:59'\t"
+           "%j='0'\t"
+           "%Q='43199'\t"
+           "%q='s'\t"
+           "\n"),
+        lfmt,
+        11h + 59min + 59s);
+
+  check(SV("%H='12'\t"
+           "%OH='12'\t"
+           "%I='12'\t"
+           "%OI='12'\t"
+           "%M='00'\t"
+           "%OM='00'\t"
+           "%S='00'\t"
+           "%OS='00'\t"
+#if defined(_AIX)
+           "%p='PM'\t"
+#else
+           "%p=''\t"
+#endif
+           "%R='12:00'\t"
+           "%T='12:00:00'\t"
+#ifdef _WIN32
+           "%r='00:00:00'\t"
+#elif defined(_AIX)
+           "%r='12:00:00 PM'\t"
+#elif defined(__APPLE__)
+           "%r=''\t"
+#else
+           "%r='12:00:00 '\t"
+#endif
+           "%X='12:00:00'\t"
+           "%EX='12:00:00'\t"
+           "%j='0'\t"
+           "%Q='12'\t"
+           "%q='h'\t"
+           "\n"),
+        lfmt,
+        12h);
+
+  check(SV("%H='23'\t"
+           "%OH='23'\t"
+           "%I='11'\t"
+           "%OI='11'\t"
+           "%M='59'\t"
+           "%OM='59'\t"
+           "%S='59'\t"
+           "%OS='59'\t"
+#if defined(_AIX)
+           "%p='PM'\t"
+#else
+           "%p=''\t"
+#endif
+           "%R='23:59'\t"
+           "%T='23:59:59'\t"
+#if defined(_AIX)
+           "%r='11:59:59 PM'\t"
+#elif defined(__APPLE__)
+           "%r=''\t"
+#else
+           "%r='11:59:59 '\t"
+#endif
+           "%X='23:59:59'\t"
+           "%EX='23:59:59'\t"
+           "%j='0'\t"
+           "%Q='86399'\t"
+           "%q='s'\t"
+           "\n"),
+        lfmt,
+        23h + 59min + 59s);
+
+  check(SV("%H='00'\t"
+           "%OH='00'\t"
+           "%I='12'\t"
+           "%OI='12'\t"
+           "%M='00'\t"
+           "%OM='00'\t"
+           "%S='00'\t"
+           "%OS='00'\t"
+#if defined(_AIX)
+           "%p='AM'\t"
+#else
+           "%p=''\t"
+#endif
+           "%R='00:00'\t"
+           "%T='00:00:00'\t"
+#ifdef _WIN32
+           "%r='12:00:00'\t"
+#elif defined(_AIX)
+           "%r='12:00:00 AM'\t"
+#elif defined(__APPLE__)
+           "%r=''\t"
+#else
+           "%r='12:00:00 '\t"
+#endif
+           "%X='00:00:00'\t"
+           "%EX='00:00:00'\t"
+           "%j='7'\t"
+           "%Q='7'\t"
+           "%q='d'\t"
+           "\n"),
+        lfmt,
+        std::chrono::duration<int, std::ratio<86400>>(7));
+
+  // Use supplied locale (ja_JP). This locale has a 
diff erent alternate.
+#if defined(__APPLE__) || defined(_AIX)
+  check(loc,
+        SV("%H='00'\t"
+           "%OH='00'\t"
+           "%I='12'\t"
+           "%OI='12'\t"
+           "%M='00'\t"
+           "%OM='00'\t"
+           "%S='00'\t"
+           "%OS='00'\t"
+#  if defined(__APPLE__)
+           "%p='AM'\t"
+#  else
+           "%p='午前'\t"
+#  endif
+           "%R='00:00'\t"
+           "%T='00:00:00'\t"
+#  if defined(__APPLE__)
+           "%r='12:00:00 AM'\t"
+           "%X='00時00分00秒'\t"
+           "%EX='00時00分00秒'\t"
+#  else
+           "%r='午前12:00:00'\t"
+           "%X='00:00:00'\t"
+           "%EX='00:00:00'\t"
+#  endif
+           "%j='0'\t"
+           "%Q='0'\t"
+           "%q='s'\t"
+           "\n"),
+        lfmt,
+        0s);
+
+  check(loc,
+        SV("%H='11'\t"
+           "%OH='11'\t"
+           "%I='11'\t"
+           "%OI='11'\t"
+           "%M='59'\t"
+           "%OM='59'\t"
+           "%S='59'\t"
+           "%OS='59'\t"
+#  if defined(__APPLE__)
+           "%p='AM'\t"
+#  else
+           "%p='午前'\t"
+#  endif
+           "%R='11:59'\t"
+           "%T='11:59:59'\t"
+#  if defined(__APPLE__)
+           "%r='11:59:59 AM'\t"
+           "%X='11時59分59秒'\t"
+           "%EX='11時59分59秒'\t"
+#  else
+           "%r='午前11:59:59'\t"
+           "%X='11:59:59'\t"
+           "%EX='11:59:59'\t"
+#  endif
+           "%j='0'\t"
+           "%Q='43199'\t"
+           "%q='s'\t"
+           "\n"),
+        lfmt,
+        11h + 59min + 59s);
+
+  check(loc,
+        SV("%H='12'\t"
+           "%OH='12'\t"
+           "%I='12'\t"
+           "%OI='12'\t"
+           "%M='00'\t"
+           "%OM='00'\t"
+           "%S='00'\t"
+           "%OS='00'\t"
+#  if defined(__APPLE__)
+           "%p='PM'\t"
+#  else
+           "%p='午後'\t"
+#  endif
+           "%R='12:00'\t"
+           "%T='12:00:00'\t"
+#  if defined(__APPLE__)
+           "%r='12:00:00 PM'\t"
+           "%X='12時00分00秒'\t"
+           "%EX='12時00分00秒'\t"
+#  else
+           "%r='午後12:00:00'\t"
+           "%X='12:00:00'\t"
+           "%EX='12:00:00'\t"
+#  endif
+           "%j='0'\t"
+           "%Q='12'\t"
+           "%q='h'\t"
+           "\n"),
+        lfmt,
+        12h);
+
+  check(loc,
+        SV("%H='23'\t"
+           "%OH='23'\t"
+           "%I='11'\t"
+           "%OI='11'\t"
+           "%M='59'\t"
+           "%OM='59'\t"
+           "%S='59'\t"
+           "%OS='59'\t"
+#  if defined(__APPLE__)
+           "%p='PM'\t"
+#  else
+           "%p='午後'\t"
+#  endif
+           "%R='23:59'\t"
+           "%T='23:59:59'\t"
+#  if defined(__APPLE__)
+           "%r='11:59:59 PM'\t"
+           "%X='23時59分59秒'\t"
+           "%EX='23時59分59秒'\t"
+#  else
+           "%r='午後11:59:59'\t"
+           "%X='23:59:59'\t"
+           "%EX='23:59:59'\t"
+#  endif
+           "%j='0'\t"
+           "%Q='86399'\t"
+           "%q='s'\t"
+           "\n"),
+        lfmt,
+        23h + 59min + 59s);
+
+  check(loc,
+        SV("%H='00'\t"
+           "%OH='00'\t"
+           "%I='12'\t"
+           "%OI='12'\t"
+           "%M='00'\t"
+           "%OM='00'\t"
+           "%S='00'\t"
+           "%OS='00'\t"
+#  if defined(__APPLE__)
+           "%p='AM'\t"
+#  else
+           "%p='午前'\t"
+#  endif
+           "%R='00:00'\t"
+           "%T='00:00:00'\t"
+#  if defined(__APPLE__)
+           "%r='12:00:00 AM'\t"
+           "%X='00時00分00秒'\t"
+           "%EX='00時00分00秒'\t"
+#  else
+           "%r='午前12:00:00'\t"
+           "%X='00:00:00'\t"
+           "%EX='00:00:00'\t"
+#  endif
+           "%j='7'\t"
+           "%Q='7'\t"
+           "%q='d'\t"
+           "\n"),
+        lfmt,
+        std::chrono::duration<int, std::ratio<86400>>(7));
+#else // defined(__APPLE__) || defined(_AIX)
+  check(loc,
+        SV("%H='00'\t"
+           "%OH='〇'\t"
+           "%I='12'\t"
+           "%OI='十二'\t"
+           "%M='00'\t"
+           "%OM='〇'\t"
+           "%S='00'\t"
+           "%OS='〇'\t"
+           "%p='午前'\t"
+           "%R='00:00'\t"
+           "%T='00:00:00'\t"
+           "%r='午前12時00分00秒'\t"
+           "%X='00時00分00秒'\t"
+           "%EX='00時00分00秒'\t"
+           "%j='0'\t"
+           "%Q='0'\t"
+           "%q='s'\t"
+           "\n"),
+        lfmt,
+        0s);
+
+  check(loc,
+        SV("%H='11'\t"
+           "%OH='十一'\t"
+           "%I='11'\t"
+           "%OI='十一'\t"
+           "%M='59'\t"
+           "%OM='五十九'\t"
+           "%S='59'\t"
+           "%OS='五十九'\t"
+           "%p='午前'\t"
+           "%R='11:59'\t"
+           "%T='11:59:59'\t"
+           "%r='午前11時59分59秒'\t"
+           "%X='11時59分59秒'\t"
+           "%EX='11時59分59秒'\t"
+           "%j='0'\t"
+           "%Q='43199'\t"
+           "%q='s'\t"
+           "\n"),
+        lfmt,
+        11h + 59min + 59s);
+
+  check(loc,
+        SV("%H='12'\t"
+           "%OH='十二'\t"
+           "%I='12'\t"
+           "%OI='十二'\t"
+           "%M='00'\t"
+           "%OM='〇'\t"
+           "%S='00'\t"
+           "%OS='〇'\t"
+           "%p='午後'\t"
+           "%R='12:00'\t"
+           "%T='12:00:00'\t"
+           "%r='午後12時00分00秒'\t"
+           "%X='12時00分00秒'\t"
+           "%EX='12時00分00秒'\t"
+           "%j='0'\t"
+           "%Q='12'\t"
+           "%q='h'\t"
+           "\n"),
+        lfmt,
+        12h);
+
+  check(loc,
+        SV("%H='23'\t"
+           "%OH='二十三'\t"
+           "%I='11'\t"
+           "%OI='十一'\t"
+           "%M='59'\t"
+           "%OM='五十九'\t"
+           "%S='59'\t"
+           "%OS='五十九'\t"
+           "%p='午後'\t"
+           "%R='23:59'\t"
+           "%T='23:59:59'\t"
+           "%r='午後11時59分59秒'\t"
+           "%X='23時59分59秒'\t"
+           "%EX='23時59分59秒'\t"
+           "%j='0'\t"
+           "%Q='86399'\t"
+           "%q='s'\t"
+           "\n"),
+        lfmt,
+        23h + 59min + 59s);
+
+  check(loc,
+        SV("%H='00'\t"
+           "%OH='〇'\t"
+           "%I='12'\t"
+           "%OI='十二'\t"
+           "%M='00'\t"
+           "%OM='〇'\t"
+           "%S='00'\t"
+           "%OS='〇'\t"
+           "%p='午前'\t"
+           "%R='00:00'\t"
+           "%T='00:00:00'\t"
+           "%r='午前12時00分00秒'\t"
+           "%X='00時00分00秒'\t"
+           "%EX='00時00分00秒'\t"
+           "%j='7'\t"
+           "%Q='7'\t"
+           "%q='d'\t"
+           "\n"),
+        lfmt,
+        std::chrono::duration<int, std::ratio<86400>>(7));
+
+#endif // defined(__APPLE__) || defined(_AIX)
+  std::locale::global(std::locale::classic());
+}
+
+template <class CharT>
+static void test_valid_negative_integral_values() {
+  // [time.format]/4 The result of formatting a std::chrono::duration instance
+  // holding a negative value, or an hh_mm_ss object h for which
+  // h.is_negative() is true, is equivalent to the output of the corresponding
+  // positive value, with a STATICALLY-WIDEN<charT>("-") character sequence
+  // placed before the replacement of the initial conversion specifier.
+  //
+  // Note in this case %% is the initial conversion specifier.
+  using namespace std::literals::chrono_literals;
+
+  constexpr std::basic_string_view<CharT> fmt = SV(
+      "{:"
+      "%%H='%H'%t"
+      "%%OH='%OH'%t"
+      "%%I='%I'%t"
+      "%%OI='%OI'%t"
+      "%%M='%M'%t"
+      "%%OM='%OM'%t"
+      "%%S='%S'%t"
+      "%%OS='%OS'%t"
+      "%%p='%p'%t"
+      "%%R='%R'%t"
+      "%%T='%T'%t"
+      "%%r='%r'%t"
+      "%%X='%X'%t"
+      "%%EX='%EX'%t"
+      "%%j='%j'%t"
+      "%%Q='%Q'%t"
+      "%%q='%q'%t"
+      "%n}");
+  constexpr std::basic_string_view<CharT> lfmt = SV(
+      "{:L"
+      "%%H='%H'%t"
+      "%%OH='%OH'%t"
+      "%%I='%I'%t"
+      "%%OI='%OI'%t"
+      "%%M='%M'%t"
+      "%%OM='%OM'%t"
+      "%%S='%S'%t"
+      "%%OS='%OS'%t"
+      "%%p='%p'%t"
+      "%%R='%R'%t"
+      "%%T='%T'%t"
+      "%%r='%r'%t"
+      "%%X='%X'%t"
+      "%%EX='%EX'%t"
+      "%%j='%j'%t"
+      "%%Q='%Q'%t"
+      "%%q='%q'%t"
+      "%n}");
+
+  const std::locale loc(LOCALE_ja_JP_UTF_8);
+  std::locale::global(std::locale(LOCALE_fr_FR_UTF_8));
+
+  // Non localized output using C-locale
+  check(SV("-%H='23'\t"
+           "%OH='23'\t"
+           "%I='11'\t"
+           "%OI='11'\t"
+           "%M='59'\t"
+           "%OM='59'\t"
+           "%S='59'\t"
+           "%OS='59'\t"
+           "%p='PM'\t"
+           "%R='23:59'\t"
+           "%T='23:59:59'\t"
+           "%r='11:59:59 PM'\t"
+           "%X='23:59:59'\t"
+           "%EX='23:59:59'\t"
+           "%j='0'\t"
+           "%Q='86399'\t"
+           "%q='s'\t"
+           "\n"),
+        fmt,
+        -(23h + 59min + 59s));
+
+  // Use the global locale (fr_FR)
+  check(SV("-%H='23'\t"
+           "%OH='23'\t"
+           "%I='11'\t"
+           "%OI='11'\t"
+           "%M='59'\t"
+           "%OM='59'\t"
+           "%S='59'\t"
+           "%OS='59'\t"
+#if defined(_AIX)
+           "%p='PM'\t"
+#else
+           "%p=''\t"
+#endif
+           "%R='23:59'\t"
+           "%T='23:59:59'\t"
+#if defined(_AIX)
+           "%r='11:59:59 PM'\t"
+#elif defined(__APPLE__)
+           "%r=''\t"
+#else
+           "%r='11:59:59 '\t"
+#endif
+           "%X='23:59:59'\t"
+           "%EX='23:59:59'\t"
+           "%j='0'\t"
+           "%Q='86399'\t"
+           "%q='s'\t"
+           "\n"),
+        lfmt,
+        -(23h + 59min + 59s));
+
+  // Use supplied locale (ja_JP). This locale has a 
diff erent alternate.
+#if defined(__APPLE__) || defined(_AIX)
+  check(loc,
+        SV("-%H='23'\t"
+           "%OH='23'\t"
+           "%I='11'\t"
+           "%OI='11'\t"
+           "%M='59'\t"
+           "%OM='59'\t"
+           "%S='59'\t"
+           "%OS='59'\t"
+#  if defined(__APPLE__)
+           "%p='PM'\t"
+#  else
+           "%p='午後'\t"
+#  endif
+           "%R='23:59'\t"
+           "%T='23:59:59'\t"
+#  if defined(__APPLE__)
+           "%r='11:59:59 PM'\t"
+           "%X='23時59分59秒'\t"
+           "%EX='23時59分59秒'\t"
+#  else
+           "%r='午後11:59:59'\t"
+           "%X='23:59:59'\t"
+           "%EX='23:59:59'\t"
+#  endif
+           "%j='0'\t"
+           "%Q='86399'\t"
+           "%q='s'\t"
+           "\n"),
+        lfmt,
+        -(23h + 59min + 59s));
+#else  // defined(__APPLE__) || defined(_AIX)
+  check(loc,
+        SV("-%H='23'\t"
+           "%OH='二十三'\t"
+           "%I='11'\t"
+           "%OI='十一'\t"
+           "%M='59'\t"
+           "%OM='五十九'\t"
+           "%S='59'\t"
+           "%OS='五十九'\t"
+           "%p='午後'\t"
+           "%R='23:59'\t"
+           "%T='23:59:59'\t"
+           "%r='午後11時59分59秒'\t"
+           "%X='23時59分59秒'\t"
+           "%EX='23時59分59秒'\t"
+           "%j='0'\t"
+           "%Q='86399'\t"
+           "%q='s'\t"
+           "\n"),
+        lfmt,
+        -(23h + 59min + 59s));
+#endif // defined(__APPLE__) || defined(_AIX)
+  std::locale::global(std::locale::classic());
+}
+
+template <class CharT>
+static void test_valid_fractional_values() {
+  using namespace std::literals::chrono_literals;
+
+  const std::locale loc(LOCALE_ja_JP_UTF_8);
+  std::locale::global(std::locale(LOCALE_fr_FR_UTF_8));
+
+  // Non localized output using C-locale
+  check(SV("00.000000001"), SV("{:%S}"), 1ns);
+  check(SV("00.000000501"), SV("{:%S}"), 501ns);
+  check(SV("00.000001000"), SV("{:%S}"), 1000ns);
+  check(SV("00.000000000001"), SV("{:%S}"), std::chrono::duration<int, std::pico>(1));
+  check(SV("00.000000000000001"), SV("{:%S}"), std::chrono::duration<int, std::femto>(1));
+  check(SV("00.000000000000000001"), SV("{:%S}"), std::chrono::duration<int, std::atto>(1));
+
+  check(SV("00.001"), SV("{:%S}"), 1ms);
+  check(SV("00.01"), SV("{:%S}"), std::chrono::duration<int, std::centi>(1));
+  check(SV("00.1"), SV("{:%S}"), std::chrono::duration<int, std::deci>(1));
+  check(SV("01.1"), SV("{:%S}"), std::chrono::duration<int, std::deci>(11));
+
+  check(SV("00.001"), SV("{:%S}"), std::chrono::duration<float, std::milli>(1.123456789));
+  check(SV("00.011"), SV("{:%S}"), std::chrono::duration<double, std::milli>(11.123456789));
+  check(SV("01"), SV("{:%S}"), std::chrono::duration<long double>(61.123456789));
+
+  check(SV("00.000000001"), SV("{:%OS}"), 1ns);
+  check(SV("00.000000501"), SV("{:%OS}"), 501ns);
+  check(SV("00.000001000"), SV("{:%OS}"), 1000ns);
+  check(SV("00.000000000001"), SV("{:%OS}"), std::chrono::duration<int, std::pico>(1));
+  check(SV("00.000000000000001"), SV("{:%OS}"), std::chrono::duration<int, std::femto>(1));
+  check(SV("00.000000000000000001"), SV("{:%OS}"), std::chrono::duration<int, std::atto>(1));
+
+  check(SV("00.001"), SV("{:%OS}"), 1ms);
+  check(SV("00.01"), SV("{:%OS}"), std::chrono::duration<int, std::centi>(1));
+  check(SV("00.1"), SV("{:%OS}"), std::chrono::duration<int, std::deci>(1));
+  check(SV("01.1"), SV("{:%OS}"), std::chrono::duration<int, std::deci>(11));
+
+  check(SV("00.001"), SV("{:%OS}"), std::chrono::duration<float, std::milli>(1.123456789));
+  check(SV("00.011"), SV("{:%OS}"), std::chrono::duration<double, std::milli>(11.123456789));
+  check(SV("01"), SV("{:%OS}"), std::chrono::duration<long double>(61.123456789));
+
+  check(SV("01:05:06.000000001"), SV("{:%T}"), 1h + 5min + 6s + 1ns);
+  check(SV("01:05:06.000000501"), SV("{:%T}"), 1h + 5min + 6s + 501ns);
+  check(SV("01:05:06.000001000"), SV("{:%T}"), 1h + 5min + 6s + 1000ns);
+
+  check(SV("01:05:06.001"), SV("{:%T}"), 1h + 5min + 6s + 1ms);
+  check(SV("01:05:06.01"), SV("{:%T}"), 1h + 5min + 6s + std::chrono::duration<int, std::centi>(1));
+  check(SV("01:05:06.1"), SV("{:%T}"), 1h + 5min + 6s + std::chrono::duration<int, std::deci>(1));
+  check(SV("01:05:07.1"), SV("{:%T}"), 1h + 5min + 6s + std::chrono::duration<int, std::deci>(11));
+
+  check(
+      SV("00:01:02"), SV("{:%T}"), std::chrono::duration<float, std::ratio<60>>(1) + std::chrono::duration<float>(2.5));
+  check(SV("01:05:11"),
+        SV("{:%T}"),
+        std::chrono::duration<double, std::ratio<3600>>(1) + std::chrono::duration<double, std::ratio<60>>(5) +
+            std::chrono::duration<double>(11.123456789));
+  check(SV("01:06:01"),
+        SV("{:%T}"),
+        std::chrono::duration<long double, std::ratio<3600>>(1) +
+            std::chrono::duration<long double, std::ratio<60>>(5) + std::chrono::duration<long double>(61.123456789));
+
+  check(SV("0"), SV("{:%j}"), std::chrono::duration<float, std::milli>(1.));
+  check(SV("1"), SV("{:%j}"), std::chrono::duration<double, std::milli>(86'400'000));
+  check(SV("1"), SV("{:%j}"), std::chrono::duration<long double, std::ratio<7 * 24 * 3600>>(0.14285714286));
+
+  check(SV("1000000"), SV("{:%Q}"), 1'000'000s);
+  check(SV("1"), SV("{:%Q}"), std::chrono::duration<float, std::milli>(1.));
+  check(SV("1.123456789"), SV("{:.6%Q}"), std::chrono::duration<double, std::milli>(1.123456789));
+  check(SV("1.123456789"), SV("{:.9%Q}"), std::chrono::duration<long double, std::milli>(1.123456789));
+
+  // Use the global locale (fr_FR)
+  check(SV("00,000000001"), SV("{:L%S}"), 1ns);
+  check(SV("00,000000501"), SV("{:L%S}"), 501ns);
+  check(SV("00,000001000"), SV("{:L%S}"), 1000ns);
+  check(SV("00,000000000001"), SV("{:L%S}"), std::chrono::duration<int, std::pico>(1));
+  check(SV("00,000000000000001"), SV("{:L%S}"), std::chrono::duration<int, std::femto>(1));
+  check(SV("00,000000000000000001"), SV("{:L%S}"), std::chrono::duration<int, std::atto>(1));
+
+  check(SV("00,001"), SV("{:L%S}"), 1ms);
+  check(SV("00,01"), SV("{:L%S}"), std::chrono::duration<int, std::centi>(1));
+  check(SV("00,1"), SV("{:L%S}"), std::chrono::duration<int, std::deci>(1));
+  check(SV("01,1"), SV("{:L%S}"), std::chrono::duration<int, std::deci>(11));
+
+  check(SV("00,001"), SV("{:L%S}"), std::chrono::duration<float, std::milli>(1.123456789));
+  check(SV("00,011"), SV("{:L%S}"), std::chrono::duration<double, std::milli>(11.123456789));
+  check(SV("01"), SV("{:L%S}"), std::chrono::duration<long double>(61.123456789));
+
+  check(SV("00,000000001"), SV("{:L%OS}"), 1ns);
+  check(SV("00,000000501"), SV("{:L%OS}"), 501ns);
+  check(SV("00,000001000"), SV("{:L%OS}"), 1000ns);
+  check(SV("00,000000000001"), SV("{:L%OS}"), std::chrono::duration<int, std::pico>(1));
+  check(SV("00,000000000000001"), SV("{:L%OS}"), std::chrono::duration<int, std::femto>(1));
+  check(SV("00,000000000000000001"), SV("{:L%OS}"), std::chrono::duration<int, std::atto>(1));
+
+  check(SV("00,001"), SV("{:L%OS}"), 1ms);
+  check(SV("00,01"), SV("{:L%OS}"), std::chrono::duration<int, std::centi>(1));
+  check(SV("00,1"), SV("{:L%OS}"), std::chrono::duration<int, std::deci>(1));
+  check(SV("01,1"), SV("{:L%OS}"), std::chrono::duration<int, std::deci>(11));
+
+  check(SV("00,001"), SV("{:L%OS}"), std::chrono::duration<float, std::milli>(1.123456789));
+  check(SV("00,011"), SV("{:L%OS}"), std::chrono::duration<double, std::milli>(11.123456789));
+  check(SV("01"), SV("{:L%OS}"), std::chrono::duration<long double>(61.123456789));
+
+  check(SV("01:05:06,000000001"), SV("{:L%T}"), 1h + 5min + 6s + 1ns);
+  check(SV("01:05:06,000000501"), SV("{:L%T}"), 1h + 5min + 6s + 501ns);
+  check(SV("01:05:06,000001000"), SV("{:L%T}"), 1h + 5min + 6s + 1000ns);
+
+  check(SV("01:05:06,001"), SV("{:L%T}"), 1h + 5min + 6s + 1ms);
+  check(SV("01:05:06,01"), SV("{:L%T}"), 1h + 5min + 6s + std::chrono::duration<int, std::centi>(1));
+  check(SV("01:05:06,1"), SV("{:L%T}"), 1h + 5min + 6s + std::chrono::duration<int, std::deci>(1));
+  check(SV("01:05:07,1"), SV("{:L%T}"), 1h + 5min + 6s + std::chrono::duration<int, std::deci>(11));
+
+  check(SV("00:01:02"),
+        SV("{:L%T}"),
+        std::chrono::duration<float, std::ratio<60>>(1) + std::chrono::duration<float>(2.5));
+  check(SV("01:05:11"),
+        SV("{:L%T}"),
+        std::chrono::duration<double, std::ratio<3600>>(1) + std::chrono::duration<double, std::ratio<60>>(5) +
+            std::chrono::duration<double>(11.123456789));
+  check(SV("01:06:01"),
+        SV("{:L%T}"),
+        std::chrono::duration<long double, std::ratio<3600>>(1) +
+            std::chrono::duration<long double, std::ratio<60>>(5) + std::chrono::duration<long double>(61.123456789));
+
+  check(SV("0"), SV("{:L%j}"), std::chrono::duration<float, std::milli>(1.));
+  check(SV("1"), SV("{:L%j}"), std::chrono::duration<double, std::milli>(86'400'000));
+  check(SV("1"), SV("{:L%j}"), std::chrono::duration<long double, std::ratio<7 * 24 * 3600>>(0.14285714286));
+
+  check(SV("1000000"), SV("{:L%Q}"), 1'000'000s); // The Standard mandates not localized.
+  check(SV("1"), SV("{:L%Q}"), std::chrono::duration<float, std::milli>(1.));
+  check(SV("1.123456789"), SV("{:.6L%Q}"), std::chrono::duration<double, std::milli>(1.123456789));
+  check(SV("1.123456789"), SV("{:.9L%Q}"), std::chrono::duration<long double, std::milli>(1.123456789));
+
+  // Use supplied locale (ja_JP). This locale has a 
diff erent alternate.
+  check(loc, SV("00.000000001"), SV("{:L%S}"), 1ns);
+  check(loc, SV("00.000000501"), SV("{:L%S}"), 501ns);
+  check(loc, SV("00.000001000"), SV("{:L%S}"), 1000ns);
+  check(loc, SV("00.000000000001"), SV("{:L%S}"), std::chrono::duration<int, std::pico>(1));
+  check(loc, SV("00.000000000000001"), SV("{:L%S}"), std::chrono::duration<int, std::femto>(1));
+  check(loc, SV("00.000000000000000001"), SV("{:L%S}"), std::chrono::duration<int, std::atto>(1));
+
+  check(loc, SV("00.001"), SV("{:L%S}"), 1ms);
+  check(loc, SV("00.01"), SV("{:L%S}"), std::chrono::duration<int, std::centi>(1));
+  check(loc, SV("00.1"), SV("{:L%S}"), std::chrono::duration<int, std::deci>(1));
+  check(loc, SV("01.1"), SV("{:L%S}"), std::chrono::duration<int, std::deci>(11));
+
+  check(loc, SV("00.001"), SV("{:L%S}"), std::chrono::duration<float, std::milli>(1.123456789));
+  check(loc, SV("00.011"), SV("{:L%S}"), std::chrono::duration<double, std::milli>(11.123456789));
+  check(loc, SV("01"), SV("{:L%S}"), std::chrono::duration<long double>(61.123456789));
+
+#if defined(__APPLE__) || defined(_AIX)
+  check(SV("00.000000001"), SV("{:%OS}"), 1ns);
+  check(SV("00.000000501"), SV("{:%OS}"), 501ns);
+  check(SV("00.000001000"), SV("{:%OS}"), 1000ns);
+  check(SV("00.000000000001"), SV("{:%OS}"), std::chrono::duration<int, std::pico>(1));
+  check(SV("00.000000000000001"), SV("{:%OS}"), std::chrono::duration<int, std::femto>(1));
+  check(SV("00.000000000000000001"), SV("{:%OS}"), std::chrono::duration<int, std::atto>(1));
+
+  check(SV("00.001"), SV("{:%OS}"), 1ms);
+  check(SV("00.01"), SV("{:%OS}"), std::chrono::duration<int, std::centi>(1));
+  check(SV("00.1"), SV("{:%OS}"), std::chrono::duration<int, std::deci>(1));
+  check(SV("01.1"), SV("{:%OS}"), std::chrono::duration<int, std::deci>(11));
+
+  check(SV("00.001"), SV("{:%OS}"), std::chrono::duration<float, std::milli>(1.123456789));
+  check(SV("00.011"), SV("{:%OS}"), std::chrono::duration<double, std::milli>(11.123456789));
+  check(SV("01"), SV("{:%OS}"), std::chrono::duration<long double>(61.123456789));
+#else  // defined(__APPLE__) || defined(_AIX)
+  check(loc, SV("〇.000000001"), SV("{:L%OS}"), 1ns);
+  check(loc, SV("〇.000000501"), SV("{:L%OS}"), 501ns);
+  check(loc, SV("〇.000001000"), SV("{:L%OS}"), 1000ns);
+  check(loc, SV("〇.000000000001"), SV("{:L%OS}"), std::chrono::duration<int, std::pico>(1));
+  check(loc, SV("〇.000000000000001"), SV("{:L%OS}"), std::chrono::duration<int, std::femto>(1));
+  check(loc, SV("〇.000000000000000001"), SV("{:L%OS}"), std::chrono::duration<int, std::atto>(1));
+
+  check(loc, SV("〇.001"), SV("{:L%OS}"), 1ms);
+  check(loc, SV("〇.01"), SV("{:L%OS}"), std::chrono::duration<int, std::centi>(1));
+  check(loc, SV("〇.1"), SV("{:L%OS}"), std::chrono::duration<int, std::deci>(1));
+  check(loc, SV("一.1"), SV("{:L%OS}"), std::chrono::duration<int, std::deci>(11));
+
+  check(loc, SV("〇.001"), SV("{:L%OS}"), std::chrono::duration<float, std::milli>(1.123456789));
+  check(loc, SV("〇.011"), SV("{:L%OS}"), std::chrono::duration<double, std::milli>(11.123456789));
+  check(loc, SV("一"), SV("{:L%OS}"), std::chrono::duration<long double>(61.123456789));
+#endif // defined(__APPLE__) || defined(_AIX)
+
+  check(loc, SV("01:05:06.000000001"), SV("{:L%T}"), 1h + 5min + 6s + 1ns);
+  check(loc, SV("01:05:06.000000501"), SV("{:L%T}"), 1h + 5min + 6s + 501ns);
+  check(loc, SV("01:05:06.000001000"), SV("{:L%T}"), 1h + 5min + 6s + 1000ns);
+
+  check(loc, SV("01:05:06.001"), SV("{:L%T}"), 1h + 5min + 6s + 1ms);
+  check(loc, SV("01:05:06.01"), SV("{:L%T}"), 1h + 5min + 6s + std::chrono::duration<int, std::centi>(1));
+  check(loc, SV("01:05:06.1"), SV("{:L%T}"), 1h + 5min + 6s + std::chrono::duration<int, std::deci>(1));
+  check(loc, SV("01:05:07.1"), SV("{:L%T}"), 1h + 5min + 6s + std::chrono::duration<int, std::deci>(11));
+
+  check(loc,
+        SV("00:01:02"),
+        SV("{:L%T}"),
+        std::chrono::duration<float, std::ratio<60>>(1) + std::chrono::duration<float>(2.5));
+  check(loc,
+        SV("01:05:11"),
+        SV("{:L%T}"),
+        std::chrono::duration<double, std::ratio<3600>>(1) + std::chrono::duration<double, std::ratio<60>>(5) +
+            std::chrono::duration<double>(11.123456789));
+  check(loc,
+        SV("01:06:01"),
+        SV("{:L%T}"),
+        std::chrono::duration<long double, std::ratio<3600>>(1) +
+            std::chrono::duration<long double, std::ratio<60>>(5) + std::chrono::duration<long double>(61.123456789));
+
+  check(loc, SV("0"), SV("{:L%j}"), std::chrono::duration<float, std::milli>(1.));
+  check(loc, SV("1"), SV("{:L%j}"), std::chrono::duration<double, std::milli>(86'400'000));
+  check(loc, SV("1"), SV("{:L%j}"), std::chrono::duration<long double, std::ratio<7 * 24 * 3600>>(0.14285714286));
+
+  check(loc, SV("1000000"), SV("{:L%Q}"), 1'000'000s); // The Standard mandates not localized.
+  check(loc, SV("1"), SV("{:L%Q}"), std::chrono::duration<float, std::milli>(1.));
+  check(loc, SV("1.123456789"), SV("{:.6L%Q}"), std::chrono::duration<double, std::milli>(1.123456789));
+  check(loc, SV("1.123456789"), SV("{:.9L%Q}"), std::chrono::duration<long double, std::milli>(1.123456789));
+
+  std::locale::global(std::locale::classic());
+}
+
+template <class CharT>
+static void test_valid_values() {
+  test_valid_positive_integral_values<CharT>();
+  test_valid_negative_integral_values<CharT>();
+  test_valid_fractional_values<CharT>();
+}
+
+template <class CharT>
+static void test() {
+  using namespace std::literals::chrono_literals;
+
+  test_no_chrono_specs<CharT>();
+  test_valid_values<CharT>();
+  check_invalid_types<CharT>(
+      {SV("H"), SV("I"), SV("j"), SV("M"), SV("n"), SV("O"),  SV("p"),  SV("q"),  SV("Q"),  SV("r"),
+       SV("R"), SV("S"), SV("t"), SV("T"), SV("X"), SV("EX"), SV("OH"), SV("OI"), SV("OM"), SV("OS")},
+      0ms);
+
+  check_exception("Expected '%' or '}' in the chrono format-string", SV("{:A"), 0ms);
+  check_exception("The chrono-specs contains a '{'", SV("{:%%{"), 0ms);
+  check_exception("End of input while parsing the modifier chrono conversion-spec", SV("{:%"), 0ms);
+  check_exception("End of input while parsing the modifier E", SV("{:%E"), 0ms);
+  check_exception("End of input while parsing the modifier O", SV("{:%O"), 0ms);
+
+  // Precision not allowed
+  check_exception("Expected '%' or '}' in the chrono format-string", SV("{:.3}"), 0ms);
+}
+
+int main(int, char**) {
+  test<char>();
+
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test<wchar_t>();
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp b/libcxx/test/std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp
index f5002577fc6e1..7d7f3e83e054a 100644
--- a/libcxx/test/std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp
+++ b/libcxx/test/std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp
@@ -130,7 +130,7 @@ void test_P1361() {
 // In libc++ std:::ostringstream requires localization support.
 #ifndef TEST_HAS_NO_LOCALIZATION
 
-  assert_is_not_formattable<std::chrono::microseconds, CharT>();
+  assert_is_formattable<std::chrono::microseconds, CharT>();
 
   assert_is_not_formattable<std::chrono::sys_time<std::chrono::microseconds>, CharT>();
   //assert_is_formattable<std::chrono::utc_time<std::chrono::microseconds>, CharT>();


        


More information about the libcxx-commits mailing list