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

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

Depends on D137022

Reviewed By: ldionne, #libc

Differential Revision: https://reviews.llvm.org/D139771
This commit is contained in:
Mark de Wever
2022-03-20 13:40:02 +01:00
parent 5205c7126b
commit 7f5d130a42
16 changed files with 924 additions and 9 deletions

View File

@@ -23,7 +23,7 @@ Section,Description,Dependencies,Assignee,Status,First released version
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::year_month_day_last``",,Mark de Wever,|Complete|, Clang 16
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::year_month_weekday``",,Mark de Wever,|Complete|, Clang 16
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::year_month_weekday_last``",,Mark de Wever,|Complete|, Clang 16
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::hh_mm_ss<duration<Rep, Period>>``",,Mark de Wever,|In Progress|,
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::hh_mm_ss<duration<Rep, Period>>``",,Mark de Wever,|Complete|, Clang 17
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::sys_info``",A ``<chrono>`` implementation,Mark de Wever,,
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::local_info``",A ``<chrono>`` implementation,Mark de Wever,,
`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::zoned_time<Duration, TimeZonePtr>``",A ``<chrono>`` implementation,Mark de Wever,,
1 Section Description Dependencies Assignee Status First released version
23 `[time.syn] <https://wg21.link/time.syn>`_ Formatter ``chrono::year_month_day_last`` Mark de Wever |Complete| Clang 16
24 `[time.syn] <https://wg21.link/time.syn>`_ Formatter ``chrono::year_month_weekday`` Mark de Wever |Complete| Clang 16
25 `[time.syn] <https://wg21.link/time.syn>`_ Formatter ``chrono::year_month_weekday_last`` Mark de Wever |Complete| Clang 16
26 `[time.syn] <https://wg21.link/time.syn>`_ Formatter ``chrono::hh_mm_ss<duration<Rep, Period>>`` Mark de Wever |In Progress| |Complete| Clang 17
27 `[time.syn] <https://wg21.link/time.syn>`_ Formatter ``chrono::sys_info`` A ``<chrono>`` implementation Mark de Wever
28 `[time.syn] <https://wg21.link/time.syn>`_ Formatter ``chrono::local_info`` A ``<chrono>`` implementation Mark de Wever
29 `[time.syn] <https://wg21.link/time.syn>`_ Formatter ``chrono::zoned_time<Duration, TimeZonePtr>`` A ``<chrono>`` implementation Mark de Wever

View File

@@ -216,6 +216,7 @@ set(files
__charconv/to_chars_base_10.h
__charconv/to_chars_result.h
__chrono/calendar.h
__chrono/concepts.h
__chrono/convert_to_timespec.h
__chrono/convert_to_tm.h
__chrono/day.h

View File

@@ -0,0 +1,32 @@
// -*- C++ -*-
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef _LIBCPP___CHRONO_CONCEPTS_H
#define _LIBCPP___CHRONO_CONCEPTS_H
#include <__chrono/hh_mm_ss.h>
#include <__config>
#include <__type_traits/is_specialization.h>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
#endif
_LIBCPP_BEGIN_NAMESPACE_STD
#if _LIBCPP_STD_VER > 17
template <class _Tp>
concept __is_hh_mm_ss = __is_specialization_v<_Tp, chrono::hh_mm_ss>;
#endif // _LIBCPP_STD_VER > 17
_LIBCPP_END_NAMESPACE_STD
#endif // _LIBCPP___CHRONO_CONCEPTS_H

View File

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

View File

@@ -11,6 +11,7 @@
#define _LIBCPP___CHRONO_FORMATTER_H
#include <__chrono/calendar.h>
#include <__chrono/concepts.h>
#include <__chrono/convert_to_tm.h>
#include <__chrono/day.h>
#include <__chrono/duration.h>
@@ -75,13 +76,15 @@ namespace __formatter {
// For tiny ratios it's not possible to convert a duration to a hh_mm_ss. This
// fails compile-time due to the limited precision of the ratio (64-bit is too
// small). Therefore a duration uses its own conversion.
template <class _CharT, class _Tp>
requires(chrono::__is_duration<_Tp>::value)
_LIBCPP_HIDE_FROM_ABI void __format_sub_seconds(const _Tp& __value, basic_stringstream<_CharT>& __sstr) {
template <class _CharT, class _Rep, class _Period>
_LIBCPP_HIDE_FROM_ABI void
__format_sub_seconds(const chrono::duration<_Rep, _Period>& __value, basic_stringstream<_CharT>& __sstr) {
__sstr << std::use_facet<numpunct<_CharT>>(__sstr.getloc()).decimal_point();
using __duration = chrono::duration<_Rep, _Period>;
auto __fraction = __value - chrono::duration_cast<chrono::seconds>(__value);
if constexpr (chrono::treat_as_floating_point_v<typename _Tp::rep>)
if constexpr (chrono::treat_as_floating_point_v<_Rep>)
// When the floating-point value has digits itself they are ignored based
// on the wording in [tab:time.format.spec]
// If the precision of the input cannot be exactly represented with
@@ -97,18 +100,36 @@ _LIBCPP_HIDE_FROM_ABI void __format_sub_seconds(const _Tp& __value, basic_string
std::format_to(std::ostreambuf_iterator<_CharT>{__sstr},
_LIBCPP_STATICALLY_WIDEN(_CharT, "{:0{}.0f}"),
__fraction.count(),
chrono::hh_mm_ss<_Tp>::fractional_width);
chrono::hh_mm_ss<__duration>::fractional_width);
else
std::format_to(std::ostreambuf_iterator<_CharT>{__sstr},
_LIBCPP_STATICALLY_WIDEN(_CharT, "{:0{}}"),
__fraction.count(),
chrono::hh_mm_ss<_Tp>::fractional_width);
chrono::hh_mm_ss<__duration>::fractional_width);
}
template <class _CharT, class _Duration>
_LIBCPP_HIDE_FROM_ABI void
__format_sub_seconds(const chrono::hh_mm_ss<_Duration>& __value, basic_stringstream<_CharT>& __sstr) {
__sstr << std::use_facet<numpunct<_CharT>>(__sstr.getloc()).decimal_point();
if constexpr (chrono::treat_as_floating_point_v<typename _Duration::rep>)
std::format_to(std::ostreambuf_iterator<_CharT>{__sstr},
_LIBCPP_STATICALLY_WIDEN(_CharT, "{:0{}.0f}"),
__value.subseconds().count(),
__value.fractional_width);
else
std::format_to(std::ostreambuf_iterator<_CharT>{__sstr},
_LIBCPP_STATICALLY_WIDEN(_CharT, "{:0{}}"),
__value.subseconds().count(),
__value.fractional_width);
}
template <class _Tp>
consteval bool __use_fraction() {
if constexpr (chrono::__is_duration<_Tp>::value)
return chrono::hh_mm_ss<_Tp>::fractional_width;
else if constexpr (__is_hh_mm_ss<_Tp>)
return _Tp::fractional_width;
else
return false;
}
@@ -322,6 +343,8 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool __weekday_ok(const _Tp& __value) {
return __value.weekday().ok();
else if constexpr (same_as<_Tp, chrono::year_month_weekday_last>)
return __value.weekday().ok();
else if constexpr (__is_hh_mm_ss<_Tp>)
return true;
else
static_assert(sizeof(_Tp) == 0, "Add the missing type specialization");
}
@@ -358,6 +381,8 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool __weekday_name_ok(const _Tp& __value) {
return __value.weekday().ok();
else if constexpr (same_as<_Tp, chrono::year_month_weekday_last>)
return __value.weekday().ok();
else if constexpr (__is_hh_mm_ss<_Tp>)
return true;
else
static_assert(sizeof(_Tp) == 0, "Add the missing type specialization");
}
@@ -394,6 +419,8 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool __date_ok(const _Tp& __value) {
return __value.ok();
else if constexpr (same_as<_Tp, chrono::year_month_weekday_last>)
return __value.ok();
else if constexpr (__is_hh_mm_ss<_Tp>)
return true;
else
static_assert(sizeof(_Tp) == 0, "Add the missing type specialization");
}
@@ -430,6 +457,8 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool __month_name_ok(const _Tp& __value) {
return __value.month().ok();
else if constexpr (same_as<_Tp, chrono::year_month_weekday_last>)
return __value.month().ok();
else if constexpr (__is_hh_mm_ss<_Tp>)
return true;
else
static_assert(sizeof(_Tp) == 0, "Add the missing type specialization");
}
@@ -478,6 +507,29 @@ __format_chrono(const _Tp& __value,
if (__specs.__chrono_.__month_name_ && !__formatter::__month_name_ok(__value))
std::__throw_format_error("formatting a month name from an invalid month number");
if constexpr (__is_hh_mm_ss<_Tp>) {
// Note this is a pedantic intepretation of the Standard. A hh_mm_ss
// is no longer a time_of_day and can store an arbitrary number of
// hours. A number of hours in a 12 or 24 hour clock can't represent
// 24 hours or more. The functions std::chrono::make12 and
// std::chrono::make24 reaffirm this view point.
//
// Interestingly this will be the only output stream function that
// throws.
//
// TODO FMT The wording probably needs to be adapted to
// - The displayed hours is hh_mm_ss.hours() % 24
// - It should probably allow %j in the same fashion as duration.
// - The stream formatter should change its output when hours >= 24
// - Write it as not valid,
// - or write the number of days.
if (__specs.__chrono_.__hour_ && __value.hours().count() > 23)
std::__throw_format_error("formatting a hour needs a valid value");
if (__value.is_negative())
__sstr << _CharT('-');
}
__formatter::__format_chrono_using_chrono_specs(__value, __sstr, __chrono_specs);
}
}
@@ -709,6 +761,16 @@ public:
}
};
template <class _Duration, __fmt_char_type _CharT>
struct formatter<chrono::hh_mm_ss<_Duration>, _CharT> : public __formatter_chrono<_CharT> {
public:
using _Base = __formatter_chrono<_CharT>;
_LIBCPP_HIDE_FROM_ABI constexpr auto parse(basic_format_parse_context<_CharT>& __parse_ctx)
-> decltype(__parse_ctx.begin()) {
return _Base::__parse(__parse_ctx, __format_spec::__fields_chrono, __format_spec::__flags::__time);
}
};
#endif // if _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT)
_LIBCPP_END_NAMESPACE_STD

View File

@@ -85,6 +85,7 @@ private:
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); }

View File

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

View File

@@ -214,6 +214,7 @@ private:
case _CharT('p'): // TODO FMT does the formater require an hour or a time?
case _CharT('H'):
case _CharT('I'):
__parser_.__hour_ = true;
__validate_hour(__flags);
break;
@@ -221,6 +222,7 @@ private:
case _CharT('R'):
case _CharT('T'):
case _CharT('X'):
__parser_.__hour_ = true;
__format_spec::__validate_time(__flags);
break;
@@ -313,6 +315,7 @@ private:
switch (*__begin) {
case _CharT('X'):
__parser_.__hour_ = true;
__format_spec::__validate_time(__flags);
break;
@@ -361,6 +364,7 @@ private:
case _CharT('I'):
case _CharT('H'):
__parser_.__hour_ = true;
__format_spec::__validate_hour(__flags);
break;

View File

@@ -199,6 +199,7 @@ struct __std {
struct __chrono {
__alignment __alignment_ : 3;
bool __locale_specific_form_ : 1;
bool __hour_ : 1;
bool __weekday_name_ : 1;
bool __weekday_ : 1;
bool __day_of_year_ : 1;
@@ -329,6 +330,7 @@ public:
.__chrono_ =
__chrono{.__alignment_ = __alignment_,
.__locale_specific_form_ = __locale_specific_form_,
.__hour_ = __hour_,
.__weekday_name_ = __weekday_name_,
.__weekday_ = __weekday_,
.__day_of_year_ = __day_of_year_,
@@ -348,6 +350,8 @@ public:
// These flags are only used for formatting chrono. Since the struct has
// padding space left it's added to this structure.
bool __hour_ : 1 {false};
bool __weekday_name_ : 1 {false};
bool __weekday_ : 1 {false};
@@ -356,7 +360,7 @@ public:
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.

View File

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

View File

@@ -674,6 +674,7 @@ module std [system] {
module __chrono {
module calendar { private header "__chrono/calendar.h" }
module concepts { private header "__chrono/concepts.h" }
module convert_to_timespec { private header "__chrono/convert_to_timespec.h" }
module convert_to_tm { private header "__chrono/convert_to_tm.h" }
module day { private header "__chrono/day.h" }

View File

@@ -250,6 +250,7 @@ END-SCRIPT
#include <__charconv/to_chars_base_10.h> // expected-error@*:* {{use of private header from outside its module: '__charconv/to_chars_base_10.h'}}
#include <__charconv/to_chars_result.h> // expected-error@*:* {{use of private header from outside its module: '__charconv/to_chars_result.h'}}
#include <__chrono/calendar.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/calendar.h'}}
#include <__chrono/concepts.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/concepts.h'}}
#include <__chrono/convert_to_timespec.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/convert_to_timespec.h'}}
#include <__chrono/convert_to_tm.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/convert_to_tm.h'}}
#include <__chrono/day.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/day.h'}}

View File

@@ -0,0 +1,62 @@
//===----------------------------------------------------------------------===//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: libcpp-has-no-incomplete-format
// <chrono>
// template <class _Tm, class _ChronoT>
// _LIBCPP_HIDE_FROM_ABI _Tm __convert_to_tm(const _ChronoT& __value)
// Most of the code is tested indirectly in the chrono formatters. This only
// tests the hour overflow.
#include <chrono>
#include <cassert>
#include <format>
#include <string_view>
#include "test_macros.h"
// libc++ uses a long as representation in std::chrono::hours.
// std::tm uses an int for its integral members. The overflow in the hour
// conversion can only occur on platforms where sizeof(long) > sizeof(int).
// Instead emulate this error by using a "tm" with shorts.
// (The function is already templated to this is quite easy to do,)
struct minimal_short_tm {
short tm_sec;
short tm_min;
short tm_hour;
const char* tm_zone;
};
int main(int, char**) {
{ // Test with the maximum number of hours that fit in a short.
std::chrono::hh_mm_ss time{std::chrono::hours{32767}};
minimal_short_tm result = std::__convert_to_tm<minimal_short_tm>(time);
assert(result.tm_sec == 0);
assert(result.tm_min == 0);
assert(result.tm_hour == 32767);
}
#ifndef TEST_HAS_NO_EXCEPTIONS
{ // Test above the maximum number of hours that fit in a short.
std::chrono::hh_mm_ss time{std::chrono::hours{32768}};
try {
TEST_IGNORE_NODISCARD std::__convert_to_tm<minimal_short_tm>(time);
assert(false);
} catch ([[maybe_unused]] const std::format_error& e) {
LIBCPP_ASSERT(e.what() == std::string_view("Formatting hh_mm_ss, encountered an hour overflow"));
return 0;
}
assert(false);
}
#endif // TEST_HAS_NO_EXCEPTIONS
return 0;
}

View File

@@ -0,0 +1,160 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: no-localization
// UNSUPPORTED: libcpp-has-no-incomplete-format
// TODO FMT Evaluate gcc-12 status
// UNSUPPORTED: gcc-12
// TODO FMT Investigate Windows issues.
// UNSUPPORTED: msvc, target={{.+}}-windows-gnu
// REQUIRES: locale.fr_FR.UTF-8
// REQUIRES: locale.ja_JP.UTF-8
// <chrono>
// class hh_mm_ss;
// template<class charT, class traits, class Duration>
// basic_ostream<charT, traits>&
// operator<<(basic_ostream<charT, traits>& os, const hh_mm_ss<Duration>& hms);
#include <chrono>
#include <cassert>
#include <sstream>
#include "make_string.h"
#include "platform_support.h" // locale name macros
#include "test_macros.h"
#define SV(S) MAKE_STRING_VIEW(CharT, S)
template <class CharT, class Duration>
static std::basic_string<CharT> stream_c_locale(std::chrono::hh_mm_ss<Duration> hms) {
std::basic_stringstream<CharT> sstr;
sstr << hms;
return sstr.str();
}
template <class CharT, class Duration>
static std::basic_string<CharT> stream_fr_FR_locale(std::chrono::hh_mm_ss<Duration> hms) {
std::basic_stringstream<CharT> sstr;
const std::locale locale(LOCALE_fr_FR_UTF_8);
sstr.imbue(locale);
sstr << hms;
return sstr.str();
}
template <class CharT, class Duration>
static std::basic_string<CharT> stream_ja_JP_locale(std::chrono::hh_mm_ss<Duration> hms) {
std::basic_stringstream<CharT> sstr;
const std::locale locale(LOCALE_ja_JP_UTF_8);
sstr.imbue(locale);
sstr << hms;
return sstr.str();
}
template <class CharT>
static void test() {
// Note std::atto can't be tested since the ratio conversion from std::atto
// std::chrono::seconds to std::chrono::hours overflows when intmax_t is a
// 64-bit type. This is a limitiation in the constructor of
// std::chrono::hh_mm_ss.
// C locale - integral power of 10 ratios
assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::femto>{1'234'567'890}}) ==
SV("00:00:00.000001234567890"));
assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::pico>{1'234'567'890}}) ==
SV("00:00:00.001234567890"));
assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::nano>{1'234'567'890}}) ==
SV("00:00:01.234567890"));
assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::micro>{1'234'567}}) ==
SV("00:00:01.234567"));
assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::milli>{123'456}}) ==
SV("00:02:03.456"));
assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::centi>{12'345}}) ==
SV("00:02:03.45"));
assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::deci>{1'234}}) ==
SV("00:02:03.4"));
assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t>{123}}) == SV("00:02:03"));
assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::deca>{-366}}) ==
SV("-01:01:00"));
assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::hecto>{-72}}) ==
SV("-02:00:00"));
assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::kilo>{-86}}) ==
SV("-23:53:20"));
// Starting at mega it will pass one day
// fr_FR locale - integral power of not 10 ratios
assert(stream_fr_FR_locale<CharT>(std::chrono::hh_mm_ss{
std::chrono::duration<intmax_t, std::ratio<1, 5'000'000>>{5'000}}) == SV("00:00:00,0010000"));
assert(stream_fr_FR_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::ratio<1, 8'000>>{3}}) ==
SV("00:00:00,000375"));
assert(stream_fr_FR_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::ratio<1, 4'000>>{1}}) ==
SV("00:00:00,00025"));
assert(stream_fr_FR_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::ratio<1, 5'000>>{5}}) ==
SV("00:00:00,0010"));
assert(stream_fr_FR_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::ratio<1, 8>>{-4}}) ==
SV("-00:00:00,500"));
assert(stream_fr_FR_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::ratio<1, 4>>{-8}}) ==
SV("-00:00:02,00"));
assert(stream_fr_FR_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::ratio<1, 5>>{-5}}) ==
SV("-00:00:01,0"));
// TODO FMT Note there's no wording on the rounding
assert(stream_fr_FR_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::ratio<1, 9>>{5}}) ==
SV("00:00:00,555555"));
assert(stream_fr_FR_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::ratio<1, 7>>{7}}) ==
SV("00:00:01,000000"));
assert(stream_fr_FR_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::ratio<1, 6>>{1}}) ==
SV("00:00:00,166666"));
assert(stream_fr_FR_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<intmax_t, std::ratio<1, 3>>{2}}) ==
SV("00:00:00,666666"));
// ja_JP locale - floating points
assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{
std::chrono::duration<long double, std::femto>{1'234'567'890.123}}) == SV("00:00:00.000001234567890"));
assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{
std::chrono::duration<long double, std::pico>{1'234'567'890.123}}) == SV("00:00:00.001234567890"));
assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{
std::chrono::duration<long double, std::nano>{1'234'567'890.123}}) == SV("00:00:01.234567890"));
assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<double, std::micro>{1'234'567.123}}) ==
SV("00:00:01.234567"));
assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<double, std::milli>{123'456.123}}) ==
SV("00:02:03.456"));
assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<double, std::centi>{12'345.123}}) ==
SV("00:02:03.45"));
assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<float, std::deci>{1'234.123}}) ==
SV("00:02:03.4"));
assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<float>{123.123}}) == SV("00:02:03"));
assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<double, std::deca>{-366.5}}) ==
SV("-01:01:05"));
assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<double, std::hecto>{-72.64}}) ==
SV("-02:01:04"));
assert(stream_c_locale<CharT>(std::chrono::hh_mm_ss{std::chrono::duration<double, std::kilo>{-86}}) ==
SV("-23:53:20"));
}
int main(int, char**) {
test<char>();
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
test<wchar_t>();
#endif
return 0;
}

View File

@@ -0,0 +1,556 @@
//===----------------------------------------------------------------------===//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: no-localization
// UNSUPPORTED: libcpp-has-no-incomplete-format
// TODO FMT Evaluate gcc-12 status
// UNSUPPORTED: gcc-12
// TODO FMT Investigate Windows issues.
// UNSUPPORTED: msvc, target={{.+}}-windows-gnu
// REQUIRES: locale.fr_FR.UTF-8
// REQUIRES: locale.ja_JP.UTF-8
// <chrono>
// template<class Rep, class Period, class charT>
// struct formatter<chrono::hh_mm_ss<duration<Rep, Period>>, charT>;
#include <chrono>
#include <format>
#include <cassert>
#include <concepts>
#include <locale>
#include <iostream>
#include <type_traits>
#include "formatter_tests.h"
#include "make_string.h"
#include "platform_support.h" // locale name macros
#include "string_literal.h"
#include "test_macros.h"
template <class CharT>
static void test_no_chrono_specs() {
using namespace std::literals::chrono_literals;
std::locale::global(std::locale(LOCALE_fr_FR_UTF_8));
// Non localized output
check(SV("00:00:00.000"), SV("{}"), std::chrono::hh_mm_ss{0ms});
check(SV("*00:00:00.000*"), SV("{:*^14}"), std::chrono::hh_mm_ss{0ms});
check(SV("*00:00:00.000"), SV("{:*>13}"), std::chrono::hh_mm_ss{0ms});
std::locale::global(std::locale::classic());
}
template <class CharT>
static void test_valid_values() {
using namespace std::literals::chrono_literals;
constexpr std::basic_string_view<CharT> fmt = SV(
"{:"
"%%H='%H'%t"
"%%OH='%OH'%t"
"%%I='%I'%t"
"%%OI='%OI'%t"
"%%M='%M'%t"
"%%OM='%OM'%t"
"%%S='%S'%t"
"%%OS='%OS'%t"
"%%p='%p'%t"
"%%R='%R'%t"
"%%T='%T'%t"
"%%r='%r'%t"
"%%X='%X'%t"
"%%EX='%EX'%t"
"%n}");
constexpr std::basic_string_view<CharT> lfmt = SV(
"{:L"
"%%H='%H'%t"
"%%OH='%OH'%t"
"%%I='%I'%t"
"%%OI='%OI'%t"
"%%M='%M'%t"
"%%OM='%OM'%t"
"%%S='%S'%t"
"%%OS='%OS'%t"
"%%p='%p'%t"
"%%R='%R'%t"
"%%T='%T'%t"
"%%r='%r'%t"
"%%X='%X'%t"
"%%EX='%EX'%t"
"%n}");
const std::locale loc(LOCALE_ja_JP_UTF_8);
std::locale::global(std::locale(LOCALE_fr_FR_UTF_8));
// Non localized output using C-locale
check(SV("%H='00'\t"
"%OH='00'\t"
"%I='12'\t"
"%OI='12'\t"
"%M='00'\t"
"%OM='00'\t"
"%S='00'\t"
"%OS='00'\t"
"%p='AM'\t"
"%R='00:00'\t"
"%T='00:00:00'\t"
"%r='12:00:00 AM'\t"
"%X='00:00:00'\t"
"%EX='00:00:00'\t"
"\n"),
fmt,
std::chrono::hh_mm_ss(0s));
check(SV("%H='23'\t"
"%OH='23'\t"
"%I='11'\t"
"%OI='11'\t"
"%M='31'\t"
"%OM='31'\t"
"%S='30.123'\t"
"%OS='30.123'\t"
"%p='PM'\t"
"%R='23:31'\t"
"%T='23:31:30.123'\t"
"%r='11:31:30 PM'\t"
"%X='23:31:30'\t"
"%EX='23:31:30'\t"
"\n"),
fmt,
std::chrono::hh_mm_ss(23h + 31min + 30s + 123ms));
check(SV("-%H='03'\t"
"%OH='03'\t"
"%I='03'\t"
"%OI='03'\t"
"%M='02'\t"
"%OM='02'\t"
"%S='01.123456789012'\t"
"%OS='01.123456789012'\t"
"%p='AM'\t"
"%R='03:02'\t"
"%T='03:02:01.123456789012'\t"
"%r='03:02:01 AM'\t"
"%X='03:02:01'\t"
"%EX='03:02:01'\t"
"\n"),
fmt,
std::chrono::hh_mm_ss(-(3h + 2min + 1s + std::chrono::duration<int64_t, std::pico>(123456789012))));
// The number of fractional seconds is 0 according to the Standard
// TODO FMT Determine what to do.
check(SV("%H='01'\t"
"%OH='01'\t"
"%I='01'\t"
"%OI='01'\t"
"%M='01'\t"
"%OM='01'\t"
"%S='01'\t"
"%OS='01'\t"
"%p='AM'\t"
"%R='01:01'\t"
"%T='01:01:01'\t"
"%r='01:01:01 AM'\t"
"%X='01:01:01'\t"
"%EX='01:01:01'\t"
"\n"),
fmt,
std::chrono::hh_mm_ss(std::chrono::duration<double>(3661.123456)));
// Use the global locale (fr_FR)
check(SV("%H='00'\t"
"%OH='00'\t"
"%I='12'\t"
"%OI='12'\t"
"%M='00'\t"
"%OM='00'\t"
"%S='00'\t"
"%OS='00'\t"
#if defined(_AIX)
"%p='AM'\t"
#else
"%p=''\t"
#endif
"%R='00:00'\t"
"%T='00:00:00'\t"
#ifdef _WIN32
"%r='00:00:00'\t"
#elif defined(_AIX)
"%r='12:00:00 AM'\t"
#elif defined(__APPLE__)
"%r=''\t"
#else
"%r='12:00:00 '\t"
#endif
"%X='00:00:00'\t"
"%EX='00:00:00'\t"
"\n"),
lfmt,
std::chrono::hh_mm_ss(0s));
check(SV("%H='23'\t"
"%OH='23'\t"
"%I='11'\t"
"%OI='11'\t"
"%M='31'\t"
"%OM='31'\t"
"%S='30,123'\t"
"%OS='30,123'\t"
#if defined(_AIX)
"%p='PM'\t"
#else
"%p=''\t"
#endif
"%R='23:31'\t"
"%T='23:31:30,123'\t"
#ifdef _WIN32
"%r='23:31:30'\t"
#elif defined(_AIX)
"%r='11:31:30 PM'\t"
#elif defined(__APPLE__)
"%r=''\t"
#else
"%r='11:31:30 '\t"
#endif
"%X='23:31:30'\t"
"%EX='23:31:30'\t"
"\n"),
lfmt,
std::chrono::hh_mm_ss(23h + 31min + 30s + 123ms));
check(SV("-%H='03'\t"
"%OH='03'\t"
"%I='03'\t"
"%OI='03'\t"
"%M='02'\t"
"%OM='02'\t"
"%S='01,123456789012'\t"
"%OS='01,123456789012'\t"
#if defined(_AIX)
"%p='AM'\t"
#else
"%p=''\t"
#endif
"%R='03:02'\t"
"%T='03:02:01,123456789012'\t"
#ifdef _WIN32
"%r='03:02:01'\t"
#elif defined(_AIX)
"%r='03:02:01 AM'\t"
#elif defined(__APPLE__)
"%r=''\t"
#else
"%r='03:02:01 '\t"
#endif
"%X='03:02:01'\t"
"%EX='03:02:01'\t"
"\n"),
lfmt,
std::chrono::hh_mm_ss(-(3h + 2min + 1s + std::chrono::duration<int64_t, std::pico>(123456789012))));
check(SV("%H='01'\t"
"%OH='01'\t"
"%I='01'\t"
"%OI='01'\t"
"%M='01'\t"
"%OM='01'\t"
"%S='01'\t"
"%OS='01'\t"
#if defined(_AIX)
"%p='AM'\t"
#else
"%p=''\t"
#endif
"%R='01:01'\t"
"%T='01:01:01'\t"
#ifdef _WIN32
"%r='01:01:01'\t"
#elif defined(_AIX)
"%r='01:01:01 AM'\t"
#elif defined(__APPLE__)
"%r=''\t"
#else
"%r='01:01:01 '\t"
#endif
"%X='01:01:01'\t"
"%EX='01:01:01'\t"
"\n"),
lfmt,
std::chrono::hh_mm_ss(std::chrono::duration<double>(3661.123456)));
// Use supplied locale (ja_JP). This locale has a 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<int64_t, std::pico>(123456789012))));
check(loc,
SV("%H='01'\t"
"%OH='01'\t"
"%I='01'\t"
"%OI='01'\t"
"%M='01'\t"
"%OM='01'\t"
"%S='01'\t"
"%OS='01'\t"
# if defined(__APPLE__)
"%p='AM'\t"
# else
"%p='午前'\t"
# endif
"%R='01:01'\t"
"%T='01:01:01'\t"
# if defined(__APPLE__)
"%r='01:01:01 AM'\t"
"%X='01時01分01秒'\t"
"%EX='01時01分01秒'\t"
# else
"%r='午前01:01:01'\t"
"%X='01:01:01'\t"
"%EX='01:01:01'\t"
# endif
"\n"),
lfmt,
std::chrono::hh_mm_ss(std::chrono::duration<double>(3661.123456)));
#else // defined(__APPLE__) || defined(_AIX)
check(loc,
SV("%H='00'\t"
"%OH=''\t"
"%I='12'\t"
"%OI='十二'\t"
"%M='00'\t"
"%OM=''\t"
"%S='00'\t"
"%OS=''\t"
"%p='午前'\t"
"%R='00:00'\t"
"%T='00:00:00'\t"
"%r='午前12時00分00秒'\t"
"%X='00時00分00秒'\t"
"%EX='00時00分00秒'\t"
"\n"),
lfmt,
std::chrono::hh_mm_ss(0s));
// TODO FMT What should fractions be in alternate display mode?
check(loc,
SV("%H='23'\t"
"%OH='二十三'\t"
"%I='11'\t"
"%OI='十一'\t"
"%M='31'\t"
"%OM='三十一'\t"
"%S='30.123'\t"
"%OS='三十.123'\t"
"%p='午後'\t"
"%R='23:31'\t"
"%T='23:31:30.123'\t"
"%r='午後11時31分30秒'\t"
"%X='23時31分30秒'\t"
"%EX='23時31分30秒'\t"
"\n"),
lfmt,
std::chrono::hh_mm_ss(23h + 31min + 30s + 123ms));
check(loc,
SV("-%H='03'\t"
"%OH='三'\t"
"%I='03'\t"
"%OI='三'\t"
"%M='02'\t"
"%OM='二'\t"
"%S='01.123456789012'\t"
"%OS='一.123456789012'\t"
"%p='午前'\t"
"%R='03:02'\t"
"%T='03:02:01.123456789012'\t"
"%r='午前03時02分01秒'\t"
"%X='03時02分01秒'\t"
"%EX='03時02分01秒'\t"
"\n"),
lfmt,
std::chrono::hh_mm_ss(-(3h + 2min + 1s + std::chrono::duration<int64_t, std::pico>(123456789012))));
check(loc,
SV("%H='01'\t"
"%OH='一'\t"
"%I='01'\t"
"%OI='一'\t"
"%M='01'\t"
"%OM='一'\t"
"%S='01'\t"
"%OS='一'\t"
"%p='午前'\t"
"%R='01:01'\t"
"%T='01:01:01'\t"
"%r='午前01時01分01秒'\t"
"%X='01時01分01秒'\t"
"%EX='01時01分01秒'\t"
"\n"),
lfmt,
std::chrono::hh_mm_ss(std::chrono::duration<double>(3661.123456)));
#endif // defined(__APPLE__) || defined(_AIX)
std::locale::global(std::locale::classic());
}
template <class CharT>
static void test_invalid_values() {
using namespace std::literals::chrono_literals;
// This looks odd, however the 24 hours is not valid for a 24 hour clock.
// TODO FMT discuss what the "proper" behaviour is.
check_exception("formatting a hour needs a valid value", SV("{:%H"), std::chrono::hh_mm_ss{24h});
check_exception("formatting a hour needs a valid value", SV("{:%OH"), std::chrono::hh_mm_ss{24h});
check_exception("formatting a hour needs a valid value", SV("{:%I"), std::chrono::hh_mm_ss{24h});
check_exception("formatting a hour needs a valid value", SV("{:%OI"), std::chrono::hh_mm_ss{24h});
check(SV("00"), SV("{:%M}"), std::chrono::hh_mm_ss{24h});
check(SV("00"), SV("{:%OM}"), std::chrono::hh_mm_ss{24h});
check(SV("00"), SV("{:%S}"), std::chrono::hh_mm_ss{24h});
check(SV("00"), SV("{:%OS}"), std::chrono::hh_mm_ss{24h});
check_exception("formatting a hour needs a valid value", SV("{:%p"), std::chrono::hh_mm_ss{24h});
check_exception("formatting a hour needs a valid value", SV("{:%R"), std::chrono::hh_mm_ss{24h});
check_exception("formatting a hour needs a valid value", SV("{:%T"), std::chrono::hh_mm_ss{24h});
check_exception("formatting a hour needs a valid value", SV("{:%r"), std::chrono::hh_mm_ss{24h});
check_exception("formatting a hour needs a valid value", SV("{:%X"), std::chrono::hh_mm_ss{24h});
check_exception("formatting a hour needs a valid value", SV("{:%EX"), std::chrono::hh_mm_ss{24h});
}
template <class CharT>
static void test() {
using namespace std::literals::chrono_literals;
test_no_chrono_specs<CharT>();
test_valid_values<CharT>();
test_invalid_values<CharT>();
check_invalid_types<CharT>(
{SV("H"),
SV("I"),
SV("M"),
SV("S"),
SV("p"),
SV("r"),
SV("R"),
SV("T"),
SV("X"),
SV("OH"),
SV("OI"),
SV("OM"),
SV("OS"),
SV("EX")},
std::chrono::hh_mm_ss{0ms});
check_exception("Expected '%' or '}' in the chrono format-string", SV("{:A"), std::chrono::hh_mm_ss{0ms});
check_exception("The chrono-specs contains a '{'", SV("{:%%{"), std::chrono::hh_mm_ss{0ms});
check_exception(
"End of input while parsing the modifier chrono conversion-spec", SV("{:%"), std::chrono::hh_mm_ss{0ms});
check_exception("End of input while parsing the modifier E", SV("{:%E"), std::chrono::hh_mm_ss{0ms});
check_exception("End of input while parsing the modifier O", SV("{:%O"), std::chrono::hh_mm_ss{0ms});
check_exception("Expected '%' or '}' in the chrono format-string", SV("{:.3}"), std::chrono::hh_mm_ss{0ms});
}
int main(int, char**) {
test<char>();
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
test<wchar_t>();
#endif
return 0;
}

View File

@@ -161,7 +161,7 @@ void test_P1361() {
assert_is_formattable<std::chrono::year_month_weekday, CharT>();
assert_is_formattable<std::chrono::year_month_weekday_last, CharT>();
assert_is_not_formattable<std::chrono::hh_mm_ss<std::chrono::microseconds>, CharT>();
assert_is_formattable<std::chrono::hh_mm_ss<std::chrono::microseconds>, CharT>();
//assert_is_formattable<std::chrono::sys_info, CharT>();
//assert_is_formattable<std::chrono::local_info, CharT>();