[libcxx-commits] [libcxx] 7f5d130 - [libc++][chrono] Add hh_mm_ss formatter.

Mark de Wever via libcxx-commits libcxx-commits at lists.llvm.org
Tue Feb 14 10:12:24 PST 2023


Author: Mark de Wever
Date: 2023-02-14T19:12:19+01:00
New Revision: 7f5d130a428f3d605f8a38d48f7ed7d8f6087ee2

URL: https://github.com/llvm/llvm-project/commit/7f5d130a428f3d605f8a38d48f7ed7d8f6087ee2
DIFF: https://github.com/llvm/llvm-project/commit/7f5d130a428f3d605f8a38d48f7ed7d8f6087ee2.diff

LOG: [libc++][chrono] Add hh_mm_ss formatter.

Partially implements:
- P1361 Integration of chrono with text formatting
- P2372 Fixing locale handling in chrono formatters
- P1466 Miscellaneous minor fixes for chrono

Depends on D137022

Reviewed By: ldionne, #libc

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

Added: 
    libcxx/include/__chrono/concepts.h
    libcxx/test/libcxx/time/convert_to_tm.pass.cpp
    libcxx/test/std/time/time.hms/time.hms.nonmembers/ostream.pass.cpp
    libcxx/test/std/time/time.syn/formatter.hh_mm_ss.pass.cpp

Modified: 
    libcxx/docs/Status/FormatPaper.csv
    libcxx/include/CMakeLists.txt
    libcxx/include/__chrono/convert_to_tm.h
    libcxx/include/__chrono/formatter.h
    libcxx/include/__chrono/hh_mm_ss.h
    libcxx/include/__chrono/ostream.h
    libcxx/include/__chrono/parser_std_format_spec.h
    libcxx/include/__format/parser_std_format_spec.h
    libcxx/include/chrono
    libcxx/include/module.modulemap.in
    libcxx/test/libcxx/private_headers.verify.cpp
    libcxx/test/std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/Status/FormatPaper.csv b/libcxx/docs/Status/FormatPaper.csv
index 8b91cfdfb9d14..d2f9fdf6c8efe 100644
--- a/libcxx/docs/Status/FormatPaper.csv
+++ b/libcxx/docs/Status/FormatPaper.csv
@@ -23,7 +23,7 @@ Section,Description,Dependencies,Assignee,Status,First released version
 `[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::year_month_day_last``",,Mark de Wever,|Complete|, Clang 16
 `[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::year_month_weekday``",,Mark de Wever,|Complete|, Clang 16
 `[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::year_month_weekday_last``",,Mark de Wever,|Complete|, Clang 16
-`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::hh_mm_ss<duration<Rep, Period>>``",,Mark de Wever,|In Progress|,
+`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::hh_mm_ss<duration<Rep, Period>>``",,Mark de Wever,|Complete|, Clang 17
 `[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::sys_info``",A ``<chrono>`` implementation,Mark de Wever,,
 `[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::local_info``",A ``<chrono>`` implementation,Mark de Wever,,
 `[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::zoned_time<Duration, TimeZonePtr>``",A ``<chrono>`` implementation,Mark de Wever,,

diff  --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 0388df32c763c..25904fe25b54e 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -216,6 +216,7 @@ set(files
   __charconv/to_chars_base_10.h
   __charconv/to_chars_result.h
   __chrono/calendar.h
+  __chrono/concepts.h
   __chrono/convert_to_timespec.h
   __chrono/convert_to_tm.h
   __chrono/day.h

diff  --git a/libcxx/include/__chrono/concepts.h b/libcxx/include/__chrono/concepts.h
new file mode 100644
index 0000000000000..d7502ef9f84dc
--- /dev/null
+++ b/libcxx/include/__chrono/concepts.h
@@ -0,0 +1,32 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___CHRONO_CONCEPTS_H
+#define _LIBCPP___CHRONO_CONCEPTS_H
+
+#include <__chrono/hh_mm_ss.h>
+#include <__config>
+#include <__type_traits/is_specialization.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER > 17
+
+template <class _Tp>
+concept __is_hh_mm_ss = __is_specialization_v<_Tp, chrono::hh_mm_ss>;
+
+#endif // _LIBCPP_STD_VER > 17
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___CHRONO_CONCEPTS_H

diff  --git a/libcxx/include/__chrono/convert_to_tm.h b/libcxx/include/__chrono/convert_to_tm.h
index 36846b3f71404..f5cfde07b5e0b 100644
--- a/libcxx/include/__chrono/convert_to_tm.h
+++ b/libcxx/include/__chrono/convert_to_tm.h
@@ -10,6 +10,7 @@
 #ifndef _LIBCPP___CHRONO_CONVERT_TO_TM_H
 #define _LIBCPP___CHRONO_CONVERT_TO_TM_H
 
+#include <__chrono/concepts.h>
 #include <__chrono/day.h>
 #include <__chrono/duration.h>
 #include <__chrono/hh_mm_ss.h>
@@ -26,14 +27,19 @@
 #include <__chrono/year_month_weekday.h>
 #include <__concepts/same_as.h>
 #include <__config>
+#include <__format/format_error.h>
 #include <__memory/addressof.h>
 #include <cstdint>
 #include <ctime>
+#include <limits>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
 #endif
 
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
 _LIBCPP_BEGIN_NAMESPACE_STD
 
 #if _LIBCPP_STD_VER > 17
@@ -114,6 +120,16 @@ _LIBCPP_HIDE_FROM_ABI _Tm __convert_to_tm(const _ChronoT& __value) {
   } else if constexpr (same_as<_ChronoT, chrono::year_month_weekday> ||
                        same_as<_ChronoT, chrono::year_month_weekday_last>) {
     return std::__convert_to_tm<_Tm>(chrono::year_month_day{static_cast<chrono::sys_days>(__value)}, __value.weekday());
+  } else if constexpr (__is_hh_mm_ss<_ChronoT>) {
+    __result.tm_sec = __value.seconds().count();
+    __result.tm_min = __value.minutes().count();
+    // In libc++ hours is stored as a long. The type in std::tm is an int. So
+    // the overflow can only occur when hour uses more bits than an int
+    // provides.
+    if constexpr (sizeof(std::chrono::hours::rep) > sizeof(__result.tm_hour))
+      if (__value.hours().count() > std::numeric_limits<decltype(__result.tm_hour)>::max())
+        std::__throw_format_error("Formatting hh_mm_ss, encountered an hour overflow");
+    __result.tm_hour = __value.hours().count();
   } else
     static_assert(sizeof(_ChronoT) == 0, "Add the missing type specialization");
 
@@ -124,4 +140,6 @@ _LIBCPP_HIDE_FROM_ABI _Tm __convert_to_tm(const _ChronoT& __value) {
 
 _LIBCPP_END_NAMESPACE_STD
 
+_LIBCPP_POP_MACROS
+
 #endif // _LIBCPP___CHRONO_CONVERT_TO_TM_H

diff  --git a/libcxx/include/__chrono/formatter.h b/libcxx/include/__chrono/formatter.h
index f3748bc730528..cc2d7da27fb2d 100644
--- a/libcxx/include/__chrono/formatter.h
+++ b/libcxx/include/__chrono/formatter.h
@@ -11,6 +11,7 @@
 #define _LIBCPP___CHRONO_FORMATTER_H
 
 #include <__chrono/calendar.h>
+#include <__chrono/concepts.h>
 #include <__chrono/convert_to_tm.h>
 #include <__chrono/day.h>
 #include <__chrono/duration.h>
@@ -75,13 +76,15 @@ namespace __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) {
+template <class _CharT, class _Rep, class _Period>
+_LIBCPP_HIDE_FROM_ABI void
+__format_sub_seconds(const chrono::duration<_Rep, _Period>& __value, basic_stringstream<_CharT>& __sstr) {
   __sstr << std::use_facet<numpunct<_CharT>>(__sstr.getloc()).decimal_point();
 
+  using __duration = chrono::duration<_Rep, _Period>;
+
   auto __fraction = __value - chrono::duration_cast<chrono::seconds>(__value);
-  if constexpr (chrono::treat_as_floating_point_v<typename _Tp::rep>)
+  if constexpr (chrono::treat_as_floating_point_v<_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
@@ -97,18 +100,36 @@ _LIBCPP_HIDE_FROM_ABI void __format_sub_seconds(const _Tp& __value, basic_string
     std::format_to(std::ostreambuf_iterator<_CharT>{__sstr},
                    _LIBCPP_STATICALLY_WIDEN(_CharT, "{:0{}.0f}"),
                    __fraction.count(),
-                   chrono::hh_mm_ss<_Tp>::fractional_width);
+                   chrono::hh_mm_ss<__duration>::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);
+                   chrono::hh_mm_ss<__duration>::fractional_width);
+}
+
+template <class _CharT, class _Duration>
+_LIBCPP_HIDE_FROM_ABI void
+__format_sub_seconds(const chrono::hh_mm_ss<_Duration>& __value, basic_stringstream<_CharT>& __sstr) {
+  __sstr << std::use_facet<numpunct<_CharT>>(__sstr.getloc()).decimal_point();
+  if constexpr (chrono::treat_as_floating_point_v<typename _Duration::rep>)
+    std::format_to(std::ostreambuf_iterator<_CharT>{__sstr},
+                   _LIBCPP_STATICALLY_WIDEN(_CharT, "{:0{}.0f}"),
+                   __value.subseconds().count(),
+                   __value.fractional_width);
+  else
+    std::format_to(std::ostreambuf_iterator<_CharT>{__sstr},
+                   _LIBCPP_STATICALLY_WIDEN(_CharT, "{:0{}}"),
+                   __value.subseconds().count(),
+                   __value.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 if constexpr (__is_hh_mm_ss<_Tp>)
+    return _Tp::fractional_width;
   else
     return false;
 }
@@ -322,6 +343,8 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool __weekday_ok(const _Tp& __value) {
     return __value.weekday().ok();
   else if constexpr (same_as<_Tp, chrono::year_month_weekday_last>)
     return __value.weekday().ok();
+  else if constexpr (__is_hh_mm_ss<_Tp>)
+    return true;
   else
     static_assert(sizeof(_Tp) == 0, "Add the missing type specialization");
 }
@@ -358,6 +381,8 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool __weekday_name_ok(const _Tp& __value) {
     return __value.weekday().ok();
   else if constexpr (same_as<_Tp, chrono::year_month_weekday_last>)
     return __value.weekday().ok();
+  else if constexpr (__is_hh_mm_ss<_Tp>)
+    return true;
   else
     static_assert(sizeof(_Tp) == 0, "Add the missing type specialization");
 }
@@ -394,6 +419,8 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool __date_ok(const _Tp& __value) {
     return __value.ok();
   else if constexpr (same_as<_Tp, chrono::year_month_weekday_last>)
     return __value.ok();
+  else if constexpr (__is_hh_mm_ss<_Tp>)
+    return true;
   else
     static_assert(sizeof(_Tp) == 0, "Add the missing type specialization");
 }
@@ -430,6 +457,8 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool __month_name_ok(const _Tp& __value) {
     return __value.month().ok();
   else if constexpr (same_as<_Tp, chrono::year_month_weekday_last>)
     return __value.month().ok();
+  else if constexpr (__is_hh_mm_ss<_Tp>)
+    return true;
   else
     static_assert(sizeof(_Tp) == 0, "Add the missing type specialization");
 }
@@ -478,6 +507,29 @@ __format_chrono(const _Tp& __value,
       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 (__is_hh_mm_ss<_Tp>) {
+        // Note this is a pedantic intepretation of the Standard. A hh_mm_ss
+        // is no longer a time_of_day and can store an arbitrary number of
+        // hours. A number of hours in a 12 or 24 hour clock can't represent
+        // 24 hours or more. The functions std::chrono::make12 and
+        // std::chrono::make24 reaffirm this view point.
+        //
+        // Interestingly this will be the only output stream function that
+        // throws.
+        //
+        // TODO FMT The wording probably needs to be adapted to
+        // - The displayed hours is hh_mm_ss.hours() % 24
+        // - It should probably allow %j in the same fashion as duration.
+        // - The stream formatter should change its output when hours >= 24
+        //   - Write it as not valid,
+        //   - or write the number of days.
+        if (__specs.__chrono_.__hour_ && __value.hours().count() > 23)
+          std::__throw_format_error("formatting a hour needs a valid value");
+
+        if (__value.is_negative())
+          __sstr << _CharT('-');
+      }
+
       __formatter::__format_chrono_using_chrono_specs(__value, __sstr, __chrono_specs);
     }
   }
@@ -709,6 +761,16 @@ struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter<chrono::year_m
   }
 };
 
+template <class _Duration, __fmt_char_type _CharT>
+struct formatter<chrono::hh_mm_ss<_Duration>, _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()) {
+    return _Base::__parse(__parse_ctx, __format_spec::__fields_chrono, __format_spec::__flags::__time);
+  }
+};
 #endif // if _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT)
 
 _LIBCPP_END_NAMESPACE_STD

diff  --git a/libcxx/include/__chrono/hh_mm_ss.h b/libcxx/include/__chrono/hh_mm_ss.h
index fd61cbe8f8453..a8c8c84acc249 100644
--- a/libcxx/include/__chrono/hh_mm_ss.h
+++ b/libcxx/include/__chrono/hh_mm_ss.h
@@ -85,6 +85,7 @@ class hh_mm_ss
     chrono::seconds __s_;
     precision       __f_;
 };
+_LIBCPP_CTAD_SUPPORTED_FOR_TYPE(hh_mm_ss);
 
 _LIBCPP_HIDE_FROM_ABI constexpr bool is_am(const hours& __h) noexcept { return __h >= hours( 0) && __h < hours(12); }
 _LIBCPP_HIDE_FROM_ABI constexpr bool is_pm(const hours& __h) noexcept { return __h >= hours(12) && __h < hours(24); }

diff  --git a/libcxx/include/__chrono/ostream.h b/libcxx/include/__chrono/ostream.h
index 30a04bd2658b0..4af4476fa63d9 100644
--- a/libcxx/include/__chrono/ostream.h
+++ b/libcxx/include/__chrono/ostream.h
@@ -12,6 +12,7 @@
 
 #include <__chrono/day.h>
 #include <__chrono/duration.h>
+#include <__chrono/hh_mm_ss.h>
 #include <__chrono/month.h>
 #include <__chrono/month_weekday.h>
 #include <__chrono/monthday.h>
@@ -229,6 +230,12 @@ operator<<(basic_ostream<_CharT, _Traits>& __os, const year_month_weekday_last&
              __ymwdl.weekday_last());
 }
 
+template <class _CharT, class _Traits, class _Duration>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_AVAILABILITY_FORMAT basic_ostream<_CharT, _Traits>&
+operator<<(basic_ostream<_CharT, _Traits>& __os, const hh_mm_ss<_Duration> __hms) {
+  return __os << std::format(__os.getloc(), _LIBCPP_STATICALLY_WIDEN(_CharT, "{:L%T}"), __hms);
+}
+
 } // namespace chrono
 
 #endif //if _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT)

diff  --git a/libcxx/include/__chrono/parser_std_format_spec.h b/libcxx/include/__chrono/parser_std_format_spec.h
index f4f2b079b6bb1..d30a44c64e551 100644
--- a/libcxx/include/__chrono/parser_std_format_spec.h
+++ b/libcxx/include/__chrono/parser_std_format_spec.h
@@ -214,6 +214,7 @@ class _LIBCPP_TEMPLATE_VIS __parser_chrono {
     case _CharT('p'): // TODO FMT does the formater require an hour or a time?
     case _CharT('H'):
     case _CharT('I'):
+      __parser_.__hour_ = true;
       __validate_hour(__flags);
       break;
 
@@ -221,6 +222,7 @@ class _LIBCPP_TEMPLATE_VIS __parser_chrono {
     case _CharT('R'):
     case _CharT('T'):
     case _CharT('X'):
+      __parser_.__hour_ = true;
       __format_spec::__validate_time(__flags);
       break;
 
@@ -313,6 +315,7 @@ class _LIBCPP_TEMPLATE_VIS __parser_chrono {
 
     switch (*__begin) {
     case _CharT('X'):
+      __parser_.__hour_ = true;
       __format_spec::__validate_time(__flags);
       break;
 
@@ -361,6 +364,7 @@ class _LIBCPP_TEMPLATE_VIS __parser_chrono {
 
     case _CharT('I'):
     case _CharT('H'):
+      __parser_.__hour_ = true;
       __format_spec::__validate_hour(__flags);
       break;
 

diff  --git a/libcxx/include/__format/parser_std_format_spec.h b/libcxx/include/__format/parser_std_format_spec.h
index f70a8258ce655..adbc5fc7c7b1c 100644
--- a/libcxx/include/__format/parser_std_format_spec.h
+++ b/libcxx/include/__format/parser_std_format_spec.h
@@ -199,6 +199,7 @@ struct __std {
 struct __chrono {
   __alignment __alignment_ : 3;
   bool __locale_specific_form_ : 1;
+  bool __hour_                 : 1;
   bool __weekday_name_ : 1;
   bool __weekday_              : 1;
   bool __day_of_year_          : 1;
@@ -329,6 +330,7 @@ class _LIBCPP_TEMPLATE_VIS __parser {
         .__chrono_ =
             __chrono{.__alignment_            = __alignment_,
                      .__locale_specific_form_ = __locale_specific_form_,
+                     .__hour_                 = __hour_,
                      .__weekday_name_         = __weekday_name_,
                      .__weekday_              = __weekday_,
                      .__day_of_year_          = __day_of_year_,
@@ -348,6 +350,8 @@ class _LIBCPP_TEMPLATE_VIS __parser {
 
   // These flags are only used for formatting chrono. Since the struct has
   // padding space left it's added to this structure.
+  bool __hour_ : 1 {false};
+
   bool __weekday_name_ : 1 {false};
   bool __weekday_      : 1 {false};
 
@@ -356,7 +360,7 @@ class _LIBCPP_TEMPLATE_VIS __parser {
 
   bool __month_name_ : 1 {false};
 
-  uint8_t __reserved_1_ : 3 {0};
+  uint8_t __reserved_1_ : 2 {0};
   uint8_t __reserved_2_ : 6 {0};
   // These two flags are only used internally and not part of the
   // __parsed_specifications. Therefore put them at the end.

diff  --git a/libcxx/include/chrono b/libcxx/include/chrono
index 44073b557ba12..0d1c5bb875044 100644
--- a/libcxx/include/chrono
+++ b/libcxx/include/chrono
@@ -656,6 +656,10 @@ public:
     constexpr          precision to_duration() const noexcept;
 };
 
+template<class charT, class traits, class Duration>
+  basic_ostream<charT, traits>&
+    operator<<(basic_ostream<charT, traits>& os, const hh_mm_ss<Duration>& hms); // C++20
+
 // 26.10, 12/24 hour functions
 constexpr bool is_am(hours const& h) noexcept;
 constexpr bool is_pm(hours const& h) noexcept;
@@ -691,6 +695,8 @@ namespace std {
   template<class charT> struct formatter<chrono::year_month_day_last, charT>;     // C++20
   template<class charT> struct formatter<chrono::year_month_weekday, charT>;      // C++20
   template<class charT> struct formatter<chrono::year_month_weekday_last, charT>; // C++20
+  template<class Rep, class Period, class charT>
+    struct formatter<chrono::hh_mm_ss<duration<Rep, Period>>, charT>;             // C++20
 } // namespace std
 
 namespace chrono {

diff  --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 53bfd1a11a2d4..b601ef0671669 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -674,6 +674,7 @@ module std [system] {
 
     module __chrono {
       module calendar               { private header "__chrono/calendar.h" }
+      module concepts               { private header "__chrono/concepts.h" }
       module convert_to_timespec    { private header "__chrono/convert_to_timespec.h" }
       module convert_to_tm          { private header "__chrono/convert_to_tm.h" }
       module day                    { private header "__chrono/day.h" }

diff  --git a/libcxx/test/libcxx/private_headers.verify.cpp b/libcxx/test/libcxx/private_headers.verify.cpp
index d94bb185ed6a9..49c0d1a81a765 100644
--- a/libcxx/test/libcxx/private_headers.verify.cpp
+++ b/libcxx/test/libcxx/private_headers.verify.cpp
@@ -250,6 +250,7 @@ END-SCRIPT
 #include <__charconv/to_chars_base_10.h> // expected-error@*:* {{use of private header from outside its module: '__charconv/to_chars_base_10.h'}}
 #include <__charconv/to_chars_result.h> // expected-error@*:* {{use of private header from outside its module: '__charconv/to_chars_result.h'}}
 #include <__chrono/calendar.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/calendar.h'}}
+#include <__chrono/concepts.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/concepts.h'}}
 #include <__chrono/convert_to_timespec.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/convert_to_timespec.h'}}
 #include <__chrono/convert_to_tm.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/convert_to_tm.h'}}
 #include <__chrono/day.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/day.h'}}

diff  --git a/libcxx/test/libcxx/time/convert_to_tm.pass.cpp b/libcxx/test/libcxx/time/convert_to_tm.pass.cpp
new file mode 100644
index 0000000000000..9e52b5b869f66
--- /dev/null
+++ b/libcxx/test/libcxx/time/convert_to_tm.pass.cpp
@@ -0,0 +1,62 @@
+//===----------------------------------------------------------------------===//
+// 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: libcpp-has-no-incomplete-format
+
+// <chrono>
+
+// template <class _Tm, class _ChronoT>
+// _LIBCPP_HIDE_FROM_ABI _Tm __convert_to_tm(const _ChronoT& __value)
+
+// Most of the code is tested indirectly in the chrono formatters. This only
+// tests the hour overflow.
+
+#include <chrono>
+#include <cassert>
+#include <format>
+#include <string_view>
+
+#include "test_macros.h"
+
+// libc++ uses a long as representation in std::chrono::hours.
+// std::tm uses an int for its integral members. The overflow in the hour
+// conversion can only occur on platforms where sizeof(long) > sizeof(int).
+// Instead emulate this error by using a "tm" with shorts.
+// (The function is already templated to this is quite easy to do,)
+struct minimal_short_tm {
+  short tm_sec;
+  short tm_min;
+  short tm_hour;
+  const char* tm_zone;
+};
+
+int main(int, char**) {
+  { // Test with the maximum number of hours that fit in a short.
+    std::chrono::hh_mm_ss time{std::chrono::hours{32767}};
+    minimal_short_tm result = std::__convert_to_tm<minimal_short_tm>(time);
+    assert(result.tm_sec == 0);
+    assert(result.tm_min == 0);
+    assert(result.tm_hour == 32767);
+  }
+
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  { // Test above the maximum number of hours that fit in a short.
+    std::chrono::hh_mm_ss time{std::chrono::hours{32768}};
+    try {
+      TEST_IGNORE_NODISCARD std::__convert_to_tm<minimal_short_tm>(time);
+      assert(false);
+    } catch ([[maybe_unused]] const std::format_error& e) {
+      LIBCPP_ASSERT(e.what() == std::string_view("Formatting hh_mm_ss, encountered an hour overflow"));
+      return 0;
+    }
+    assert(false);
+  }
+#endif // TEST_HAS_NO_EXCEPTIONS
+
+  return 0;
+}

diff  --git a/libcxx/test/std/time/time.hms/time.hms.nonmembers/ostream.pass.cpp b/libcxx/test/std/time/time.hms/time.hms.nonmembers/ostream.pass.cpp
new file mode 100644
index 0000000000000..76d4b27cd7b0b
--- /dev/null
+++ b/libcxx/test/std/time/time.hms/time.hms.nonmembers/ostream.pass.cpp
@@ -0,0 +1,160 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 Evaluate gcc-12 status
+// UNSUPPORTED: gcc-12
+
+// TODO FMT Investigate Windows issues.
+// UNSUPPORTED: msvc, target={{.+}}-windows-gnu
+
+// REQUIRES: locale.fr_FR.UTF-8
+// REQUIRES: locale.ja_JP.UTF-8
+
+// <chrono>
+
+// class hh_mm_ss;
+
+// template<class charT, class traits, class Duration>
+//   basic_ostream<charT, traits>&
+//     operator<<(basic_ostream<charT, traits>& os, const hh_mm_ss<Duration>& hms);
+
+#include <chrono>
+#include <cassert>
+#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 Duration>
+static std::basic_string<CharT> stream_c_locale(std::chrono::hh_mm_ss<Duration> hms) {
+  std::basic_stringstream<CharT> sstr;
+  sstr << hms;
+  return sstr.str();
+}
+
+template <class CharT, class Duration>
+static std::basic_string<CharT> stream_fr_FR_locale(std::chrono::hh_mm_ss<Duration> hms) {
+  std::basic_stringstream<CharT> sstr;
+  const std::locale locale(LOCALE_fr_FR_UTF_8);
+  sstr.imbue(locale);
+  sstr << hms;
+  return sstr.str();
+}
+
+template <class CharT, class Duration>
+static std::basic_string<CharT> stream_ja_JP_locale(std::chrono::hh_mm_ss<Duration> hms) {
+  std::basic_stringstream<CharT> sstr;
+  const std::locale locale(LOCALE_ja_JP_UTF_8);
+  sstr.imbue(locale);
+  sstr << hms;
+  return sstr.str();
+}
+
+template <class CharT>
+static void test() {
+  // Note std::atto can't be tested since the ratio conversion from std::atto
+  // std::chrono::seconds to std::chrono::hours overflows when intmax_t is a
+  // 64-bit type. This is a limitiation in the constructor of
+  // std::chrono::hh_mm_ss.
+
+  // C locale - integral power of 10 ratios
+  assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::femto>{1'234'567'890}}) ==
+         SV("00:00:00.000001234567890"));
+  assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::pico>{1'234'567'890}}) ==
+         SV("00:00:00.001234567890"));
+  assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::nano>{1'234'567'890}}) ==
+         SV("00:00:01.234567890"));
+  assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::micro>{1'234'567}}) ==
+         SV("00:00:01.234567"));
+  assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::milli>{123'456}}) ==
+         SV("00:02:03.456"));
+  assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::centi>{12'345}}) ==
+         SV("00:02:03.45"));
+  assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::deci>{1'234}}) ==
+         SV("00:02:03.4"));
+
+  assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t>{123}}) == SV("00:02:03"));
+
+  assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::deca>{-366}}) ==
+         SV("-01:01:00"));
+  assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::hecto>{-72}}) ==
+         SV("-02:00:00"));
+  assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::kilo>{-86}}) ==
+         SV("-23:53:20"));
+
+  // Starting at mega it will pass one day
+
+  // fr_FR locale - integral power of not 10 ratios
+  assert(stream_fr_FR_locale<CharT>(std::chrono::hh_mm_ss{
+             std::chrono::duration<intmax_t, std::ratio<1, 5'000'000>>{5'000}}) == SV("00:00:00,0010000"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::ratio<1, 8'000>>{3}}) ==
+         SV("00:00:00,000375"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::ratio<1, 4'000>>{1}}) ==
+         SV("00:00:00,00025"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::ratio<1, 5'000>>{5}}) ==
+         SV("00:00:00,0010"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::ratio<1, 8>>{-4}}) ==
+         SV("-00:00:00,500"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::ratio<1, 4>>{-8}}) ==
+         SV("-00:00:02,00"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::ratio<1, 5>>{-5}}) ==
+         SV("-00:00:01,0"));
+
+  // TODO FMT Note there's no wording on the rounding
+  assert(stream_fr_FR_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::ratio<1, 9>>{5}}) ==
+         SV("00:00:00,555555"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::ratio<1, 7>>{7}}) ==
+         SV("00:00:01,000000"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::ratio<1, 6>>{1}}) ==
+         SV("00:00:00,166666"));
+  assert(stream_fr_FR_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::ratio<1, 3>>{2}}) ==
+         SV("00:00:00,666666"));
+
+  // ja_JP locale - floating points
+
+  assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{
+             std::chrono::duration<long double, std::femto>{1'234'567'890.123}}) == SV("00:00:00.000001234567890"));
+  assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{
+             std::chrono::duration<long double, std::pico>{1'234'567'890.123}}) == SV("00:00:00.001234567890"));
+  assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{
+             std::chrono::duration<long double, std::nano>{1'234'567'890.123}}) == SV("00:00:01.234567890"));
+  assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<double, std::micro>{1'234'567.123}}) ==
+         SV("00:00:01.234567"));
+  assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<double, std::milli>{123'456.123}}) ==
+         SV("00:02:03.456"));
+  assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<double, std::centi>{12'345.123}}) ==
+         SV("00:02:03.45"));
+  assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<float, std::deci>{1'234.123}}) ==
+         SV("00:02:03.4"));
+
+  assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<float>{123.123}}) == SV("00:02:03"));
+
+  assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<double, std::deca>{-366.5}}) ==
+         SV("-01:01:05"));
+  assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<double, std::hecto>{-72.64}}) ==
+         SV("-02:01:04"));
+  assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<double, std::kilo>{-86}}) ==
+         SV("-23:53:20"));
+}
+
+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.hh_mm_ss.pass.cpp b/libcxx/test/std/time/time.syn/formatter.hh_mm_ss.pass.cpp
new file mode 100644
index 0000000000000..49ab0d025c15a
--- /dev/null
+++ b/libcxx/test/std/time/time.syn/formatter.hh_mm_ss.pass.cpp
@@ -0,0 +1,556 @@
+//===----------------------------------------------------------------------===//
+// 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 Evaluate gcc-12 status
+// UNSUPPORTED: gcc-12
+
+// 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::hh_mm_ss<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 "string_literal.h"
+#include "test_macros.h"
+
+template <class CharT>
+static void test_no_chrono_specs() {
+  using namespace std::literals::chrono_literals;
+
+  std::locale::global(std::locale(LOCALE_fr_FR_UTF_8));
+
+  // Non localized output
+  check(SV("00:00:00.000"), SV("{}"), std::chrono::hh_mm_ss{0ms});
+  check(SV("*00:00:00.000*"), SV("{:*^14}"), std::chrono::hh_mm_ss{0ms});
+  check(SV("*00:00:00.000"), SV("{:*>13}"), std::chrono::hh_mm_ss{0ms});
+
+  std::locale::global(std::locale::classic());
+}
+
+template <class CharT>
+static void test_valid_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"
+      "%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"
+      "%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"
+           "\n"),
+        fmt,
+        std::chrono::hh_mm_ss(0s));
+
+  check(SV("%H='23'\t"
+           "%OH='23'\t"
+           "%I='11'\t"
+           "%OI='11'\t"
+           "%M='31'\t"
+           "%OM='31'\t"
+           "%S='30.123'\t"
+           "%OS='30.123'\t"
+           "%p='PM'\t"
+           "%R='23:31'\t"
+           "%T='23:31:30.123'\t"
+           "%r='11:31:30 PM'\t"
+           "%X='23:31:30'\t"
+           "%EX='23:31:30'\t"
+           "\n"),
+        fmt,
+        std::chrono::hh_mm_ss(23h + 31min + 30s + 123ms));
+
+  check(SV("-%H='03'\t"
+           "%OH='03'\t"
+           "%I='03'\t"
+           "%OI='03'\t"
+           "%M='02'\t"
+           "%OM='02'\t"
+           "%S='01.123456789012'\t"
+           "%OS='01.123456789012'\t"
+           "%p='AM'\t"
+           "%R='03:02'\t"
+           "%T='03:02:01.123456789012'\t"
+           "%r='03:02:01 AM'\t"
+           "%X='03:02:01'\t"
+           "%EX='03:02:01'\t"
+           "\n"),
+        fmt,
+        std::chrono::hh_mm_ss(-(3h + 2min + 1s + std::chrono::duration<int64_t, std::pico>(123456789012))));
+
+  // The number of fractional seconds is 0 according to the Standard
+  // TODO FMT Determine what to do.
+  check(SV("%H='01'\t"
+           "%OH='01'\t"
+           "%I='01'\t"
+           "%OI='01'\t"
+           "%M='01'\t"
+           "%OM='01'\t"
+           "%S='01'\t"
+           "%OS='01'\t"
+           "%p='AM'\t"
+           "%R='01:01'\t"
+           "%T='01:01:01'\t"
+           "%r='01:01:01 AM'\t"
+           "%X='01:01:01'\t"
+           "%EX='01:01:01'\t"
+           "\n"),
+        fmt,
+        std::chrono::hh_mm_ss(std::chrono::duration<double>(3661.123456)));
+
+  // 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='00: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"
+           "\n"),
+        lfmt,
+        std::chrono::hh_mm_ss(0s));
+
+  check(SV("%H='23'\t"
+           "%OH='23'\t"
+           "%I='11'\t"
+           "%OI='11'\t"
+           "%M='31'\t"
+           "%OM='31'\t"
+           "%S='30,123'\t"
+           "%OS='30,123'\t"
+#if defined(_AIX)
+           "%p='PM'\t"
+#else
+           "%p=''\t"
+#endif
+           "%R='23:31'\t"
+           "%T='23:31:30,123'\t"
+#ifdef _WIN32
+           "%r='23:31:30'\t"
+#elif defined(_AIX)
+           "%r='11:31:30 PM'\t"
+#elif defined(__APPLE__)
+           "%r=''\t"
+#else
+           "%r='11:31:30 '\t"
+#endif
+           "%X='23:31:30'\t"
+           "%EX='23:31:30'\t"
+           "\n"),
+        lfmt,
+        std::chrono::hh_mm_ss(23h + 31min + 30s + 123ms));
+
+  check(SV("-%H='03'\t"
+           "%OH='03'\t"
+           "%I='03'\t"
+           "%OI='03'\t"
+           "%M='02'\t"
+           "%OM='02'\t"
+           "%S='01,123456789012'\t"
+           "%OS='01,123456789012'\t"
+#if defined(_AIX)
+           "%p='AM'\t"
+#else
+           "%p=''\t"
+#endif
+           "%R='03:02'\t"
+           "%T='03:02:01,123456789012'\t"
+#ifdef _WIN32
+           "%r='03:02:01'\t"
+#elif defined(_AIX)
+           "%r='03:02:01 AM'\t"
+#elif defined(__APPLE__)
+           "%r=''\t"
+#else
+           "%r='03:02:01 '\t"
+#endif
+           "%X='03:02:01'\t"
+           "%EX='03:02:01'\t"
+           "\n"),
+        lfmt,
+        std::chrono::hh_mm_ss(-(3h + 2min + 1s + std::chrono::duration<int64_t, std::pico>(123456789012))));
+
+  check(SV("%H='01'\t"
+           "%OH='01'\t"
+           "%I='01'\t"
+           "%OI='01'\t"
+           "%M='01'\t"
+           "%OM='01'\t"
+           "%S='01'\t"
+           "%OS='01'\t"
+#if defined(_AIX)
+           "%p='AM'\t"
+#else
+           "%p=''\t"
+#endif
+           "%R='01:01'\t"
+           "%T='01:01:01'\t"
+#ifdef _WIN32
+           "%r='01:01:01'\t"
+#elif defined(_AIX)
+           "%r='01:01:01 AM'\t"
+#elif defined(__APPLE__)
+           "%r=''\t"
+#else
+           "%r='01:01:01 '\t"
+#endif
+           "%X='01:01:01'\t"
+           "%EX='01:01:01'\t"
+           "\n"),
+        lfmt,
+        std::chrono::hh_mm_ss(std::chrono::duration<double>(3661.123456)));
+
+  // 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
+           "\n"),
+        lfmt,
+        std::chrono::hh_mm_ss(0s));
+
+  check(loc,
+        SV("%H='23'\t"
+           "%OH='23'\t"
+           "%I='11'\t"
+           "%OI='11'\t"
+           "%M='31'\t"
+           "%OM='31'\t"
+           "%S='30.123'\t"
+           "%OS='30.123'\t"
+#  if defined(__APPLE__)
+           "%p='PM'\t"
+#  else
+           "%p='午後'\t"
+#  endif
+           "%R='23:31'\t"
+           "%T='23:31:30.123'\t"
+#  if defined(__APPLE__)
+           "%r='11:31:30 PM'\t"
+           "%X='23時31分30秒'\t"
+           "%EX='23時31分30秒'\t"
+#  else
+           "%r='午後11:31:30'\t"
+           "%X='23:31:30'\t"
+           "%EX='23:31:30'\t"
+#  endif
+           "\n"),
+        lfmt,
+        std::chrono::hh_mm_ss(23h + 31min + 30s + 123ms));
+
+  check(loc,
+        SV("-%H='03'\t"
+           "%OH='03'\t"
+           "%I='03'\t"
+           "%OI='03'\t"
+           "%M='02'\t"
+           "%OM='02'\t"
+           "%S='01.123456789012'\t"
+           "%OS='01.123456789012'\t"
+#  if defined(__APPLE__)
+           "%p='AM'\t"
+#  else
+           "%p='午前'\t"
+#  endif
+           "%R='03:02'\t"
+           "%T='03:02:01.123456789012'\t"
+#  if defined(__APPLE__)
+           "%r='03:02:01 AM'\t"
+           "%X='03時02分01秒'\t"
+           "%EX='03時02分01秒'\t"
+#  else
+           "%r='午前03:02:01'\t"
+           "%X='03:02:01'\t"
+           "%EX='03:02:01'\t"
+#  endif
+           "\n"),
+        lfmt,
+        std::chrono::hh_mm_ss(-(3h + 2min + 1s + std::chrono::duration<int64_t, std::pico>(123456789012))));
+
+  check(loc,
+        SV("%H='01'\t"
+           "%OH='01'\t"
+           "%I='01'\t"
+           "%OI='01'\t"
+           "%M='01'\t"
+           "%OM='01'\t"
+           "%S='01'\t"
+           "%OS='01'\t"
+#  if defined(__APPLE__)
+           "%p='AM'\t"
+#  else
+           "%p='午前'\t"
+#  endif
+           "%R='01:01'\t"
+           "%T='01:01:01'\t"
+#  if defined(__APPLE__)
+           "%r='01:01:01 AM'\t"
+           "%X='01時01分01秒'\t"
+           "%EX='01時01分01秒'\t"
+#  else
+           "%r='午前01:01:01'\t"
+           "%X='01:01:01'\t"
+           "%EX='01:01:01'\t"
+#  endif
+           "\n"),
+        lfmt,
+        std::chrono::hh_mm_ss(std::chrono::duration<double>(3661.123456)));
+#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"
+           "\n"),
+        lfmt,
+        std::chrono::hh_mm_ss(0s));
+
+  // TODO FMT What should fractions be in alternate display mode?
+  check(loc,
+        SV("%H='23'\t"
+           "%OH='二十三'\t"
+           "%I='11'\t"
+           "%OI='十一'\t"
+           "%M='31'\t"
+           "%OM='三十一'\t"
+           "%S='30.123'\t"
+           "%OS='三十.123'\t"
+           "%p='午後'\t"
+           "%R='23:31'\t"
+           "%T='23:31:30.123'\t"
+           "%r='午後11時31分30秒'\t"
+           "%X='23時31分30秒'\t"
+           "%EX='23時31分30秒'\t"
+           "\n"),
+        lfmt,
+        std::chrono::hh_mm_ss(23h + 31min + 30s + 123ms));
+
+  check(loc,
+        SV("-%H='03'\t"
+           "%OH='三'\t"
+           "%I='03'\t"
+           "%OI='三'\t"
+           "%M='02'\t"
+           "%OM='二'\t"
+           "%S='01.123456789012'\t"
+           "%OS='一.123456789012'\t"
+           "%p='午前'\t"
+           "%R='03:02'\t"
+           "%T='03:02:01.123456789012'\t"
+           "%r='午前03時02分01秒'\t"
+           "%X='03時02分01秒'\t"
+           "%EX='03時02分01秒'\t"
+           "\n"),
+        lfmt,
+        std::chrono::hh_mm_ss(-(3h + 2min + 1s + std::chrono::duration<int64_t, std::pico>(123456789012))));
+
+  check(loc,
+        SV("%H='01'\t"
+           "%OH='一'\t"
+           "%I='01'\t"
+           "%OI='一'\t"
+           "%M='01'\t"
+           "%OM='一'\t"
+           "%S='01'\t"
+           "%OS='一'\t"
+           "%p='午前'\t"
+           "%R='01:01'\t"
+           "%T='01:01:01'\t"
+           "%r='午前01時01分01秒'\t"
+           "%X='01時01分01秒'\t"
+           "%EX='01時01分01秒'\t"
+           "\n"),
+        lfmt,
+        std::chrono::hh_mm_ss(std::chrono::duration<double>(3661.123456)));
+#endif // defined(__APPLE__) || defined(_AIX)
+
+  std::locale::global(std::locale::classic());
+}
+
+template <class CharT>
+static void test_invalid_values() {
+  using namespace std::literals::chrono_literals;
+
+  // This looks odd, however the 24 hours is not valid for a 24 hour clock.
+  // TODO FMT discuss what the "proper" behaviour is.
+  check_exception("formatting a hour needs a valid value", SV("{:%H"), std::chrono::hh_mm_ss{24h});
+  check_exception("formatting a hour needs a valid value", SV("{:%OH"), std::chrono::hh_mm_ss{24h});
+  check_exception("formatting a hour needs a valid value", SV("{:%I"), std::chrono::hh_mm_ss{24h});
+  check_exception("formatting a hour needs a valid value", SV("{:%OI"), std::chrono::hh_mm_ss{24h});
+  check(SV("00"), SV("{:%M}"), std::chrono::hh_mm_ss{24h});
+  check(SV("00"), SV("{:%OM}"), std::chrono::hh_mm_ss{24h});
+  check(SV("00"), SV("{:%S}"), std::chrono::hh_mm_ss{24h});
+  check(SV("00"), SV("{:%OS}"), std::chrono::hh_mm_ss{24h});
+  check_exception("formatting a hour needs a valid value", SV("{:%p"), std::chrono::hh_mm_ss{24h});
+  check_exception("formatting a hour needs a valid value", SV("{:%R"), std::chrono::hh_mm_ss{24h});
+  check_exception("formatting a hour needs a valid value", SV("{:%T"), std::chrono::hh_mm_ss{24h});
+  check_exception("formatting a hour needs a valid value", SV("{:%r"), std::chrono::hh_mm_ss{24h});
+  check_exception("formatting a hour needs a valid value", SV("{:%X"), std::chrono::hh_mm_ss{24h});
+  check_exception("formatting a hour needs a valid value", SV("{:%EX"), std::chrono::hh_mm_ss{24h});
+}
+
+template <class CharT>
+static void test() {
+  using namespace std::literals::chrono_literals;
+
+  test_no_chrono_specs<CharT>();
+  test_valid_values<CharT>();
+  test_invalid_values<CharT>();
+  check_invalid_types<CharT>(
+      {SV("H"),
+       SV("I"),
+       SV("M"),
+       SV("S"),
+       SV("p"),
+       SV("r"),
+       SV("R"),
+       SV("T"),
+       SV("X"),
+       SV("OH"),
+       SV("OI"),
+       SV("OM"),
+       SV("OS"),
+       SV("EX")},
+      std::chrono::hh_mm_ss{0ms});
+
+  check_exception("Expected '%' or '}' in the chrono format-string", SV("{:A"), std::chrono::hh_mm_ss{0ms});
+  check_exception("The chrono-specs contains a '{'", SV("{:%%{"), std::chrono::hh_mm_ss{0ms});
+  check_exception(
+      "End of input while parsing the modifier chrono conversion-spec", SV("{:%"), std::chrono::hh_mm_ss{0ms});
+  check_exception("End of input while parsing the modifier E", SV("{:%E"), std::chrono::hh_mm_ss{0ms});
+  check_exception("End of input while parsing the modifier O", SV("{:%O"), std::chrono::hh_mm_ss{0ms});
+
+  check_exception("Expected '%' or '}' in the chrono format-string", SV("{:.3}"), std::chrono::hh_mm_ss{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 b0abc2c5bc172..1dd415abc38f3 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
@@ -161,7 +161,7 @@ void test_P1361() {
   assert_is_formattable<std::chrono::year_month_weekday, CharT>();
   assert_is_formattable<std::chrono::year_month_weekday_last, CharT>();
 
-  assert_is_not_formattable<std::chrono::hh_mm_ss<std::chrono::microseconds>, CharT>();
+  assert_is_formattable<std::chrono::hh_mm_ss<std::chrono::microseconds>, CharT>();
 
   //assert_is_formattable<std::chrono::sys_info, CharT>();
   //assert_is_formattable<std::chrono::local_info, CharT>();


        


More information about the libcxx-commits mailing list