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 @@ -23,7 +23,7 @@ `[time.syn] `_,"Formatter ``chrono::year_month_day_last``",,Mark de Wever,|Complete|, Clang 16 `[time.syn] `_,"Formatter ``chrono::year_month_weekday``",,Mark de Wever,|Complete|, Clang 16 `[time.syn] `_,"Formatter ``chrono::year_month_weekday_last``",,Mark de Wever,|Complete|, Clang 16 -`[time.syn] `_,"Formatter ``chrono::hh_mm_ss>``",,Mark de Wever,|In Progress|, +`[time.syn] `_,"Formatter ``chrono::hh_mm_ss>``",,Mark de Wever,|Complete|, Clang 16 `[time.syn] `_,"Formatter ``chrono::sys_info``",A ```` implementation,Mark de Wever,, `[time.syn] `_,"Formatter ``chrono::local_info``",A ```` implementation,Mark de Wever,, `[time.syn] `_,"Formatter ``chrono::zoned_time``",A ```` implementation,Mark de Wever,, diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -214,6 +214,7 @@ __charconv/to_chars_base_10.h __charconv/to_chars_result.h __chrono/calendar.h + __chrono/concepts.h __chrono/convert_to_timespec.h __chrono/convert_to_tm.h __chrono/day.h diff --git a/libcxx/include/__chrono/concepts.h b/libcxx/include/__chrono/concepts.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__chrono/concepts.h @@ -0,0 +1,32 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP___CHRONO_CONCEPTS_H +#define _LIBCPP___CHRONO_CONCEPTS_H + +#include <__chrono/hh_mm_ss.h> +#include <__config> +#include <__type_traits/is_specialization.h> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER > 17 + +template +concept __is_hh_mm_ss = __is_specialization_v<_Tp, chrono::hh_mm_ss>; + +#endif // _LIBCPP_STD_VER > 17 + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___CHRONO_CONCEPTS_H diff --git a/libcxx/include/__chrono/convert_to_tm.h b/libcxx/include/__chrono/convert_to_tm.h --- a/libcxx/include/__chrono/convert_to_tm.h +++ b/libcxx/include/__chrono/convert_to_tm.h @@ -10,6 +10,7 @@ #ifndef _LIBCPP___CHRONO_CONVERT_TO_TM_H #define _LIBCPP___CHRONO_CONVERT_TO_TM_H +#include <__chrono/concepts.h> #include <__chrono/day.h> #include <__chrono/duration.h> #include <__chrono/hh_mm_ss.h> @@ -114,6 +115,16 @@ } else if constexpr (same_as<_ChronoT, chrono::year_month_weekday> || same_as<_ChronoT, chrono::year_month_weekday_last>) { return std::__convert_to_tm<_Tm>(chrono::year_month_day{static_cast(__value)}, __value.weekday()); + } else if constexpr (__is_hh_mm_ss<_ChronoT>) { + __result.tm_sec = __value.seconds().count(); + __result.tm_min = __value.minutes().count(); + // In libc++ hours is stored as a long. The type in std::tm is an int. So + // the overflow can only occur when hour uses more bits than an int + // provides. + if constexpr (sizeof(std::chrono::hours::rep) > sizeof(__result.tm_hour)) + if (__value.hours().count() > std::numeric_limits::max()) + std::__throw_format_error("Formatting hh_mm_ss, encountered an hour overflow"); + __result.tm_hour = __value.hours().count(); } else static_assert(sizeof(_ChronoT) == 0, "Add the missing type specialization"); 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 @@ -11,6 +11,7 @@ #define _LIBCPP___CHRONO_FORMATTER_H #include <__chrono/calendar.h> +#include <__chrono/concepts.h> #include <__chrono/convert_to_tm.h> #include <__chrono/day.h> #include <__chrono/duration.h> @@ -104,11 +105,27 @@ __fraction.count(), chrono::hh_mm_ss<_Tp>::fractional_width); } +template +_LIBCPP_HIDE_FROM_ABI void __format_sub_seconds(const _Tp& __value, basic_stringstream<_CharT>& __sstr) { + __sstr << std::use_facet>(__sstr.getloc()).decimal_point(); + if constexpr (chrono::treat_as_floating_point_v) + std::format_to(std::ostreambuf_iterator<_CharT>{__sstr}, + _LIBCPP_STATICALLY_WIDEN(_CharT, "{:0{}.0f}"), + __value.subseconds().count(), + __value.fractional_width); + else + std::format_to(std::ostreambuf_iterator<_CharT>{__sstr}, + _LIBCPP_STATICALLY_WIDEN(_CharT, "{:0{}}"), + __value.subseconds().count(), + __value.fractional_width); +} template consteval bool __use_fraction() { if constexpr (chrono::__is_duration<_Tp>::value) return chrono::hh_mm_ss<_Tp>::fractional_width; + else if constexpr (__is_hh_mm_ss<_Tp>) + return _Tp::fractional_width; else return false; } @@ -322,6 +339,8 @@ return __value.weekday().ok(); else if constexpr (same_as<_Tp, chrono::year_month_weekday_last>) return __value.weekday().ok(); + else if constexpr (__is_hh_mm_ss<_Tp>) + return true; else static_assert(sizeof(_Tp) == 0, "Add the missing type specialization"); } @@ -358,6 +377,8 @@ return __value.weekday().ok(); else if constexpr (same_as<_Tp, chrono::year_month_weekday_last>) return __value.weekday().ok(); + else if constexpr (__is_hh_mm_ss<_Tp>) + return true; else static_assert(sizeof(_Tp) == 0, "Add the missing type specialization"); } @@ -394,6 +415,8 @@ return __value.ok(); else if constexpr (same_as<_Tp, chrono::year_month_weekday_last>) return __value.ok(); + else if constexpr (__is_hh_mm_ss<_Tp>) + return true; else static_assert(sizeof(_Tp) == 0, "Add the missing type specialization"); } @@ -430,6 +453,8 @@ return __value.month().ok(); else if constexpr (same_as<_Tp, chrono::year_month_weekday_last>) return __value.month().ok(); + else if constexpr (__is_hh_mm_ss<_Tp>) + return true; else static_assert(sizeof(_Tp) == 0, "Add the missing type specialization"); } @@ -478,6 +503,29 @@ if (__specs.__chrono_.__month_name_ && !__formatter::__month_name_ok(__value)) std::__throw_format_error("formatting a month name from an invalid month number"); + if constexpr (__is_hh_mm_ss<_Tp>) { + // Note this is a pedantic intepretation of the Standard. A hh_mm_ss + // is no longer a time_of_day and can store an arbitrary number of + // hours. A number of hours in a 12 or 24 hour clock can't represent + // 24 hours or more. The functions std::chrono::make12 and + // std::chrono::make24 reaffirm this view point. + // + // Interestingly this will be the only output stream function that + // throws. + // + // TODO FMT Propbaby the wording needs to be adapted to + // - The displayed hours is hh_mm_ss.hours() % 24 + // - It should probably allow %j in the same fashion as duration. + // - The stream formatter should change its output when hours >= 24 + // - Write it as not valid, + // - or write the number of days. + if (__specs.__chrono_.__hour_ && __value.hours().count() > 23) + std::__throw_format_error("formatting a hour needs a valid value"); + + if (__value.is_negative()) + __sstr << _CharT('-'); + } + __formatter::__format_chrono_using_chrono_specs(__value, __sstr, __chrono_specs); } } @@ -709,6 +757,16 @@ } }; +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()) { + return _Base::__parse(__parse_ctx, __format_spec::__fields_chrono, __format_spec::__flags::__time); + } +}; #endif // if _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT) _LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/include/__chrono/hh_mm_ss.h b/libcxx/include/__chrono/hh_mm_ss.h --- a/libcxx/include/__chrono/hh_mm_ss.h +++ b/libcxx/include/__chrono/hh_mm_ss.h @@ -85,6 +85,7 @@ chrono::seconds __s_; precision __f_; }; +_LIBCPP_CTAD_SUPPORTED_FOR_TYPE(hh_mm_ss); _LIBCPP_HIDE_FROM_ABI constexpr bool is_am(const hours& __h) noexcept { return __h >= hours( 0) && __h < hours(12); } _LIBCPP_HIDE_FROM_ABI constexpr bool is_pm(const hours& __h) noexcept { return __h >= hours(12) && __h < hours(24); } diff --git a/libcxx/include/__chrono/ostream.h b/libcxx/include/__chrono/ostream.h --- a/libcxx/include/__chrono/ostream.h +++ b/libcxx/include/__chrono/ostream.h @@ -12,6 +12,7 @@ #include <__chrono/day.h> #include <__chrono/duration.h> +#include <__chrono/hh_mm_ss.h> #include <__chrono/month.h> #include <__chrono/month_weekday.h> #include <__chrono/monthday.h> @@ -229,6 +230,12 @@ __ymwdl.weekday_last()); } +template +_LIBCPP_HIDE_FROM_ABI _LIBCPP_AVAILABILITY_FORMAT basic_ostream<_CharT, _Traits>& +operator<<(basic_ostream<_CharT, _Traits>& __os, const hh_mm_ss<_Duration> __hms) { + return __os << std::format(__os.getloc(), _LIBCPP_STATICALLY_WIDEN(_CharT, "{:L%T}"), __hms); +} + } // namespace chrono #endif //if _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT) diff --git a/libcxx/include/__chrono/parser_std_format_spec.h b/libcxx/include/__chrono/parser_std_format_spec.h --- a/libcxx/include/__chrono/parser_std_format_spec.h +++ b/libcxx/include/__chrono/parser_std_format_spec.h @@ -212,6 +212,7 @@ case _CharT('p'): // TODO FMT does the formater require an hour or a time? case _CharT('H'): case _CharT('I'): + __parser_.__hour_ = true; __validate_hour(__flags); break; @@ -219,6 +220,7 @@ case _CharT('R'): case _CharT('T'): case _CharT('X'): + __parser_.__hour_ = true; __format_spec::__validate_time(__flags); break; @@ -311,6 +313,7 @@ switch (*__begin) { case _CharT('X'): + __parser_.__hour_ = true; __format_spec::__validate_time(__flags); break; @@ -359,6 +362,7 @@ case _CharT('I'): case _CharT('H'): + __parser_.__hour_ = true; __format_spec::__validate_hour(__flags); break; diff --git a/libcxx/include/__format/parser_std_format_spec.h b/libcxx/include/__format/parser_std_format_spec.h --- a/libcxx/include/__format/parser_std_format_spec.h +++ b/libcxx/include/__format/parser_std_format_spec.h @@ -195,6 +195,7 @@ struct __chrono { __alignment __alignment_ : 3; bool __locale_specific_form_ : 1; + bool __hour_ : 1; bool __weekday_name_ : 1; bool __weekday_ : 1; bool __day_of_year_ : 1; @@ -325,6 +326,7 @@ .__chrono_ = __chrono{.__alignment_ = __alignment_, .__locale_specific_form_ = __locale_specific_form_, + .__hour_ = __hour_, .__weekday_name_ = __weekday_name_, .__weekday_ = __weekday_, .__day_of_year_ = __day_of_year_, @@ -344,6 +346,8 @@ // These flags are only used for formatting chrono. Since the struct has // padding space left it's added to this structure. + bool __hour_ : 1 {false}; + bool __weekday_name_ : 1 {false}; bool __weekday_ : 1 {false}; @@ -352,7 +356,7 @@ bool __month_name_ : 1 {false}; - uint8_t __reserved_1_ : 3 {0}; + uint8_t __reserved_1_ : 2 {0}; uint8_t __reserved_2_ : 6 {0}; // These two flags are only used internally and not part of the // __parsed_specifications. Therefore put them at the end. diff --git a/libcxx/include/chrono b/libcxx/include/chrono --- a/libcxx/include/chrono +++ b/libcxx/include/chrono @@ -656,6 +656,10 @@ constexpr precision to_duration() const noexcept; }; +template + basic_ostream& + operator<<(basic_ostream& os, const hh_mm_ss& hms); // C++20 + // 26.10, 12/24 hour functions constexpr bool is_am(hours const& h) noexcept; constexpr bool is_pm(hours const& h) noexcept; @@ -691,6 +695,8 @@ template struct formatter; // C++20 template struct formatter; // C++20 template struct formatter; // C++20 + template + struct formatter>, charT>; // C++20 } // namespace std namespace chrono { diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in --- a/libcxx/include/module.modulemap.in +++ b/libcxx/include/module.modulemap.in @@ -670,6 +670,7 @@ module __chrono { module calendar { private header "__chrono/calendar.h" } + module concepts { private header "__chrono/concepts.h" } module convert_to_timespec { private header "__chrono/convert_to_timespec.h" } module convert_to_tm { private header "__chrono/convert_to_tm.h" } module day { private header "__chrono/day.h" } diff --git a/libcxx/test/libcxx/private_headers.verify.cpp b/libcxx/test/libcxx/private_headers.verify.cpp --- a/libcxx/test/libcxx/private_headers.verify.cpp +++ b/libcxx/test/libcxx/private_headers.verify.cpp @@ -248,6 +248,7 @@ #include <__charconv/to_chars_base_10.h> // expected-error@*:* {{use of private header from outside its module: '__charconv/to_chars_base_10.h'}} #include <__charconv/to_chars_result.h> // expected-error@*:* {{use of private header from outside its module: '__charconv/to_chars_result.h'}} #include <__chrono/calendar.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/calendar.h'}} +#include <__chrono/concepts.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/concepts.h'}} #include <__chrono/convert_to_timespec.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/convert_to_timespec.h'}} #include <__chrono/convert_to_tm.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/convert_to_tm.h'}} #include <__chrono/day.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/day.h'}} diff --git a/libcxx/test/libcxx/time/convert_to_tm.pass.cpp b/libcxx/test/libcxx/time/convert_to_tm.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/time/convert_to_tm.pass.cpp @@ -0,0 +1,60 @@ +//===----------------------------------------------------------------------===// +// 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 + +// + +// template +// _LIBCPP_HIDE_FROM_ABI _Tm __convert_to_tm(const _ChronoT& __value) + +// Most of the code is tested indirectly in the chrono formatters. This only +// tests the hour overflow. + +#include +#include +#include + +#include "test_macros.h" + +// libc++ uses a long as representation in std::chrono::hours. +// std::tm uses an int for its integral members. The overflow in the hour +// conversion can only occur on platforms where sizeof(long) > sizeof(int). +// Instead emulate this error by using a "tm" with shorts. +// (The function is already templated to this is quite easy to do,) +struct minimal_short_tm { + short tm_sec; + short tm_min; + short tm_hour; + const char* tm_zone; +}; + +int main(int, char**) { + { // Test with the maximum number of hours that fit in a short. + std::chrono::hh_mm_ss time{std::chrono::hours{32767}}; + minimal_short_tm result = std::__convert_to_tm(time); + assert(result.tm_sec == 0); + assert(result.tm_min == 0); + assert(result.tm_hour == 32767); + } + +#ifndef TEST_HAS_NO_EXCEPTIONS + { // Test above the maximum number of hours that fit in a short. + std::chrono::hh_mm_ss time{std::chrono::hours{32768}}; + try { + TEST_IGNORE_NODISCARD std::__convert_to_tm(time); + assert(false); + } catch ([[maybe_unused]] const std::format_error& e) { + LIBCPP_ASSERT(e.what() == std::string_view("Formatting hh_mm_ss, encountered an hour overflow")); + return 0; + } + assert(false); + } +#endif // TEST_HAS_NO_EXCEPTIONS + + return 0; +} diff --git a/libcxx/test/std/time/time.hms/time.hms.nonmembers/ostream.pass.cpp b/libcxx/test/std/time/time.hms/time.hms.nonmembers/ostream.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/time/time.hms/time.hms.nonmembers/ostream.pass.cpp @@ -0,0 +1,157 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// + +// class hh_mm_ss; + +// template +// basic_ostream& +// operator<<(basic_ostream& os, const hh_mm_ss& hms); + +#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::hh_mm_ss hms) { + std::basic_stringstream sstr; + sstr << hms; + return sstr.str(); +} + +template +static std::basic_string stream_fr_FR_locale(std::chrono::hh_mm_ss hms) { + std::basic_stringstream sstr; + const std::locale locale(LOCALE_fr_FR_UTF_8); + sstr.imbue(locale); + sstr << hms; + return sstr.str(); +} + +template +static std::basic_string stream_ja_JP_locale(std::chrono::hh_mm_ss hms) { + std::basic_stringstream sstr; + const std::locale locale(LOCALE_ja_JP_UTF_8); + sstr.imbue(locale); + sstr << hms; + return sstr.str(); +} + +template +static void test() { + // Note std::atto can't be tested since the ratio conversion from std::atto + // std::chrono::seconds to std::chrono::hours overflows when intmax_t is a + // 64-bit type. This is a limitiation in the constructor of + // std::chrono::hh_mm_ss. + + // C locale - integral power of 10 ratios + assert(stream_c_locale(std::chrono::hh_mm_ss{std::chrono::duration{1'234'567'890}}) == + SV("00:00:00.000001234567890")); + assert(stream_c_locale(std::chrono::hh_mm_ss{std::chrono::duration{1'234'567'890}}) == + SV("00:00:00.001234567890")); + assert(stream_c_locale(std::chrono::hh_mm_ss{std::chrono::duration{1'234'567'890}}) == + SV("00:00:01.234567890")); + assert(stream_c_locale(std::chrono::hh_mm_ss{std::chrono::duration{1'234'567}}) == + SV("00:00:01.234567")); + assert(stream_c_locale(std::chrono::hh_mm_ss{std::chrono::duration{123'456}}) == + SV("00:02:03.456")); + assert(stream_c_locale(std::chrono::hh_mm_ss{std::chrono::duration{12'345}}) == + SV("00:02:03.45")); + assert(stream_c_locale(std::chrono::hh_mm_ss{std::chrono::duration{1'234}}) == + SV("00:02:03.4")); + + assert(stream_c_locale(std::chrono::hh_mm_ss{std::chrono::duration{123}}) == SV("00:02:03")); + + assert(stream_c_locale(std::chrono::hh_mm_ss{std::chrono::duration{-366}}) == + SV("-01:01:00")); + assert(stream_c_locale(std::chrono::hh_mm_ss{std::chrono::duration{-72}}) == + SV("-02:00:00")); + assert(stream_c_locale(std::chrono::hh_mm_ss{std::chrono::duration{-86}}) == + SV("-23:53:20")); + + // Starting at mega it will pass one day + + // fr_FR locale - integral power of not 10 ratios + assert(stream_fr_FR_locale(std::chrono::hh_mm_ss{ + std::chrono::duration>{5'000}}) == SV("00:00:00,0010000")); + assert(stream_fr_FR_locale(std::chrono::hh_mm_ss{std::chrono::duration>{3}}) == + SV("00:00:00,000375")); + assert(stream_fr_FR_locale(std::chrono::hh_mm_ss{std::chrono::duration>{1}}) == + SV("00:00:00,00025")); + assert(stream_fr_FR_locale(std::chrono::hh_mm_ss{std::chrono::duration>{5}}) == + SV("00:00:00,0010")); + assert(stream_fr_FR_locale(std::chrono::hh_mm_ss{std::chrono::duration>{-4}}) == + SV("-00:00:00,500")); + assert(stream_fr_FR_locale(std::chrono::hh_mm_ss{std::chrono::duration>{-8}}) == + SV("-00:00:02,00")); + assert(stream_fr_FR_locale(std::chrono::hh_mm_ss{std::chrono::duration>{-5}}) == + SV("-00:00:01,0")); + + // TODO FMT Note there's no wording on the rounding + assert(stream_fr_FR_locale(std::chrono::hh_mm_ss{std::chrono::duration>{5}}) == + SV("00:00:00,555555")); + assert(stream_fr_FR_locale(std::chrono::hh_mm_ss{std::chrono::duration>{7}}) == + SV("00:00:01,000000")); + assert(stream_fr_FR_locale(std::chrono::hh_mm_ss{std::chrono::duration>{1}}) == + SV("00:00:00,166666")); + assert(stream_fr_FR_locale(std::chrono::hh_mm_ss{std::chrono::duration>{2}}) == + SV("00:00:00,666666")); + + // ja_JP locale - floating points + + assert(stream_c_locale(std::chrono::hh_mm_ss{ + std::chrono::duration{1'234'567'890.123}}) == SV("00:00:00.000001234567890")); + assert(stream_c_locale(std::chrono::hh_mm_ss{ + std::chrono::duration{1'234'567'890.123}}) == SV("00:00:00.001234567890")); + assert(stream_c_locale(std::chrono::hh_mm_ss{ + std::chrono::duration{1'234'567'890.123}}) == SV("00:00:01.234567890")); + assert(stream_c_locale(std::chrono::hh_mm_ss{std::chrono::duration{1'234'567.123}}) == + SV("00:00:01.234567")); + assert(stream_c_locale(std::chrono::hh_mm_ss{std::chrono::duration{123'456.123}}) == + SV("00:02:03.456")); + assert(stream_c_locale(std::chrono::hh_mm_ss{std::chrono::duration{12'345.123}}) == + SV("00:02:03.45")); + assert(stream_c_locale(std::chrono::hh_mm_ss{std::chrono::duration{1'234.123}}) == + SV("00:02:03.4")); + + assert(stream_c_locale(std::chrono::hh_mm_ss{std::chrono::duration{123.123}}) == SV("00:02:03")); + + assert(stream_c_locale(std::chrono::hh_mm_ss{std::chrono::duration{-366.5}}) == + SV("-01:01:05")); + assert(stream_c_locale(std::chrono::hh_mm_ss{std::chrono::duration{-72.64}}) == + SV("-02:01:04")); + assert(stream_c_locale(std::chrono::hh_mm_ss{std::chrono::duration{-86}}) == + SV("-23:53:20")); +} + +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.hh_mm_ss.pass.cpp b/libcxx/test/std/time/time.syn/formatter.hh_mm_ss.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/time/time.syn/formatter.hh_mm_ss.pass.cpp @@ -0,0 +1,553 @@ +//===----------------------------------------------------------------------===// +// 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 "string_literal.h" +#include "test_macros.h" + +template +static void test_no_chrono_specs() { + using namespace std::literals::chrono_literals; + + std::locale::global(std::locale(LOCALE_fr_FR_UTF_8)); + + // Non localized output + check(SV("00:00:00.000"), SV("{}"), std::chrono::hh_mm_ss{0ms}); + check(SV("*00:00:00.000*"), SV("{:*^14}"), std::chrono::hh_mm_ss{0ms}); + check(SV("*00:00:00.000"), SV("{:*>13}"), std::chrono::hh_mm_ss{0ms}); + + std::locale::global(std::locale::classic()); +} + +template +static void test_valid_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" + "%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" + "%n}"); + + const std::locale loc(LOCALE_ja_JP_UTF_8); + std::locale::global(std::locale(LOCALE_fr_FR_UTF_8)); + + // Non localized output using C-locale + check(SV("%H='00'\t" + "%OH='00'\t" + "%I='12'\t" + "%OI='12'\t" + "%M='00'\t" + "%OM='00'\t" + "%S='00'\t" + "%OS='00'\t" + "%p='AM'\t" + "%R='00:00'\t" + "%T='00:00:00'\t" + "%r='12:00:00 AM'\t" + "%X='00:00:00'\t" + "%EX='00:00:00'\t" + "\n"), + fmt, + std::chrono::hh_mm_ss(0s)); + + check(SV("%H='23'\t" + "%OH='23'\t" + "%I='11'\t" + "%OI='11'\t" + "%M='31'\t" + "%OM='31'\t" + "%S='30.123'\t" + "%OS='30.123'\t" + "%p='PM'\t" + "%R='23:31'\t" + "%T='23:31:30.123'\t" + "%r='11:31:30 PM'\t" + "%X='23:31:30'\t" + "%EX='23:31:30'\t" + "\n"), + fmt, + std::chrono::hh_mm_ss(23h + 31min + 30s + 123ms)); + + check(SV("-%H='03'\t" + "%OH='03'\t" + "%I='03'\t" + "%OI='03'\t" + "%M='02'\t" + "%OM='02'\t" + "%S='01.123456789012'\t" + "%OS='01.123456789012'\t" + "%p='AM'\t" + "%R='03:02'\t" + "%T='03:02:01.123456789012'\t" + "%r='03:02:01 AM'\t" + "%X='03:02:01'\t" + "%EX='03:02:01'\t" + "\n"), + fmt, + std::chrono::hh_mm_ss(-(3h + 2min + 1s + std::chrono::duration(123456789012)))); + + // The number of fractional seconds is 0 according to the Standard + // TODO FMT Determine what to do. + check(SV("%H='01'\t" + "%OH='01'\t" + "%I='01'\t" + "%OI='01'\t" + "%M='01'\t" + "%OM='01'\t" + "%S='01'\t" + "%OS='01'\t" + "%p='AM'\t" + "%R='01:01'\t" + "%T='01:01:01'\t" + "%r='01:01:01 AM'\t" + "%X='01:01:01'\t" + "%EX='01:01:01'\t" + "\n"), + fmt, + std::chrono::hh_mm_ss(std::chrono::duration(3661.123456))); + + // Use the global locale (fr_FR) + check(SV("%H='00'\t" + "%OH='00'\t" + "%I='12'\t" + "%OI='12'\t" + "%M='00'\t" + "%OM='00'\t" + "%S='00'\t" + "%OS='00'\t" +#if defined(_AIX) + "%p='AM'\t" +#else + "%p=''\t" +#endif + "%R='00:00'\t" + "%T='00:00:00'\t" +#ifdef _WIN32 + "%r='00:00:00'\t" +#elif defined(_AIX) + "%r='12:00:00 AM'\t" +#elif defined(__APPLE__) + "%r=''\t" +#else + "%r='12:00:00 '\t" +#endif + "%X='00:00:00'\t" + "%EX='00:00:00'\t" + "\n"), + lfmt, + std::chrono::hh_mm_ss(0s)); + + check(SV("%H='23'\t" + "%OH='23'\t" + "%I='11'\t" + "%OI='11'\t" + "%M='31'\t" + "%OM='31'\t" + "%S='30,123'\t" + "%OS='30,123'\t" +#if defined(_AIX) + "%p='PM'\t" +#else + "%p=''\t" +#endif + "%R='23:31'\t" + "%T='23:31:30,123'\t" +#ifdef _WIN32 + "%r='23:31:30'\t" +#elif defined(_AIX) + "%r='11:31:30 PM'\t" +#elif defined(__APPLE__) + "%r=''\t" +#else + "%r='11:31:30 '\t" +#endif + "%X='23:31:30'\t" + "%EX='23:31:30'\t" + "\n"), + lfmt, + std::chrono::hh_mm_ss(23h + 31min + 30s + 123ms)); + + check(SV("-%H='03'\t" + "%OH='03'\t" + "%I='03'\t" + "%OI='03'\t" + "%M='02'\t" + "%OM='02'\t" + "%S='01,123456789012'\t" + "%OS='01,123456789012'\t" +#if defined(_AIX) + "%p='AM'\t" +#else + "%p=''\t" +#endif + "%R='03:02'\t" + "%T='03:02:01,123456789012'\t" +#ifdef _WIN32 + "%r='03:02:01'\t" +#elif defined(_AIX) + "%r='03:02:01 AM'\t" +#elif defined(__APPLE__) + "%r=''\t" +#else + "%r='03:02:01 '\t" +#endif + "%X='03:02:01'\t" + "%EX='03:02:01'\t" + "\n"), + lfmt, + std::chrono::hh_mm_ss(-(3h + 2min + 1s + std::chrono::duration(123456789012)))); + + check(SV("%H='01'\t" + "%OH='01'\t" + "%I='01'\t" + "%OI='01'\t" + "%M='01'\t" + "%OM='01'\t" + "%S='01'\t" + "%OS='01'\t" +#if defined(_AIX) + "%p='AM'\t" +#else + "%p=''\t" +#endif + "%R='01:01'\t" + "%T='01:01:01'\t" +#ifdef _WIN32 + "%r='01:01:01'\t" +#elif defined(_AIX) + "%r='01:01:01 AM'\t" +#elif defined(__APPLE__) + "%r=''\t" +#else + "%r='01:01:01 '\t" +#endif + "%X='01:01:01'\t" + "%EX='01:01:01'\t" + "\n"), + lfmt, + std::chrono::hh_mm_ss(std::chrono::duration(3661.123456))); + + // 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 + "\n"), + lfmt, + std::chrono::hh_mm_ss(0s)); + + check(loc, + SV("%H='23'\t" + "%OH='23'\t" + "%I='11'\t" + "%OI='11'\t" + "%M='31'\t" + "%OM='31'\t" + "%S='30.123'\t" + "%OS='30.123'\t" +# if defined(__APPLE__) + "%p='PM'\t" +# else + "%p='午後'\t" +# endif + "%R='23:31'\t" + "%T='23:31:30.123'\t" +# if defined(__APPLE__) + "%r='11:31:30 PM'\t" + "%X='23時31分30秒'\t" + "%EX='23時31分30秒'\t" +# else + "%r='午後11:31:30'\t" + "%X='23:31:30'\t" + "%EX='23:31:30'\t" +# endif + "\n"), + lfmt, + std::chrono::hh_mm_ss(23h + 31min + 30s + 123ms)); + + check(loc, + SV("-%H='03'\t" + "%OH='03'\t" + "%I='03'\t" + "%OI='03'\t" + "%M='02'\t" + "%OM='02'\t" + "%S='01.123456789012'\t" + "%OS='01.123456789012'\t" +# if defined(__APPLE__) + "%p='AM'\t" +# else + "%p='午前'\t" +# endif + "%R='03:02'\t" + "%T='03:02:01.123456789012'\t" +# if defined(__APPLE__) + "%r='03:02:01 AM'\t" + "%X='03時02分01秒'\t" + "%EX='03時02分01秒'\t" +# else + "%r='午前03:02:01'\t" + "%X='03:02:01'\t" + "%EX='03:02:01'\t" +# endif + "\n"), + lfmt, + std::chrono::hh_mm_ss(-(3h + 2min + 1s + std::chrono::duration(123456789012)))); + + check(loc, + SV("%H='01'\t" + "%OH='01'\t" + "%I='01'\t" + "%OI='01'\t" + "%M='01'\t" + "%OM='01'\t" + "%S='01'\t" + "%OS='01'\t" +# if defined(__APPLE__) + "%p='AM'\t" +# else + "%p='午前'\t" +# endif + "%R='01:01'\t" + "%T='01:01:01'\t" +# if defined(__APPLE__) + "%r='01:01:01 AM'\t" + "%X='01時01分01秒'\t" + "%EX='01時01分01秒'\t" +# else + "%r='午前01:01:01'\t" + "%X='01:01:01'\t" + "%EX='01:01:01'\t" +# endif + "\n"), + lfmt, + std::chrono::hh_mm_ss(std::chrono::duration(3661.123456))); +#else // defined(__APPLE__) || defined(_AIX) + check(loc, + SV("%H='00'\t" + "%OH='〇'\t" + "%I='12'\t" + "%OI='十二'\t" + "%M='00'\t" + "%OM='〇'\t" + "%S='00'\t" + "%OS='〇'\t" + "%p='午前'\t" + "%R='00:00'\t" + "%T='00:00:00'\t" + "%r='午前12時00分00秒'\t" + "%X='00時00分00秒'\t" + "%EX='00時00分00秒'\t" + "\n"), + lfmt, + std::chrono::hh_mm_ss(0s)); + + // TODO FMT What should fractions be in alternate display mode? + check(loc, + SV("%H='23'\t" + "%OH='二十三'\t" + "%I='11'\t" + "%OI='十一'\t" + "%M='31'\t" + "%OM='三十一'\t" + "%S='30.123'\t" + "%OS='三十.123'\t" + "%p='午後'\t" + "%R='23:31'\t" + "%T='23:31:30.123'\t" + "%r='午後11時31分30秒'\t" + "%X='23時31分30秒'\t" + "%EX='23時31分30秒'\t" + "\n"), + lfmt, + std::chrono::hh_mm_ss(23h + 31min + 30s + 123ms)); + + check(loc, + SV("-%H='03'\t" + "%OH='三'\t" + "%I='03'\t" + "%OI='三'\t" + "%M='02'\t" + "%OM='二'\t" + "%S='01.123456789012'\t" + "%OS='一.123456789012'\t" + "%p='午前'\t" + "%R='03:02'\t" + "%T='03:02:01.123456789012'\t" + "%r='午前03時02分01秒'\t" + "%X='03時02分01秒'\t" + "%EX='03時02分01秒'\t" + "\n"), + lfmt, + std::chrono::hh_mm_ss(-(3h + 2min + 1s + std::chrono::duration(123456789012)))); + + check(loc, + SV("%H='01'\t" + "%OH='一'\t" + "%I='01'\t" + "%OI='一'\t" + "%M='01'\t" + "%OM='一'\t" + "%S='01'\t" + "%OS='一'\t" + "%p='午前'\t" + "%R='01:01'\t" + "%T='01:01:01'\t" + "%r='午前01時01分01秒'\t" + "%X='01時01分01秒'\t" + "%EX='01時01分01秒'\t" + "\n"), + lfmt, + std::chrono::hh_mm_ss(std::chrono::duration(3661.123456))); +#endif // defined(__APPLE__) || defined(_AIX) + + std::locale::global(std::locale::classic()); +} + +template +static void test_invalid_values() { + using namespace std::literals::chrono_literals; + + // This looks odd, however the 24 hours is not valid for a 24 hour clock. + // TODO FMT discuss what the "proper" behaviour is. + check_exception("formatting a hour needs a valid value", SV("{:%H"), std::chrono::hh_mm_ss{24h}); + check_exception("formatting a hour needs a valid value", SV("{:%OH"), std::chrono::hh_mm_ss{24h}); + check_exception("formatting a hour needs a valid value", SV("{:%I"), std::chrono::hh_mm_ss{24h}); + check_exception("formatting a hour needs a valid value", SV("{:%OI"), std::chrono::hh_mm_ss{24h}); + check(SV("00"), SV("{:%M}"), std::chrono::hh_mm_ss{24h}); + check(SV("00"), SV("{:%OM}"), std::chrono::hh_mm_ss{24h}); + check(SV("00"), SV("{:%S}"), std::chrono::hh_mm_ss{24h}); + check(SV("00"), SV("{:%OS}"), std::chrono::hh_mm_ss{24h}); + check_exception("formatting a hour needs a valid value", SV("{:%p"), std::chrono::hh_mm_ss{24h}); + check_exception("formatting a hour needs a valid value", SV("{:%R"), std::chrono::hh_mm_ss{24h}); + check_exception("formatting a hour needs a valid value", SV("{:%T"), std::chrono::hh_mm_ss{24h}); + check_exception("formatting a hour needs a valid value", SV("{:%r"), std::chrono::hh_mm_ss{24h}); + check_exception("formatting a hour needs a valid value", SV("{:%X"), std::chrono::hh_mm_ss{24h}); + check_exception("formatting a hour needs a valid value", SV("{:%EX"), std::chrono::hh_mm_ss{24h}); +} + +template +static void test() { + using namespace std::literals::chrono_literals; + + test_no_chrono_specs(); + test_valid_values(); + test_invalid_values(); + check_invalid_types( + {SV("H"), + SV("I"), + SV("M"), + SV("S"), + SV("p"), + SV("r"), + SV("R"), + SV("T"), + SV("X"), + SV("OH"), + SV("OI"), + SV("OM"), + SV("OS"), + SV("EX")}, + std::chrono::hh_mm_ss{0ms}); + + check_exception("Expected '%' or '}' in the chrono format-string", SV("{:A"), std::chrono::hh_mm_ss{0ms}); + check_exception("The chrono-specs contains a '{'", SV("{:%%{"), std::chrono::hh_mm_ss{0ms}); + check_exception( + "End of input while parsing the modifier chrono conversion-spec", SV("{:%"), std::chrono::hh_mm_ss{0ms}); + check_exception("End of input while parsing the modifier E", SV("{:%E"), std::chrono::hh_mm_ss{0ms}); + check_exception("End of input while parsing the modifier O", SV("{:%O"), std::chrono::hh_mm_ss{0ms}); + + check_exception("Expected '%' or '}' in the chrono format-string", SV("{:.3}"), std::chrono::hh_mm_ss{0ms}); +} + +int main(int, char**) { + test(); + +#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 @@ -160,7 +160,7 @@ assert_is_formattable(); assert_is_formattable(); - assert_is_not_formattable, CharT>(); + assert_is_formattable, CharT>(); //assert_is_formattable(); //assert_is_formattable();