[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