diff --git a/libcxx/docs/Status/Cxx20Issues.csv b/libcxx/docs/Status/Cxx20Issues.csv --- a/libcxx/docs/Status/Cxx20Issues.csv +++ b/libcxx/docs/Status/Cxx20Issues.csv @@ -213,10 +213,10 @@ "`3254 `__","Strike ``stop_token``\ 's ``operator!=``\ ","Prague","","" "`3255 `__","``span``\ 's ``array``\ constructor is too strict","Prague","|Complete|","" "`3260 `__","``year_month*``\ arithmetic rejects durations convertible to years","Prague","","","|chrono|" -"`3262 `__","Formatting of negative durations is not specified","Prague","","","|chrono| |format|" +"`3262 `__","Formatting of negative durations is not specified","Prague","|Complete|","16.0","|chrono| |format|" "`3264 `__","``sized_range``\ and ``ranges::size``\ redundantly use ``disable_sized_range``\ ","Prague","|Complete|","15.0","|ranges|" "`3269 `__","Parse manipulators do not specify the result of the extraction from stream","Prague","","","|chrono|" -"`3270 `__","Parsing and formatting ``%j``\ with ``duration``\ s","Prague","","","|chrono| |format|" +"`3270 `__","Parsing and formatting ``%j``\ with ``duration``\ s","Prague","|Partial|","","|chrono| |format|" "`3280 `__","View converting constructors can cause constraint recursion and are unneeded","Prague","|Complete|","15.0","|ranges|" "`3281 `__","Conversion from ``*pair-like*``\ types to ``subrange``\ is a silent semantic promotion","Prague","|Complete|","15.0","|ranges|" "`3282 `__","``subrange``\ converting constructor should disallow derived to base conversions","Prague","|Complete|","15.0","|ranges|" @@ -236,7 +236,7 @@ "`3307 `__","``std::allocator().allocate(n)``\ ","Prague","","" "`3310 `__","Replace ``SIZE_MAX``\ with ``numeric_limits::max()``\ ","Prague","","" "`3313 `__","``join_view::iterator::operator--``\ is incorrectly constrained","Prague","|Complete|","14.0","|ranges|" -"`3314 `__","Is stream insertion behavior locale dependent when ``Period::type``\ is ``micro``\ ?","Prague","","","|chrono|" +"`3314 `__","Is stream insertion behavior locale dependent when ``Period::type``\ is ``micro``\ ?","Prague","|Complete|","16.0","|chrono|" "`3315 `__","Correct Allocator Default Behavior","Prague","","" "`3316 `__","Correctly define epoch for ``utc_clock``\ / ``utc_timepoint``\ ","Prague","","","|chrono|" "`3317 `__","Incorrect ``operator<<``\ for floating-point durations","Prague","","","|chrono|" diff --git a/libcxx/docs/Status/Cxx20Papers.csv b/libcxx/docs/Status/Cxx20Papers.csv --- a/libcxx/docs/Status/Cxx20Papers.csv +++ b/libcxx/docs/Status/Cxx20Papers.csv @@ -127,7 +127,7 @@ "`P1638R1 `__","LWG","basic_istream_view::iterator should not be copyable","Cologne","|Complete|","16.0" "`P1643R1 `__","LWG","Add wait/notify to atomic_ref","Cologne","","" "`P1644R0 `__","LWG","Add wait/notify to atomic","Cologne","","" -"`P1650R0 `__","LWG","Output std::chrono::days with 'd' suffix","Cologne","","" +"`P1650R0 `__","LWG","Output std::chrono::days with 'd' suffix","Cologne","","|Complete|","16.0" "`P1651R0 `__","LWG","bind_front should not unwrap reference_wrapper","Cologne","|Complete|","13.0" "`P1652R1 `__","LWG","Printf corner cases in std::format","Cologne","|Complete|","14.0" "`P1661R1 `__","LWG","Remove dedicated precalculated hash lookup interface","Cologne","|Nothing To Do|","" @@ -201,7 +201,7 @@ "`P2328R1 `__","LWG",join_view should join all views of ranges,"June 2021","","" "`P2367R0 `__","LWG",Remove misuses of list-initialization from Clause 24,"June 2021","","" "","","","","","" -"`P2372R3 `__","LWG","Fixing locale handling in chrono formatters","October 2021","","" +"`P2372R3 `__","LWG","Fixing locale handling in chrono formatters","October 2021","|In Progress|","" "`P2415R2 `__","LWG","What is a ``view``","October 2021","|Complete|","14.0" "`P2418R2 `__","LWG","Add support for ``std::generator``-like types to ``std::format``","October 2021","|Complete|","15.0" "`P2432R1 `__","LWG","Fix ``istream_view``","October 2021","|Complete|","16.0" diff --git a/libcxx/docs/Status/FormatPaper.csv b/libcxx/docs/Status/FormatPaper.csv --- 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 `__ `P2372 `__,"Formatting chrono" -`[time.syn] `_,"Formatter ``chrono::duration``",,Mark de Wever,|In Progress|, +`[time.syn] `_,"Formatter ``chrono::duration``",,Mark de Wever,|Complete|, Clang 16 `[time.syn] `_,"Formatter ``chrono::sys_time``",,Mark de Wever,|In Progress|, `[time.syn] `_,"Formatter ``chrono::utc_time``",A ```` implementation,Not assigned,,, `[time.syn] `_,"Formatter ``chrono::tai_time``",A ```` implementation,Not assigned,,, diff --git a/libcxx/include/__chrono/convert_to_tm.h b/libcxx/include/__chrono/convert_to_tm.h --- 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 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) # pragma GCC system_header @@ -24,23 +26,34 @@ #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 -_LIBCPP_HIDE_FROM_ABI _Tm __convert_to_tm(const _ChronoCalendarTimePoint& __value) { +template +_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(__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(__value); - else if constexpr (same_as<_ChronoCalendarTimePoint, chrono::month>) + else if constexpr (same_as<_ChronoT, chrono::month>) __result.tm_mon = static_cast(__value) - 1; - else if constexpr (same_as<_ChronoCalendarTimePoint, chrono::year>) + else if constexpr (same_as<_ChronoT, chrono::year>) __result.tm_year = static_cast(__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 --- 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 @@ /// /// 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 + 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>(__sstr.getloc()).decimal_point(); + + auto __fraction = __value - chrono::duration_cast(__value); + if constexpr (chrono::treat_as_floating_point_v) + // 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 + // differently 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 +consteval bool __use_fraction() { + if constexpr (chrono::__is_duration<_Tp>::value) + return chrono::hh_mm_ss<_Tp>::fractional_width; + else + return false; +} + template _LIBCPP_HIDE_FROM_ABI void __format_year(int __year, basic_stringstream<_CharT>& __sstr) { if (__year < 0) { @@ -119,32 +165,73 @@ __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::duration_cast(__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 @@ } 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 @@ 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 @@ __format_spec::__parser_chrono<_CharT> __parser_; }; +template +struct formatter, _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 : public __formatter_chrono<_CharT> { diff --git a/libcxx/include/__chrono/ostream.h b/libcxx/include/__chrono/ostream.h --- 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 +#include #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) # pragma GCC system_header @@ -28,6 +31,71 @@ namespace chrono { +// Depending on the type the return is a const _CharT* or a basic_string<_CharT> +template +_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) + return _LIBCPP_STATICALLY_WIDEN(_CharT, "as"); + else if constexpr (same_as) + return _LIBCPP_STATICALLY_WIDEN(_CharT, "fs"); + else if constexpr (same_as) + return _LIBCPP_STATICALLY_WIDEN(_CharT, "ps"); + else if constexpr (same_as) + return _LIBCPP_STATICALLY_WIDEN(_CharT, "ns"); + else if constexpr (same_as) +# ifndef _LIBCPP_HAS_NO_UNICODE + return _LIBCPP_STATICALLY_WIDEN(_CharT, "\u00b5s"); +# else + return _LIBCPP_STATICALLY_WIDEN(_CharT, "us"); +# endif + else if constexpr (same_as) + return _LIBCPP_STATICALLY_WIDEN(_CharT, "ms"); + else if constexpr (same_as) + return _LIBCPP_STATICALLY_WIDEN(_CharT, "cs"); + else if constexpr (same_as) + return _LIBCPP_STATICALLY_WIDEN(_CharT, "ds"); + else if constexpr (same_as>) + return _LIBCPP_STATICALLY_WIDEN(_CharT, "s"); + else if constexpr (same_as) + return _LIBCPP_STATICALLY_WIDEN(_CharT, "das"); + else if constexpr (same_as) + return _LIBCPP_STATICALLY_WIDEN(_CharT, "hs"); + else if constexpr (same_as) + return _LIBCPP_STATICALLY_WIDEN(_CharT, "ks"); + else if constexpr (same_as) + return _LIBCPP_STATICALLY_WIDEN(_CharT, "Ms"); + else if constexpr (same_as) + return _LIBCPP_STATICALLY_WIDEN(_CharT, "Gs"); + else if constexpr (same_as) + return _LIBCPP_STATICALLY_WIDEN(_CharT, "Ts"); + else if constexpr (same_as) + return _LIBCPP_STATICALLY_WIDEN(_CharT, "Ps"); + else if constexpr (same_as) + return _LIBCPP_STATICALLY_WIDEN(_CharT, "Es"); + else if constexpr (same_as>) + return _LIBCPP_STATICALLY_WIDEN(_CharT, "min"); + else if constexpr (same_as>) + return _LIBCPP_STATICALLY_WIDEN(_CharT, "h"); + else if constexpr (same_as>) + 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 +_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 _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 --- a/libcxx/include/__chrono/parser_std_format_spec.h +++ b/libcxx/include/__chrono/parser_std_format_spec.h @@ -29,6 +29,8 @@ 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 --- a/libcxx/include/chrono +++ b/libcxx/include/chrono @@ -207,7 +207,11 @@ template constexpr ToDuration round(const duration& d); // C++17 -// duration I/O is elsewhere +// duration I/O +template // C++20 + basic_ostream& + operator<<(basic_ostream& os, + const duration& d); // time_point arithmetic (all constexpr in C++14) template @@ -625,6 +629,8 @@ } // chrono namespace std { + template + struct formatter, charT>; // C++20 template struct formatter; // C++20 template struct formatter; // C++20 template struct formatter; // 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 --- /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 + +// + +// template> class duration; + +// template +// basic_ostream& +// operator<<(basic_ostream& os, +// const duration& d); + +#include + +#include +#include +#include + +#include "make_string.h" +#include "platform_support.h" // locale name macros +#include "test_macros.h" + +#define SV(S) MAKE_STRING_VIEW(CharT, S) + +template +static std::basic_string stream_c_locale(std::chrono::duration duration) { + std::basic_stringstream sstr; + sstr.precision(4); + sstr << std::fixed << duration; + return sstr.str(); +} + +template +static std::basic_string stream_fr_FR_locale(std::chrono::duration duration) { + std::basic_stringstream sstr; + const std::locale locale(LOCALE_fr_FR_UTF_8); + sstr.imbue(locale); + sstr.precision(4); + sstr << std::fixed << duration; + return sstr.str(); +} + +template +static std::basic_string stream_ja_JP_locale(std::chrono::duration duration) { + std::basic_stringstream sstr; + const std::locale locale(LOCALE_ja_JP_UTF_8); + sstr.imbue(locale); + sstr.precision(4); + sstr << std::fixed << duration; + return sstr.str(); +} + +template +static void test_values() { + using namespace std::literals::chrono_literals; + + assert(stream_c_locale(-1'000'000s) == SV("-1000000s")); + assert(stream_c_locale(1'000'000s) == SV("1000000s")); + assert(stream_c_locale(-1'000.123456s) == SV("-1000.1235s")); + assert(stream_c_locale(1'000.123456s) == SV("1000.1235s")); + + if constexpr (std::same_as) { +#if defined(__APPLE__) + assert(stream_fr_FR_locale(-1'000'000s) == SV("-1000000s")); + assert(stream_fr_FR_locale(1'000'000s) == SV("1000000s")); + assert(stream_fr_FR_locale(-1'000.123456s) == SV("-1000,1235s")); + assert(stream_fr_FR_locale(1'000.123456s) == SV("1000,1235s")); +#else + assert(stream_fr_FR_locale(-1'000'000s) == SV("-1 000 000s")); + assert(stream_fr_FR_locale(1'000'000s) == SV("1 000 000s")); + assert(stream_fr_FR_locale(-1'000.123456s) == SV("-1 000,1235s")); + assert(stream_fr_FR_locale(1'000.123456s) == SV("1 000,1235s")); +#endif + } else { +#ifdef _WIN32 + assert(stream_fr_FR_locale(-1'000'000s) == SV("-1\u00A0000\u00A0000s")); + assert(stream_fr_FR_locale(1'000'000s) == SV("1\u00A0000\u00A0000s")); + assert(stream_fr_FR_locale(-1'000.123456s) == SV("-1\u00A0000,1235s")); + assert(stream_fr_FR_locale(1'000.123456s) == SV("1\u00A0000,1235s")); +#elif defined(__APPLE__) + assert(stream_fr_FR_locale(-1'000'000s) == SV("-1000000s")); + assert(stream_fr_FR_locale(1'000'000s) == SV("1000000s")); + assert(stream_fr_FR_locale(-1'000.123456s) == SV("-1000,1235s")); + assert(stream_fr_FR_locale(1'000.123456s) == SV("1000,1235s")); +#else + assert(stream_fr_FR_locale(-1'000'000s) == SV("-1\u202f000\u202f000s")); + assert(stream_fr_FR_locale(1'000'000s) == SV("1\u202f000\u202f000s")); + assert(stream_fr_FR_locale(-1'000.123456s) == SV("-1\u202f000,1235s")); + assert(stream_fr_FR_locale(1'000.123456s) == SV("1\u202f000,1235s")); +#endif + } + + assert(stream_ja_JP_locale(-1'000'000s) == SV("-1,000,000s")); + assert(stream_ja_JP_locale(1'000'000s) == SV("1,000,000s")); + assert(stream_ja_JP_locale(-1'000.123456s) == SV("-1,000.1235s")); + assert(stream_ja_JP_locale(1'000.123456s) == SV("1,000.1235s")); +} + +template +static void test_units() { + using namespace std::literals::chrono_literals; + + // C locale + assert(stream_c_locale(std::chrono::duration(0)) == SV("0as")); + assert(stream_c_locale(std::chrono::duration(0)) == SV("0fs")); + assert(stream_c_locale(std::chrono::duration(0)) == SV("0ps")); + assert(stream_c_locale(0ns) == SV("0ns")); +#ifndef TEST_HAS_NO_UNICODE + assert(stream_c_locale(0us) == SV("0\u00b5s")); +#else + assert(stream_c_locale(0us) == SV("0us")); +#endif + assert(stream_c_locale(0ms) == SV("0ms")); + assert(stream_c_locale(std::chrono::duration(0)) == SV("0cs")); + assert(stream_c_locale(std::chrono::duration(0)) == SV("0ds")); + + assert(stream_c_locale(0s) == SV("0s")); + + assert(stream_c_locale(std::chrono::duration(0)) == SV("0das")); + assert(stream_c_locale(std::chrono::duration(0)) == SV("0hs")); + assert(stream_c_locale(std::chrono::duration(0)) == SV("0ks")); + assert(stream_c_locale(std::chrono::duration(0)) == SV("0Ms")); + assert(stream_c_locale(std::chrono::duration(0)) == SV("0Gs")); + assert(stream_c_locale(std::chrono::duration(0)) == SV("0Ts")); + assert(stream_c_locale(std::chrono::duration(0)) == SV("0Ps")); + assert(stream_c_locale(std::chrono::duration(0)) == SV("0Es")); + + assert(stream_c_locale(0min) == SV("0min")); + assert(stream_c_locale(0h) == SV("0h")); + assert(stream_c_locale(std::chrono::duration>(0)) == SV("0d")); + + assert(stream_c_locale(std::chrono::duration>(0)) == SV("0[42]s")); + assert(stream_c_locale(std::chrono::duration>(0)) == SV("0[11]s")); + assert(stream_c_locale(std::chrono::duration>(0)) == SV("0[11/9]s")); + + // fr_FR locale + assert(stream_fr_FR_locale(std::chrono::duration(0)) == SV("0as")); + assert(stream_fr_FR_locale(std::chrono::duration(0)) == SV("0fs")); + assert(stream_fr_FR_locale(std::chrono::duration(0)) == SV("0ps")); + assert(stream_fr_FR_locale(0ns) == SV("0ns")); +#ifndef TEST_HAS_NO_UNICODE + assert(stream_fr_FR_locale(0us) == SV("0\u00b5s")); +#else + assert(stream_fr_FR_locale(0us) == SV("0us")); +#endif + assert(stream_fr_FR_locale(0ms) == SV("0ms")); + assert(stream_fr_FR_locale(std::chrono::duration(0)) == SV("0cs")); + assert(stream_fr_FR_locale(std::chrono::duration(0)) == SV("0ds")); + + assert(stream_fr_FR_locale(0s) == SV("0s")); + + assert(stream_fr_FR_locale(std::chrono::duration(0)) == SV("0das")); + assert(stream_fr_FR_locale(std::chrono::duration(0)) == SV("0hs")); + assert(stream_fr_FR_locale(std::chrono::duration(0)) == SV("0ks")); + assert(stream_fr_FR_locale(std::chrono::duration(0)) == SV("0Ms")); + assert(stream_fr_FR_locale(std::chrono::duration(0)) == SV("0Gs")); + assert(stream_fr_FR_locale(std::chrono::duration(0)) == SV("0Ts")); + assert(stream_fr_FR_locale(std::chrono::duration(0)) == SV("0Ps")); + assert(stream_fr_FR_locale(std::chrono::duration(0)) == SV("0Es")); + + assert(stream_fr_FR_locale(0min) == SV("0min")); + assert(stream_fr_FR_locale(0h) == SV("0h")); + assert(stream_fr_FR_locale(std::chrono::duration>(0)) == SV("0d")); + + assert(stream_fr_FR_locale(std::chrono::duration>(0)) == SV("0[42]s")); + assert(stream_fr_FR_locale(std::chrono::duration>(0)) == SV("0[11]s")); + assert(stream_fr_FR_locale(std::chrono::duration>(0)) == SV("0[11/9]s")); + + // ja_JP locale + assert(stream_ja_JP_locale(std::chrono::duration(0)) == SV("0as")); + assert(stream_ja_JP_locale(std::chrono::duration(0)) == SV("0fs")); + assert(stream_ja_JP_locale(std::chrono::duration(0)) == SV("0ps")); + assert(stream_ja_JP_locale(0ns) == SV("0ns")); +#ifndef TEST_HAS_NO_UNICODE + assert(stream_ja_JP_locale(0us) == SV("0\u00b5s")); +#else + assert(stream_ja_JP_locale(0us) == SV("0us")); +#endif + assert(stream_ja_JP_locale(0ms) == SV("0ms")); + assert(stream_ja_JP_locale(std::chrono::duration(0)) == SV("0cs")); + assert(stream_ja_JP_locale(std::chrono::duration(0)) == SV("0ds")); + + assert(stream_ja_JP_locale(0s) == SV("0s")); + + assert(stream_ja_JP_locale(std::chrono::duration(0)) == SV("0das")); + assert(stream_ja_JP_locale(std::chrono::duration(0)) == SV("0hs")); + assert(stream_ja_JP_locale(std::chrono::duration(0)) == SV("0ks")); + assert(stream_ja_JP_locale(std::chrono::duration(0)) == SV("0Ms")); + assert(stream_ja_JP_locale(std::chrono::duration(0)) == SV("0Gs")); + assert(stream_ja_JP_locale(std::chrono::duration(0)) == SV("0Ts")); + assert(stream_ja_JP_locale(std::chrono::duration(0)) == SV("0Ps")); + assert(stream_ja_JP_locale(std::chrono::duration(0)) == SV("0Es")); + + assert(stream_ja_JP_locale(0min) == SV("0min")); + assert(stream_ja_JP_locale(0h) == SV("0h")); + assert(stream_ja_JP_locale(std::chrono::duration>(0)) == SV("0d")); + + assert(stream_ja_JP_locale(std::chrono::duration>(0)) == SV("0[42]s")); + assert(stream_ja_JP_locale(std::chrono::duration>(0)) == SV("0[11]s")); + assert(stream_ja_JP_locale(std::chrono::duration>(0)) == SV("0[11/9]s")); +} + +template +static void test() { + test_values(); + test_units(); +} + +int main(int, char**) { + test(); + +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#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 --- /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 + +// + +// template +// struct formatter, charT>; + +#include +#include + +#include +#include +#include +#include +#include + +#include "formatter_tests.h" +#include "make_string.h" +#include "platform_support.h" // locale name macros +#include "test_macros.h" + +template +static void test_no_chrono_specs() { + using namespace std::literals::chrono_literals; + + check(SV("1as"), SV("{}"), std::chrono::duration(1)); + check(SV("1fs"), SV("{}"), std::chrono::duration(1)); + check(SV("1ps"), SV("{}"), std::chrono::duration(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(1)); + check(SV("1ds"), SV("{}"), std::chrono::duration(1)); + + check(SV("1s"), SV("{}"), 1s); + + check(SV("1das"), SV("{}"), std::chrono::duration(1)); + check(SV("1hs"), SV("{}"), std::chrono::duration(1)); + check(SV("1ks"), SV("{}"), std::chrono::duration(1)); + check(SV("1Ms"), SV("{}"), std::chrono::duration(1)); + check(SV("1Gs"), SV("{}"), std::chrono::duration(1)); + check(SV("1Ts"), SV("{}"), std::chrono::duration(1)); + check(SV("1Ps"), SV("{}"), std::chrono::duration(1)); + check(SV("1Es"), SV("{}"), std::chrono::duration(1)); + + check(SV("1min"), SV("{}"), 1min); + check(SV("1h"), SV("{}"), 1h); + check(SV("1d"), SV("{}"), std::chrono::duration>(1)); + + check(SV("1[42]s"), SV("{}"), std::chrono::duration>(1)); + check(SV("1[11]s"), SV("{}"), std::chrono::duration>(1)); + check(SV("1[11/9]s"), SV("{}"), std::chrono::duration>(1)); +} + +template +static void test_valid_positive_integral_values() { + using namespace std::literals::chrono_literals; + + constexpr std::basic_string_view 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 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>(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>(7)); + + // Use supplied locale (ja_JP). This locale has a different 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>(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>(7)); + +#endif // defined(__APPLE__) || defined(_AIX) + std::locale::global(std::locale::classic()); +} + +template +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("-") 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 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 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 different 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 +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(1)); + check(SV("00.000000000000001"), SV("{:%S}"), std::chrono::duration(1)); + check(SV("00.000000000000000001"), SV("{:%S}"), std::chrono::duration(1)); + + check(SV("00.001"), SV("{:%S}"), 1ms); + check(SV("00.01"), SV("{:%S}"), std::chrono::duration(1)); + check(SV("00.1"), SV("{:%S}"), std::chrono::duration(1)); + check(SV("01.1"), SV("{:%S}"), std::chrono::duration(11)); + + check(SV("00.001"), SV("{:%S}"), std::chrono::duration(1.123456789)); + check(SV("00.011"), SV("{:%S}"), std::chrono::duration(11.123456789)); + check(SV("01"), SV("{:%S}"), std::chrono::duration(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(1)); + check(SV("00.000000000000001"), SV("{:%OS}"), std::chrono::duration(1)); + check(SV("00.000000000000000001"), SV("{:%OS}"), std::chrono::duration(1)); + + check(SV("00.001"), SV("{:%OS}"), 1ms); + check(SV("00.01"), SV("{:%OS}"), std::chrono::duration(1)); + check(SV("00.1"), SV("{:%OS}"), std::chrono::duration(1)); + check(SV("01.1"), SV("{:%OS}"), std::chrono::duration(11)); + + check(SV("00.001"), SV("{:%OS}"), std::chrono::duration(1.123456789)); + check(SV("00.011"), SV("{:%OS}"), std::chrono::duration(11.123456789)); + check(SV("01"), SV("{:%OS}"), std::chrono::duration(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(1)); + check(SV("01:05:06.1"), SV("{:%T}"), 1h + 5min + 6s + std::chrono::duration(1)); + check(SV("01:05:07.1"), SV("{:%T}"), 1h + 5min + 6s + std::chrono::duration(11)); + + check( + SV("00:01:02"), SV("{:%T}"), std::chrono::duration>(1) + std::chrono::duration(2.5)); + check(SV("01:05:11"), + SV("{:%T}"), + std::chrono::duration>(1) + std::chrono::duration>(5) + + std::chrono::duration(11.123456789)); + check(SV("01:06:01"), + SV("{:%T}"), + std::chrono::duration>(1) + + std::chrono::duration>(5) + std::chrono::duration(61.123456789)); + + check(SV("0"), SV("{:%j}"), std::chrono::duration(1.)); + check(SV("1"), SV("{:%j}"), std::chrono::duration(86'400'000)); + check(SV("1"), SV("{:%j}"), std::chrono::duration>(0.14285714286)); + + check(SV("1000000"), SV("{:%Q}"), 1'000'000s); + check(SV("1"), SV("{:%Q}"), std::chrono::duration(1.)); + check(SV("1.123456789"), SV("{:.6%Q}"), std::chrono::duration(1.123456789)); + check(SV("1.123456789"), SV("{:.9%Q}"), std::chrono::duration(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(1)); + check(SV("00,000000000000001"), SV("{:L%S}"), std::chrono::duration(1)); + check(SV("00,000000000000000001"), SV("{:L%S}"), std::chrono::duration(1)); + + check(SV("00,001"), SV("{:L%S}"), 1ms); + check(SV("00,01"), SV("{:L%S}"), std::chrono::duration(1)); + check(SV("00,1"), SV("{:L%S}"), std::chrono::duration(1)); + check(SV("01,1"), SV("{:L%S}"), std::chrono::duration(11)); + + check(SV("00,001"), SV("{:L%S}"), std::chrono::duration(1.123456789)); + check(SV("00,011"), SV("{:L%S}"), std::chrono::duration(11.123456789)); + check(SV("01"), SV("{:L%S}"), std::chrono::duration(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(1)); + check(SV("00,000000000000001"), SV("{:L%OS}"), std::chrono::duration(1)); + check(SV("00,000000000000000001"), SV("{:L%OS}"), std::chrono::duration(1)); + + check(SV("00,001"), SV("{:L%OS}"), 1ms); + check(SV("00,01"), SV("{:L%OS}"), std::chrono::duration(1)); + check(SV("00,1"), SV("{:L%OS}"), std::chrono::duration(1)); + check(SV("01,1"), SV("{:L%OS}"), std::chrono::duration(11)); + + check(SV("00,001"), SV("{:L%OS}"), std::chrono::duration(1.123456789)); + check(SV("00,011"), SV("{:L%OS}"), std::chrono::duration(11.123456789)); + check(SV("01"), SV("{:L%OS}"), std::chrono::duration(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(1)); + check(SV("01:05:06,1"), SV("{:L%T}"), 1h + 5min + 6s + std::chrono::duration(1)); + check(SV("01:05:07,1"), SV("{:L%T}"), 1h + 5min + 6s + std::chrono::duration(11)); + + check(SV("00:01:02"), + SV("{:L%T}"), + std::chrono::duration>(1) + std::chrono::duration(2.5)); + check(SV("01:05:11"), + SV("{:L%T}"), + std::chrono::duration>(1) + std::chrono::duration>(5) + + std::chrono::duration(11.123456789)); + check(SV("01:06:01"), + SV("{:L%T}"), + std::chrono::duration>(1) + + std::chrono::duration>(5) + std::chrono::duration(61.123456789)); + + check(SV("0"), SV("{:L%j}"), std::chrono::duration(1.)); + check(SV("1"), SV("{:L%j}"), std::chrono::duration(86'400'000)); + check(SV("1"), SV("{:L%j}"), std::chrono::duration>(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(1.)); + check(SV("1.123456789"), SV("{:.6L%Q}"), std::chrono::duration(1.123456789)); + check(SV("1.123456789"), SV("{:.9L%Q}"), std::chrono::duration(1.123456789)); + + // Use supplied locale (ja_JP). This locale has a different 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(1)); + check(loc, SV("00.000000000000001"), SV("{:L%S}"), std::chrono::duration(1)); + check(loc, SV("00.000000000000000001"), SV("{:L%S}"), std::chrono::duration(1)); + + check(loc, SV("00.001"), SV("{:L%S}"), 1ms); + check(loc, SV("00.01"), SV("{:L%S}"), std::chrono::duration(1)); + check(loc, SV("00.1"), SV("{:L%S}"), std::chrono::duration(1)); + check(loc, SV("01.1"), SV("{:L%S}"), std::chrono::duration(11)); + + check(loc, SV("00.001"), SV("{:L%S}"), std::chrono::duration(1.123456789)); + check(loc, SV("00.011"), SV("{:L%S}"), std::chrono::duration(11.123456789)); + check(loc, SV("01"), SV("{:L%S}"), std::chrono::duration(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(1)); + check(SV("00.000000000000001"), SV("{:%OS}"), std::chrono::duration(1)); + check(SV("00.000000000000000001"), SV("{:%OS}"), std::chrono::duration(1)); + + check(SV("00.001"), SV("{:%OS}"), 1ms); + check(SV("00.01"), SV("{:%OS}"), std::chrono::duration(1)); + check(SV("00.1"), SV("{:%OS}"), std::chrono::duration(1)); + check(SV("01.1"), SV("{:%OS}"), std::chrono::duration(11)); + + check(SV("00.001"), SV("{:%OS}"), std::chrono::duration(1.123456789)); + check(SV("00.011"), SV("{:%OS}"), std::chrono::duration(11.123456789)); + check(SV("01"), SV("{:%OS}"), std::chrono::duration(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(1)); + check(loc, SV("〇.000000000000001"), SV("{:L%OS}"), std::chrono::duration(1)); + check(loc, SV("〇.000000000000000001"), SV("{:L%OS}"), std::chrono::duration(1)); + + check(loc, SV("〇.001"), SV("{:L%OS}"), 1ms); + check(loc, SV("〇.01"), SV("{:L%OS}"), std::chrono::duration(1)); + check(loc, SV("〇.1"), SV("{:L%OS}"), std::chrono::duration(1)); + check(loc, SV("一.1"), SV("{:L%OS}"), std::chrono::duration(11)); + + check(loc, SV("〇.001"), SV("{:L%OS}"), std::chrono::duration(1.123456789)); + check(loc, SV("〇.011"), SV("{:L%OS}"), std::chrono::duration(11.123456789)); + check(loc, SV("一"), SV("{:L%OS}"), std::chrono::duration(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(1)); + check(loc, SV("01:05:06.1"), SV("{:L%T}"), 1h + 5min + 6s + std::chrono::duration(1)); + check(loc, SV("01:05:07.1"), SV("{:L%T}"), 1h + 5min + 6s + std::chrono::duration(11)); + + check(loc, + SV("00:01:02"), + SV("{:L%T}"), + std::chrono::duration>(1) + std::chrono::duration(2.5)); + check(loc, + SV("01:05:11"), + SV("{:L%T}"), + std::chrono::duration>(1) + std::chrono::duration>(5) + + std::chrono::duration(11.123456789)); + check(loc, + SV("01:06:01"), + SV("{:L%T}"), + std::chrono::duration>(1) + + std::chrono::duration>(5) + std::chrono::duration(61.123456789)); + + check(loc, SV("0"), SV("{:L%j}"), std::chrono::duration(1.)); + check(loc, SV("1"), SV("{:L%j}"), std::chrono::duration(86'400'000)); + check(loc, SV("1"), SV("{:L%j}"), std::chrono::duration>(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(1.)); + check(loc, SV("1.123456789"), SV("{:.6L%Q}"), std::chrono::duration(1.123456789)); + check(loc, SV("1.123456789"), SV("{:.9L%Q}"), std::chrono::duration(1.123456789)); + + std::locale::global(std::locale::classic()); +} + +template +static void test_valid_values() { + test_valid_positive_integral_values(); + test_valid_negative_integral_values(); + test_valid_fractional_values(); +} + +template +static void test() { + using namespace std::literals::chrono_literals; + + test_no_chrono_specs(); + test_valid_values(); + check_invalid_types( + {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(); + +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#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 --- 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 @@ // In libc++ std:::ostringstream requires localization support. #ifndef TEST_HAS_NO_LOCALIZATION - assert_is_not_formattable(); + assert_is_formattable(); assert_is_not_formattable, CharT>(); //assert_is_formattable, CharT>();