From 2118d1e80dcb72e764995932c119754f689dc6ea Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Thu, 15 Apr 2021 01:41:18 -0700 Subject: [PATCH 01/41] Squashed chronat changes. Co-authored-by: Elnar Dakeshov <55715127+eldakesh-ms@users.noreply.github.com> Co-authored-by: mnatsuhara <46756417+mnatsuhara@users.noreply.github.com> --- stl/inc/chrono | 521 +++++++++++++----- stl/inc/format | 8 +- .../test.cpp | 180 +++++- 3 files changed, 559 insertions(+), 150 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 94027aadf5..af97a9ede9 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -22,9 +22,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -32,6 +34,8 @@ #ifdef __cpp_lib_concepts #include #include +#include +#include #endif // defined(__cpp_lib_concepts) #endif // _HAS_CXX20 @@ -5177,184 +5181,434 @@ namespace chrono { return _CHRONO from_stream(_Is, _Tpi._Fmt.c_str(), _Tpi._Tp, _Tpi._Abbrev, _Tpi._Offset); } +#endif // _HAS_CXX20 +} // namespace chrono + +#if _HAS_CXX20 #ifdef __cpp_lib_concepts - // [time.format] + // [time.format] - // clang-format off - template - concept _Chrono_parse_spec_callbacks = _Parse_align_callbacks<_Ty, _CharT> - && _Parse_width_callbacks<_Ty, _CharT> - && _Parse_precision_callbacks<_Ty, _CharT> - && _Width_adapter_callbacks<_Ty, _CharT> - && _Precision_adapter_callbacks<_Ty, _CharT> - && requires(_Ty _At, basic_string_view<_CharT> _Sv, _Fmt_align _Aln) { - { _At._On_conversion_spec(_CharT{}, _CharT{}) } -> same_as; - { _At._On_lit_char(_CharT{}) } -> same_as; - }; - // clang-format on +template +struct _Choose_literal { + static constexpr const _CharT* _Choose(const char* _Str, const wchar_t* _WStr) { + if constexpr (is_same_v<_CharT, char>) { + return _Str; + } else { + return _WStr; + } + } +}; - template - struct _Chrono_specs { - _CharT _Lit_char = _CharT{0}; // any character other than '{', '}', or '%' - char _Modifier = '\0'; // either 'E' or 'O' - char _Type = '\0'; - }; +#define _STATICALLY_WIDEN(_CharT, _Literal) (_Choose_literal<_CharT>::_Choose(_Literal, L##_Literal)) - template - struct _Chrono_format_specs { - int _Width = 0; - int _Precision = -1; - int _Dynamic_width_index = -1; - int _Dynamic_precision_index = -1; - _Fmt_align _Alignment = _Fmt_align::_None; - uint8_t _Fill_length = 1; - // At most one codepoint (so one char32_t or four utf-8 char8_t) - _CharT _Fill[4 / sizeof(_CharT)] = {_CharT{' '}}; - // recursive definition in grammar, so could have any number of these with literal chars - vector<_Chrono_specs<_CharT>> _Chrono_specs_list; - }; - // Model of _Chrono_parse_spec_callbacks that fills a _Chrono_format_specs with the parsed data - template - class _Chrono_specs_setter { - public: - constexpr explicit _Chrono_specs_setter(_Chrono_format_specs<_CharT>& _Specs_) : _Specs(_Specs_) {} +// clang-format off +template +concept _Chrono_parse_spec_callbacks = _Parse_align_callbacks<_Ty, _CharT> + && _Parse_width_callbacks<_Ty, _CharT> + && _Parse_precision_callbacks<_Ty, _CharT> + && _Width_adapter_callbacks<_Ty, _CharT> + && _Precision_adapter_callbacks<_Ty, _CharT> + && requires(_Ty _At, basic_string_view<_CharT> _Sv, _Fmt_align _Aln) { + { _At._On_conversion_spec(_CharT{}, _CharT{}) } -> same_as; + { _At._On_lit_char(_CharT{}) } -> same_as; +}; +// clang-format on + +// A chrono spec is either a type (with an optional modifier), OR a literal character, never both. +template +struct _Chrono_specs { + _CharT _Lit_char = _CharT{'\0'}; // any character other than '{', '}', or '%' + _CharT _Modifier = _CharT{'\0'}; // either 'E' or 'O' + _CharT _Type = _CharT{'\0'}; +}; - // same as _Specs_setter - constexpr void _On_align(_Fmt_align _Aln) { - _Specs._Alignment = _Aln; - } +template +struct _Chrono_format_specs { + int _Width = 0; + int _Precision = -1; + int _Dynamic_width_index = -1; + int _Dynamic_precision_index = -1; + _Fmt_align _Alignment = _Fmt_align::_None; + uint8_t _Fill_length = 1; + // At most one codepoint (so one char32_t or four utf-8 char8_t) + _CharT _Fill[4 / sizeof(_CharT)] = {_CharT{' '}}; + // recursive definition in grammar, so could have any number of these with literal chars + vector<_Chrono_specs<_CharT>> _Chrono_specs_list; +}; - // same as _Specs_setter - constexpr void _On_fill(basic_string_view<_CharT> _Sv) { - if (_Sv.size() > _STD size(_Specs._Fill)) { - _THROW(format_error("Invalid fill (too long).")); - } +// Model of _Chrono_parse_spec_callbacks that fills a _Chrono_format_specs with the parsed data +template +class _Chrono_specs_setter { +public: + constexpr explicit _Chrono_specs_setter(_Chrono_format_specs<_CharT>& _Specs_, _ParseContext& _Parse_ctx_) + : _Specs(_Specs_), _Parse_ctx(_Parse_ctx_) {} - const auto _Pos = _STD _Copy_unchecked(_Sv._Unchecked_begin(), _Sv._Unchecked_end(), _Specs._Fill); - _STD fill(_Pos, _STD end(_Specs._Fill), _CharT{}); - _Specs._Fill_length = static_cast(_Sv.size()); + // same as _Specs_setter + constexpr void _On_align(_Fmt_align _Aln) { + _Specs._Alignment = _Aln; + } + + // same as _Specs_setter + constexpr void _On_fill(basic_string_view<_CharT> _Sv) { + if (_Sv.size() > _STD size(_Specs._Fill)) { + _THROW(format_error("Invalid fill (too long).")); } - constexpr void _On_width(int _Width) { - _Specs._Width = _Width; + const auto _Pos = _STD _Copy_unchecked(_Sv._Unchecked_begin(), _Sv._Unchecked_end(), _Specs._Fill); + _STD fill(_Pos, _STD end(_Specs._Fill), _CharT{}); + _Specs._Fill_length = static_cast(_Sv.size()); + } + + constexpr void _On_width(int _Width) { + _Specs._Width = _Width; + } + + constexpr void _On_precision(int _Prec) { + _Specs._Precision = _Prec; + } + + constexpr void _On_dynamic_width(const size_t _Arg_id) { + _Parse_ctx.check_arg_id(_Arg_id); + _Specs._Dynamic_width_index = _Verify_dynamic_arg_index_in_range(_Arg_id); + } + + constexpr void _On_dynamic_width(const _Auto_id_tag) { + _Specs._Dynamic_width_index = _Verify_dynamic_arg_index_in_range(_Parse_ctx.next_arg_id()); + } + + constexpr void _On_dynamic_precision(const size_t _Arg_id) { + _Parse_ctx.check_arg_id(_Arg_id); + _Specs._Dynamic_precision_index = _Verify_dynamic_arg_index_in_range(_Arg_id); + } + + constexpr void _On_dynamic_precision(const _Auto_id_tag) { + _Specs._Dynamic_precision_index = _Verify_dynamic_arg_index_in_range(_Parse_ctx.next_arg_id()); + } + + constexpr void _On_conversion_spec(_CharT _Modifier, _CharT _Type) { + // NOTE: same performance note from _Basic_format_specs also applies here + if (_Modifier != '\0' && _Modifier != 'E' && _Modifier != 'O') { + _THROW(format_error("Invalid modifier specification.")); } - constexpr void _On_precision(int _Prec) { - _Specs._Precision = _Prec; + if (_Type < 0 || _Type > (numeric_limits::max)()) { + _THROW(format_error("Invalid type specification.")); } - constexpr void _On_conversion_spec(_CharT _Modifier, _CharT _Type) { - // NOTE: same performance note from _Basic_format_specs also applies here - const char _Char_mod = static_cast(_Modifier); - const char _Char_type = static_cast(_Type); - if (_Char_mod != '\0' && _Char_mod != 'E' && _Char_mod != 'O') { - _THROW(format_error("Invalid modifier specification.")); - } + _Chrono_specs<_CharT> _Conv_spec{._Modifier = _Modifier, ._Type = _Type}; + _Specs._Chrono_specs_list.push_back(_Conv_spec); + } - if (_Type < 0 || _Type > (numeric_limits::max)()) { - _THROW(format_error("Invalid type specification.")); - } + constexpr void _On_lit_char(_CharT _Lit_ch) { + _Chrono_specs<_CharT> _Lit_char_spec{._Lit_char = _Lit_ch}; + _Specs._Chrono_specs_list.push_back(_Lit_char_spec); + } - _Chrono_specs<_CharT> _Conv_spec{._Modifier = _Char_mod, ._Type = _Char_type}; - _Specs._Chrono_specs_list.push_back(_Conv_spec); - } +protected: + _Chrono_format_specs<_CharT>& _Specs; + +private: + _ParseContext& _Parse_ctx; - constexpr void _On_lit_char(_CharT _Lit_ch) { - _Chrono_specs<_CharT> _Lit_char_spec{._Lit_char = _Lit_ch}; - _Specs._Chrono_specs_list.push_back(_Lit_char_spec); + _NODISCARD static constexpr int _Verify_dynamic_arg_index_in_range(const size_t _Idx) { + if (_Idx > static_cast((numeric_limits::max)())) { + _THROW(format_error("Dynamic width or precision index too large.")); } - protected: - _Chrono_format_specs<_CharT>& _Specs; - }; + return static_cast(_Idx); + } +}; + +// assumes that the required '%' at the beginning of a conversion-spec has already been consumed +template _Callbacks_type> +_NODISCARD constexpr const _CharT* _Parse_conversion_specs( + const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { + if (_Begin == _End || *_Begin == '}') { + _THROW(format_error("Invalid format string.")); + } - template _Callbacks_type> - _NODISCARD constexpr const _CharT* _Parse_conversion_specs( - const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { - _STL_INTERNAL_CHECK(*_Begin == '%'); + _CharT _Mod = '\0'; + _CharT _Ch = *_Begin; + + if (_Ch == 'E' || _Ch == 'O') { // includes modifier + _Mod = _Ch; ++_Begin; if (_Begin == _End || *_Begin == '}') { - _THROW(format_error("Invalid format string.")); + _THROW(format_error("Invalid format string - missing type after modifier.")); } + } - _CharT _Mod = '\0'; - _CharT _Ch = *_Begin; + _CharT _Type = *_Begin; + _Callbacks._On_conversion_spec(_Mod, _Type); - if (_Ch == 'E' || _Ch == 'O') { // includes modifier - _Mod = _Ch; - ++_Begin; - if (_Begin == _End || *_Begin == '}') { - _THROW(format_error("Invalid format string - missing type after modifier.")); + return ++_Begin; +} + +template _Callbacks_type> +_NODISCARD constexpr const _CharT* _Parse_chrono_format_specs( + const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { + if (_Begin == _End || *_Begin == '}') { + return _Begin; + } + + _Begin = _Parse_align(_Begin, _End, _Callbacks); + if (_Begin == _End) { + return _Begin; + } + + _Begin = _Parse_width(_Begin, _End, _Callbacks); + if (_Begin == _End) { + return _Begin; + } + + if (*_Begin == '.') { + _Begin = _Parse_precision(_Begin, _End, _Callbacks); + if (_Begin == _End) { + return _Begin; + } + } + + if (_Begin != _End && *_Begin != '}' && *_Begin != '%') { + _THROW(format_error("Invalid format string - chrono-specs must begin with conversion-specs")); + } + + // chrono-spec + while (_Begin != _End && *_Begin != '}') { + if (*_Begin == '%') { // conversion-spec + if (++_Begin == _End) { + _THROW(format_error("Invalid format string - missing type after %")); + } + + _CharT _Next_ch = *_Begin; + switch (_Next_ch) { + case 'n': + _Callbacks._On_lit_char('\n'); + ++_Begin; + break; + case 't': + _Callbacks._On_lit_char('\t'); + ++_Begin; + break; + case '%': + _Callbacks._On_lit_char('%'); + ++_Begin; + break; + default: // some other type + _Begin = _Parse_conversion_specs(_Begin, _End, _Callbacks); + break; } + } else { // literal-char + _Callbacks._On_lit_char(*_Begin); + ++_Begin; } + } - _CharT _Type = *_Begin; - _Callbacks._On_conversion_spec(_Mod, _Type); + return _Begin; +} - return ++_Begin; +namespace chrono { + /* + template + // clang-format off + requires (!treat_as_floating_point_v && (_Duration{1} < days{1})) + basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const sys_time<_Duration>& _Clock) { + // clang-format on + const auto _Dp = floor(_Clock); + return _Os << year_month_day{_Dp} << _STATICALLY_WIDEN(_CharT, " ") << hh_mm_ss{_Clock - _Dp}; } - template _Callbacks_type> - _NODISCARD constexpr const _CharT* _Parse_chrono_format_specs( - const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { - if (_Begin == _End || *_Begin == '}') { - return _Begin; + template + basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const sys_days& _Clock) { + return _Os << year_month_day{_Clock}; + } + + template + basic_ostream<_CharT, _Traits>& operator<<( + basic_ostream<_CharT, _Traits>& _Os, const utc_clock<_Duration>& _Clock) { + return _Os << format(_STATICALLY_WIDEN(_CharT, "{:%F %T}"), _Clock); + }*/ + + template + basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const day& _Val) { + if (static_cast(_Val) < 10) { + _Os << _CharT{'0'}; + } + _Os << static_cast(_Val); + if (!_Val.ok()) { + _Os << _STATICALLY_WIDEN(_CharT, " is not a valid day"); } - _Begin = _Parse_align(_Begin, _End, _Callbacks); - if (_Begin == _End) { - return _Begin; + return _Os; + } + + template + basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const month& _Val) { + if (_Val.ok()) { + // tm_mon is [0, 11] while month is [1, 12] + const tm _Time = {.tm_mon = static_cast(static_cast(_Val)) - 1}; + return _Os << _STD put_time(&_Time, _STATICALLY_WIDEN(_CharT, "%b")); } - _Begin = _Parse_width(_Begin, _End, _Callbacks); - if (_Begin == _End) { - return _Begin; + if (static_cast(_Val) < 10) { + _Os << _CharT{'0'}; + } + return _Os << static_cast(_Val) << _STATICALLY_WIDEN(_CharT, " is not a valid month"); + } +} // namespace chrono + +/* +template +struct formatter<_CHRONO sys_time<_Duration>, _CharT> { + typename basic_format_parse_context<_CharT>::iterator parse(basic_format_parse_context<_CharT>& _Parse_ctx) { + _Chrono_format_specs<_CharT> _Specs{}; + _Chrono_specs_setter<_CharT, basic_format_parse_context<_CharT>> _Callback{_Specs, _Parse_ctx}; + const auto _It = + _Parse_chrono_format_specs(_Parse_ctx._Unchecked_begin(), _Parse_ctx._Unchecked_end(), _Callback); + const auto _Res_iter = _Parse_ctx.begin() + (_It - _Parse_ctx._Unchecked_begin()); + + if (_It != _Parse_ctx._Unchecked_end() && *_It != '}') { + _THROW(format_error("Missing '}' in format string.")); + } + } +}; +*/ + +template +struct _Chrono_formatter { + typename basic_format_parse_context<_CharT>::iterator _Parse( + basic_format_parse_context<_CharT>& _Parse_ctx, string_view _Valid_types) { + _Chrono_specs_setter<_CharT, basic_format_parse_context<_CharT>> _Callback{_Specs, _Parse_ctx}; + const auto _It = + _Parse_chrono_format_specs(_Parse_ctx._Unchecked_begin(), _Parse_ctx._Unchecked_end(), _Callback); + const auto _Res_iter = _Parse_ctx.begin() + (_It - _Parse_ctx._Unchecked_begin()); + + if (_It != _Parse_ctx._Unchecked_end() && *_It != '}') { + _THROW(format_error("Missing '}' in format string.")); + } + + if (!_Allow_precision && _Specs._Precision != -1) { + _THROW(format_error("Precision specification invalid for type.")); + } + + const auto& _List = _Specs._Chrono_specs_list; + + // [time.format]/6 + if (_List.empty()) { + _No_chrono_specs = true; + return _Res_iter; + } + + for (const auto& _Spec : _List) { + if (_Spec._Type != '\0' && _RANGES find(_Valid_types, _Spec._Type) == _Valid_types.end()) { + _THROW(format_error("Invalid type.")); + } + _Check_modifier(_Spec._Type, _Spec._Modifier); + } + + return _Res_iter; + } + + void _Check_modifier(const _CharT _Type, const _CharT _Modifier) { + if (_Modifier == _CharT{'\0'}) { + return; } - if (*_Begin == '.') { - _Begin = _Parse_precision(_Begin, _End, _Callbacks); - if (_Begin == _End) { - return _Begin; + enum _Allowed_bit { _E_mod = 1, _O_mod = 2, _EO_mod = _E_mod | _O_mod }; + + struct _Table_entry { + char _Type; + _Allowed_bit _Allowed; + }; + + const _Allowed_bit _Mod = _Modifier == _CharT{'E'} ? _E_mod : _O_mod; + + static const _Table_entry _Table[] = {{'d', _O_mod}, {'e', _O_mod}, {'m', _O_mod}}; + + if (auto _It = _RANGES find(_Table, static_cast(_Type), &_Table_entry::_Type); _It != _STD end(_Table)) { + if (_It->_Allowed & _Mod) { + return; } } - // chrono-spec - while (_Begin != _End && *_Begin != '}') { - if (*_Begin == '%') { // conversion-spec - if (_Begin + 1 == _End) { - _THROW(format_error("Invalid format string - missing type after %")); + _THROW(format_error("Incompatible modifier for type")); + } + + template + typename _FormatContext::iterator _Write(_FormatContext& _FormatCtx, const _Ty& _Val, const tm& _Time) { + basic_stringstream<_CharT> _Stream; + + if (_No_chrono_specs) { + _Stream << _Val; + } else { + _Stream.imbue(_FormatCtx.locale()); + for (const auto& _Spec : _Specs._Chrono_specs_list) { + if (_Spec._Lit_char != _CharT{'\0'}) { + _Stream << _Spec._Lit_char; + continue; } - const _CharT _Next_ch = *(_Begin + 1); - switch (_Next_ch) { - case 'n': - _Callbacks._On_lit_char('\n'); - _Begin += 2; - break; - case 't': - _Callbacks._On_lit_char('\t'); - _Begin += 2; - break; - case '%': - _Callbacks._On_lit_char('%'); - _Begin += 2; - break; - default: // some other type - _Begin = _Parse_conversion_specs(_Begin, _End, _Callbacks); - break; + // TRANSITION, out of bounds may be legal for non-localized decimal printing, but it isn't very + // consistent. + if (!_Val.ok()) { + _THROW(format_error("Cannot print out-of-bounds time point.")); } - } else { // literal-char - _Callbacks._On_lit_char(*_Begin); - ++_Begin; + + _CharT _Fmt_str[4]; + size_t _Next_idx = 0; + _Fmt_str[_Next_idx++] = _CharT{'%'}; + if (_Spec._Modifier != _CharT{'\0'}) { + _Fmt_str[_Next_idx++] = _Spec._Modifier; + } + _Fmt_str[_Next_idx++] = _Spec._Type; + _Fmt_str[_Next_idx] = _CharT{'\0'}; + + _Stream << _STD put_time<_CharT>(&_Time, _Fmt_str); } } - return _Begin; + return _Write_aligned(_STD move(_FormatCtx.out()), static_cast(_Stream.view().size()), _Specs, + _Fmt_align::_Left, [&](auto _Out) { return _Fmt_write(_STD move(_Out), _Stream.view()); }); + } + + _Chrono_format_specs<_CharT> _Specs{}; + bool _No_chrono_specs = false; +}; + +template +struct formatter<_CHRONO day, _CharT> { + typename basic_format_parse_context<_CharT>::iterator parse(basic_format_parse_context<_CharT>& _Parse_ctx) { + return _Impl._Parse(_Parse_ctx, "de"); } + + template + typename _FormatContext::iterator format(const _CHRONO day& _Val, _FormatContext& _FormatCtx) { + const tm _Time = {.tm_mday = static_cast(static_cast(_Val))}; + return _Impl._Write(_FormatCtx, _Val, _Time); + } + +private: + _Chrono_formatter<_CharT, false> _Impl; +}; + +template +struct formatter<_CHRONO month, _CharT> { + typename basic_format_parse_context<_CharT>::iterator parse(basic_format_parse_context<_CharT>& _Parse_ctx) { + return _Impl._Parse(_Parse_ctx, "bBhm"); + } + + template + typename _FormatContext::iterator format(const _CHRONO month& _Val, _FormatContext& _FormatCtx) { + // tm_mon is [0, 11] while month is [1, 12] + const tm _Time = {.tm_mon = static_cast(static_cast(_Val)) - 1}; + return _Impl._Write(_FormatCtx, _Val, _Time); + } + +private: + _Chrono_formatter<_CharT, false> _Impl; +}; #endif // __cpp_lib_concepts #endif // _HAS_CXX20 -} // namespace chrono // HELPERS template @@ -5410,7 +5664,8 @@ inline namespace literals { return _CHRONO duration(_Val); } - _NODISCARD constexpr _CHRONO milliseconds operator"" ms(unsigned long long _Val) noexcept /* strengthened */ { + _NODISCARD constexpr _CHRONO milliseconds operator"" ms(unsigned long long _Val) noexcept + /* strengthened */ { return _CHRONO milliseconds(_Val); } @@ -5419,7 +5674,8 @@ inline namespace literals { return _CHRONO duration(_Val); } - _NODISCARD constexpr _CHRONO microseconds operator"" us(unsigned long long _Val) noexcept /* strengthened */ { + _NODISCARD constexpr _CHRONO microseconds operator"" us(unsigned long long _Val) noexcept + /* strengthened */ { return _CHRONO microseconds(_Val); } @@ -5428,7 +5684,8 @@ inline namespace literals { return _CHRONO duration(_Val); } - _NODISCARD constexpr _CHRONO nanoseconds operator"" ns(unsigned long long _Val) noexcept /* strengthened */ { + _NODISCARD constexpr _CHRONO nanoseconds operator"" ns(unsigned long long _Val) noexcept + /* strengthened */ { return _CHRONO nanoseconds(_Val); } diff --git a/stl/inc/format b/stl/inc/format index 939581aff9..a03aa72d7e 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -1598,9 +1598,9 @@ _NODISCARD _OutputIt _Fmt_write(_OutputIt _Out, const basic_string_view<_CharT> return _RANGES copy(_Value, _STD move(_Out)).out; } -template -_NODISCARD _OutputIt _Write_aligned(_OutputIt _Out, const int _Width, const _Basic_format_specs<_CharT>& _Specs, - const _Fmt_align _Default_align, _Func&& _Fn) { +template +_NODISCARD _OutputIt _Write_aligned( + _OutputIt _Out, const int _Width, const _Specs_type& _Specs, const _Fmt_align _Default_align, _Func&& _Fn) { int _Fill_left = 0; int _Fill_right = 0; auto _Alignment = _Specs._Alignment; @@ -1627,7 +1627,7 @@ _NODISCARD _OutputIt _Write_aligned(_OutputIt _Out, const int _Width, const _Bas } } - const basic_string_view<_CharT> _Fill_char{_Specs._Fill, _Specs._Fill_length}; + const basic_string_view _Fill_char{_Specs._Fill, _Specs._Fill_length}; for (; _Fill_left > 0; --_Fill_left) { _Out = _RANGES copy(_Fill_char, _STD move(_Out)).out; } diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index 2ae3a52bb3..3941684ad5 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -33,6 +33,7 @@ struct choose_literal { }; #define TYPED_LITERAL(CharT, Literal) (choose_literal::choose(Literal, L##Literal)) +#define STR(Literal) TYPED_LITERAL(CharT, Literal) template struct testing_callbacks { @@ -72,8 +73,8 @@ struct testing_callbacks { assert(expected_auto_dynamic_precision); } void _On_conversion_spec(CharT mod, CharT type) { - assert(static_cast(mod) == expected_chrono_specs[curr_index]._Modifier); - assert(static_cast(type) == expected_chrono_specs[curr_index]._Type); + assert(mod == expected_chrono_specs[curr_index]._Modifier); + assert(type == expected_chrono_specs[curr_index]._Type); assert(expected_chrono_specs[curr_index]._Lit_char == CharT{0}); // not set ++curr_index; } @@ -107,13 +108,13 @@ bool test_parse_conversion_spec() { using view_typ = basic_string_view; using chrono_spec = _Chrono_specs; - view_typ s0(TYPED_LITERAL(CharT, "%B")); - view_typ s1(TYPED_LITERAL(CharT, "%Ec")); - view_typ s2(TYPED_LITERAL(CharT, "%Od")); - view_typ s3(TYPED_LITERAL(CharT, "%E")); - view_typ s4(TYPED_LITERAL(CharT, "%")); - view_typ s5(TYPED_LITERAL(CharT, "%}")); - view_typ s6(TYPED_LITERAL(CharT, "%E}")); + view_typ s0(TYPED_LITERAL(CharT, "B")); + view_typ s1(TYPED_LITERAL(CharT, "Ec")); + view_typ s2(TYPED_LITERAL(CharT, "Od")); + view_typ s3(TYPED_LITERAL(CharT, "E")); + view_typ s4(TYPED_LITERAL(CharT, "")); + view_typ s5(TYPED_LITERAL(CharT, "}")); + view_typ s6(TYPED_LITERAL(CharT, "E}")); vector v0{{._Type = 'B'}}; test_parse_helper(parse_conv_spec_fn, s0, false, view_typ::npos, {.expected_chrono_specs = v0}); @@ -147,13 +148,13 @@ bool test_parse_chrono_format_specs() { view_typ s5(TYPED_LITERAL(CharT, "*^4.4%ymm")); view_typ s6(TYPED_LITERAL(CharT, "%H%")); view_typ s7(TYPED_LITERAL(CharT, "%H%}")); - view_typ s8(TYPED_LITERAL(CharT, "A%nB%tC%%D")); + view_typ s8(TYPED_LITERAL(CharT, "%nB%tC%%D")); vector v0{{._Modifier = 'O', ._Type = 'e'}}; test_parse_helper(parse_chrono_format_specs_fn, s0, false, s0.size(), {.expected_chrono_specs = v0}); vector v1{{._Lit_char = 'l'}, {._Lit_char = 'i'}, {._Lit_char = 't'}}; - test_parse_helper(parse_chrono_format_specs_fn, s1, false, s1.size(), {.expected_chrono_specs = v1}); + test_parse_helper(parse_chrono_format_specs_fn, s1, true, s1.size(), {.expected_chrono_specs = v1}); vector v2{{._Type = 'H'}, {._Lit_char = ':'}, {._Type = 'M'}}; test_parse_helper(parse_chrono_format_specs_fn, s2, false, s2.size() - 1, {.expected_chrono_specs = v2}); @@ -162,12 +163,12 @@ bool test_parse_chrono_format_specs() { test_parse_helper( parse_chrono_format_specs_fn, s3, false, s3.size() - 1, {.expected_width = 6, .expected_chrono_specs = v3}); - vector v8{{._Lit_char = 'A'}, {._Lit_char = '\n'}, {._Lit_char = 'B'}, {._Lit_char = '\t'}, - {._Lit_char = 'C'}, {._Lit_char = '%'}, {._Lit_char = 'D'}}; + vector v8{{._Lit_char = '\n'}, {._Lit_char = 'B'}, {._Lit_char = '\t'}, {._Lit_char = 'C'}, + {._Lit_char = '%'}, {._Lit_char = 'D'}}; test_parse_helper(parse_chrono_format_specs_fn, s8, false, s8.size(), {.expected_chrono_specs = v8}); vector v4{{._Lit_char = 'h'}, {._Lit_char = 'i'}}; - test_parse_helper(parse_chrono_format_specs_fn, s4, false, s4.size(), + test_parse_helper(parse_chrono_format_specs_fn, s4, true, s4.size(), {.expected_alignment = _Fmt_align::_Left, .expected_fill = view_typ(TYPED_LITERAL(CharT, "*")), .expected_width = 6, @@ -188,10 +189,161 @@ bool test_parse_chrono_format_specs() { return true; } +template +void throw_helper(const basic_string_view fmt, const Args&... vals) { + try { + (void) format(fmt, vals...); + assert(false); + } catch (const format_error&) { + } +} + +template +void throw_helper(const charT* fmt, const Args&... vals) { + throw_helper(basic_string_view{fmt}, vals...); +} + +template +void stream_helper(const charT* expect, const Args&... vals) { + basic_stringstream stream; + (stream << ... << vals); + assert(stream.str() == expect); + assert(stream); +} + +template +constexpr void print(Str str) { + if constexpr (is_same_v) { + cout << "res: " << str << "\n"; + } else { + wcout << "res: " << str << "\n"; + } +} + +#ifndef __clang__ // TRANSITION, LLVM-48606 +template +void test_day_formatter() { + using view_typ = basic_string_view; + using str_typ = basic_string; + + view_typ s0(TYPED_LITERAL(CharT, "{:%d}")); + view_typ s1(TYPED_LITERAL(CharT, "{:%e}")); + view_typ s2(TYPED_LITERAL(CharT, "{:%Od}")); + view_typ s3(TYPED_LITERAL(CharT, "{:%Oe}")); + view_typ s4(TYPED_LITERAL(CharT, "{}")); + view_typ s5(TYPED_LITERAL(CharT, "{:=>8}")); + view_typ s6(TYPED_LITERAL(CharT, "{:lit}")); + view_typ s7(TYPED_LITERAL(CharT, "{:%d days}")); + view_typ s8(TYPED_LITERAL(CharT, "{:*^6%dmm}")); + + str_typ a0(TYPED_LITERAL(CharT, "27")); + str_typ a1(TYPED_LITERAL(CharT, "05")); + str_typ a2(TYPED_LITERAL(CharT, " 5")); + str_typ a3(TYPED_LITERAL(CharT, "50 is not a valid day")); + str_typ a4(TYPED_LITERAL(CharT, "======27")); + str_typ a5(TYPED_LITERAL(CharT, "======05")); + str_typ a6(TYPED_LITERAL(CharT, "lit27")); + str_typ a7(TYPED_LITERAL(CharT, "27 days")); + str_typ a8(TYPED_LITERAL(CharT, "*27mm*")); + + // 2 digits + day d0{27}; + auto res = format(s0, d0); + assert(res == a0); + res = format(s1, d0); + assert(res == a0); + + // 1 digit + day d1{5}; + res = format(s0, d1); + assert(res == a1); + res = format(s1, d1); + assert(res == a2); + + // O modifier + res = format(s2, d0); + assert(res == a0); + res = format(s3, d0); + assert(res == a0); + res = format(s2, d1); + assert(res == a1); + res = format(s3, d1); + assert(res == a2); + + // [time.format]/6 + day d2{50}; + res = format(s4, d0); + assert(res == a0); + res = format(s4, d2); + assert(res == a3); + + // width/align + res = format(s5, d0); + assert(res == a4); + res = format(s5, d1); + assert(res == a5); + res = format(s5, d2); + assert(res == a3); + + // chrono-spec must begin with conversion-spec + throw_helper(s6, d0); + + // lit chars + res = format(s7, d0); + assert(res == a7); + res = format(s8, d0); + assert(res == a8); + + assert(format(STR("{:%d %d %d}"), day{27}) == STR("27 27 27")); + throw_helper(STR("{:%d}"), day{200}); + throw_helper(STR("{:%Ed}"), day{10}); + assert(format(STR("{}"), day{0}) == STR("00 is not a valid day")); + + // Op << + stream_helper(STR("00 is not a valid day"), day{0}); + stream_helper(STR("27"), day{27}); + stream_helper(STR("200 is not a valid day"), day{200}); +} + +template +void test_month_formatter() { + assert(format(STR("{}"), month{1}) == STR("Jan")); + assert(format(STR("{}"), month{12}) == STR("Dec")); + assert(format(STR("{}"), month{0}) == STR("00 is not a valid month")); + assert(format(STR("{}"), month{20}) == STR("20 is not a valid month")); + + // Specs + assert(format(STR("{:%b %h %B}"), month{1}) == STR("Jan Jan January")); + assert(format(STR("{:%m %Om}"), month{1}) == STR("01 01")); + + // Out of bounds month + throw_helper(STR("{:%m}"), month{0}); + throw_helper(STR("{:%b}"), month{0}); + throw_helper(STR("{:%h}"), month{0}); + throw_helper(STR("{::%B}"), month{0}); + + // Invalid specs + throw_helper(STR("{:%A}"), month{1}); + throw_helper(STR("{:%.4}"), month{1}); + + // Op << + stream_helper(STR("Jan"), month{1}); + stream_helper(STR("Dec"), month{12}); + stream_helper(STR("00 is not a valid month"), month{0}); + stream_helper(STR("20 is not a valid month"), month{20}); +} +#endif // __clang__ + int main() { test_parse_conversion_spec(); test_parse_conversion_spec(); test_parse_chrono_format_specs(); test_parse_chrono_format_specs(); + + test_day_formatter(); + test_day_formatter(); + + test_month_formatter(); + test_month_formatter(); } From 33e38046f34c5e5c32b880947ccb4b079f81b81e Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Thu, 15 Apr 2021 02:09:50 -0700 Subject: [PATCH 02/41] Fix tests. --- tests/libcxx/expected_results.txt | 8 +++++--- tests/libcxx/skipped_tests.txt | 2 -- .../P0355R7_calendars_and_time_zones_formatting/test.cpp | 2 -- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/libcxx/expected_results.txt b/tests/libcxx/expected_results.txt index e667375ace..3d5c538b7a 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -276,10 +276,8 @@ std/utilities/memory/default.allocator/allocator.members/allocate.verify.cpp SKI # *** MISSING STL FEATURES *** # C++20 P0355R7 " Calendars And Time Zones" -std/utilities/time/time.cal/time.cal.day/time.cal.day.nonmembers/streaming.pass.cpp FAIL std/utilities/time/time.cal/time.cal.md/time.cal.md.nonmembers/streaming.pass.cpp FAIL std/utilities/time/time.cal/time.cal.mdlast/streaming.pass.cpp FAIL -std/utilities/time/time.cal/time.cal.month/time.cal.month.nonmembers/streaming.pass.cpp FAIL std/utilities/time/time.cal/time.cal.mwd/time.cal.mwd.nonmembers/streaming.pass.cpp FAIL std/utilities/time/time.cal/time.cal.mwdlast/time.cal.mwdlast.nonmembers/streaming.pass.cpp FAIL std/utilities/time/time.cal/time.cal.wdidx/time.cal.wdidx.nonmembers/streaming.pass.cpp FAIL @@ -870,7 +868,7 @@ std/re/re.alg/re.alg.search/extended.locale.pass.cpp FAIL # *** XFAILs WHICH PASS *** -# Not yet implemented in libcxx and marked as XFAIL +# Not yet implemented in libcxx and marked as "XFAIL: libc++" std/strings/c.strings/cuchar.pass.cpp PASS std/utilities/function.objects/func.search/func.search.bm/default.pass.cpp PASS std/utilities/function.objects/func.search/func.search.bm/hash.pass.cpp PASS @@ -880,3 +878,7 @@ std/utilities/function.objects/func.search/func.search.bmh/default.pass.cpp PASS std/utilities/function.objects/func.search/func.search.bmh/hash.pass.cpp PASS std/utilities/function.objects/func.search/func.search.bmh/hash.pred.pass.cpp PASS std/utilities/function.objects/func.search/func.search.bmh/pred.pass.cpp PASS + +# Not yet implemented in libcxx and marked as "XFAIL: *" +std/utilities/time/time.cal/time.cal.day/time.cal.day.nonmembers/streaming.pass.cpp SKIPPED +std/utilities/time/time.cal/time.cal.month/time.cal.month.nonmembers/streaming.pass.cpp SKIPPED diff --git a/tests/libcxx/skipped_tests.txt b/tests/libcxx/skipped_tests.txt index 3ac3830315..acda0d4042 100644 --- a/tests/libcxx/skipped_tests.txt +++ b/tests/libcxx/skipped_tests.txt @@ -276,10 +276,8 @@ utilities\memory\default.allocator\allocator.members\allocate.verify.cpp # *** MISSING STL FEATURES *** # C++20 P0355R7 " Calendars And Time Zones" -utilities\time\time.cal\time.cal.day\time.cal.day.nonmembers\streaming.pass.cpp utilities\time\time.cal\time.cal.md\time.cal.md.nonmembers\streaming.pass.cpp utilities\time\time.cal\time.cal.mdlast\streaming.pass.cpp -utilities\time\time.cal\time.cal.month\time.cal.month.nonmembers\streaming.pass.cpp utilities\time\time.cal\time.cal.mwd\time.cal.mwd.nonmembers\streaming.pass.cpp utilities\time\time.cal\time.cal.mwdlast\time.cal.mwdlast.nonmembers\streaming.pass.cpp utilities\time\time.cal\time.cal.wdidx\time.cal.wdidx.nonmembers\streaming.pass.cpp diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index 3941684ad5..ef3c700879 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -220,7 +220,6 @@ constexpr void print(Str str) { } } -#ifndef __clang__ // TRANSITION, LLVM-48606 template void test_day_formatter() { using view_typ = basic_string_view; @@ -332,7 +331,6 @@ void test_month_formatter() { stream_helper(STR("00 is not a valid month"), month{0}); stream_helper(STR("20 is not a valid month"), month{20}); } -#endif // __clang__ int main() { test_parse_conversion_spec(); From cdfa88c7c152bdd8a70004dc023927c57dbe0a2b Mon Sep 17 00:00:00 2001 From: Elnar D Date: Thu, 15 Apr 2021 06:52:54 -0700 Subject: [PATCH 03/41] charT -> char mod/type, small bugfix and nits --- stl/inc/chrono | 41 ++++++++----------- .../test.cpp | 8 ++-- 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index af97a9ede9..008c5526ec 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5210,7 +5210,7 @@ concept _Chrono_parse_spec_callbacks = _Parse_align_callbacks<_Ty, _CharT> && _Width_adapter_callbacks<_Ty, _CharT> && _Precision_adapter_callbacks<_Ty, _CharT> && requires(_Ty _At, basic_string_view<_CharT> _Sv, _Fmt_align _Aln) { - { _At._On_conversion_spec(_CharT{}, _CharT{}) } -> same_as; + { _At._On_conversion_spec(char{}, _CharT{}) } -> same_as; { _At._On_lit_char(_CharT{}) } -> same_as; }; // clang-format on @@ -5219,8 +5219,8 @@ concept _Chrono_parse_spec_callbacks = _Parse_align_callbacks<_Ty, _CharT> template struct _Chrono_specs { _CharT _Lit_char = _CharT{'\0'}; // any character other than '{', '}', or '%' - _CharT _Modifier = _CharT{'\0'}; // either 'E' or 'O' - _CharT _Type = _CharT{'\0'}; + char _Modifier = '\0'; // either 'E' or 'O' + char _Type = '\0'; }; template @@ -5286,7 +5286,7 @@ public: _Specs._Dynamic_precision_index = _Verify_dynamic_arg_index_in_range(_Parse_ctx.next_arg_id()); } - constexpr void _On_conversion_spec(_CharT _Modifier, _CharT _Type) { + constexpr void _On_conversion_spec(char _Modifier, _CharT _Type) { // NOTE: same performance note from _Basic_format_specs also applies here if (_Modifier != '\0' && _Modifier != 'E' && _Modifier != 'O') { _THROW(format_error("Invalid modifier specification.")); @@ -5296,7 +5296,7 @@ public: _THROW(format_error("Invalid type specification.")); } - _Chrono_specs<_CharT> _Conv_spec{._Modifier = _Modifier, ._Type = _Type}; + _Chrono_specs<_CharT> _Conv_spec{._Modifier = _Modifier, ._Type = static_cast(_Type)}; _Specs._Chrono_specs_list.push_back(_Conv_spec); } @@ -5328,11 +5328,11 @@ _NODISCARD constexpr const _CharT* _Parse_conversion_specs( _THROW(format_error("Invalid format string.")); } - _CharT _Mod = '\0'; - _CharT _Ch = *_Begin; + char _Mod = '\0'; + _CharT _Ch = *_Begin; if (_Ch == 'E' || _Ch == 'O') { // includes modifier - _Mod = _Ch; + _Mod = static_cast(_Ch); ++_Begin; if (_Begin == _End || *_Begin == '}') { _THROW(format_error("Invalid format string - missing type after modifier.")); @@ -5449,10 +5449,6 @@ namespace chrono { const tm _Time = {.tm_mon = static_cast(static_cast(_Val)) - 1}; return _Os << _STD put_time(&_Time, _STATICALLY_WIDEN(_CharT, "%b")); } - - if (static_cast(_Val) < 10) { - _Os << _CharT{'0'}; - } return _Os << static_cast(_Val) << _STATICALLY_WIDEN(_CharT, " is not a valid month"); } } // namespace chrono @@ -5476,8 +5472,7 @@ struct formatter<_CHRONO sys_time<_Duration>, _CharT> { template struct _Chrono_formatter { - typename basic_format_parse_context<_CharT>::iterator _Parse( - basic_format_parse_context<_CharT>& _Parse_ctx, string_view _Valid_types) { + auto _Parse(basic_format_parse_context<_CharT>& _Parse_ctx, string_view _Valid_types) { _Chrono_specs_setter<_CharT, basic_format_parse_context<_CharT>> _Callback{_Specs, _Parse_ctx}; const auto _It = _Parse_chrono_format_specs(_Parse_ctx._Unchecked_begin(), _Parse_ctx._Unchecked_end(), _Callback); @@ -5509,8 +5504,8 @@ struct _Chrono_formatter { return _Res_iter; } - void _Check_modifier(const _CharT _Type, const _CharT _Modifier) { - if (_Modifier == _CharT{'\0'}) { + void _Check_modifier(const char _Type, const char _Modifier) { + if (_Modifier == '\0') { return; } @@ -5521,11 +5516,11 @@ struct _Chrono_formatter { _Allowed_bit _Allowed; }; - const _Allowed_bit _Mod = _Modifier == _CharT{'E'} ? _E_mod : _O_mod; - static const _Table_entry _Table[] = {{'d', _O_mod}, {'e', _O_mod}, {'m', _O_mod}}; - if (auto _It = _RANGES find(_Table, static_cast(_Type), &_Table_entry::_Type); _It != _STD end(_Table)) { + const _Allowed_bit _Mod = _Modifier == 'E' ? _E_mod : _O_mod; + + if (auto _It = _RANGES find(_Table, _Type, &_Table_entry::_Type); _It != _STD end(_Table)) { if (_It->_Allowed & _Mod) { return; } @@ -5535,7 +5530,7 @@ struct _Chrono_formatter { } template - typename _FormatContext::iterator _Write(_FormatContext& _FormatCtx, const _Ty& _Val, const tm& _Time) { + auto _Write(_FormatContext& _FormatCtx, const _Ty& _Val, const tm& _Time) { basic_stringstream<_CharT> _Stream; if (_No_chrono_specs) { @@ -5557,10 +5552,10 @@ struct _Chrono_formatter { _CharT _Fmt_str[4]; size_t _Next_idx = 0; _Fmt_str[_Next_idx++] = _CharT{'%'}; - if (_Spec._Modifier != _CharT{'\0'}) { - _Fmt_str[_Next_idx++] = _Spec._Modifier; + if (_Spec._Modifier != '\0') { + _Fmt_str[_Next_idx++] = static_cast<_CharT>(_Spec._Modifier); } - _Fmt_str[_Next_idx++] = _Spec._Type; + _Fmt_str[_Next_idx++] = static_cast<_CharT>(_Spec._Type); _Fmt_str[_Next_idx] = _CharT{'\0'}; _Stream << _STD put_time<_CharT>(&_Time, _Fmt_str); diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index ef3c700879..8c2fccb77c 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -72,9 +72,9 @@ struct testing_callbacks { void _On_dynamic_precision(_Auto_id_tag) { assert(expected_auto_dynamic_precision); } - void _On_conversion_spec(CharT mod, CharT type) { + void _On_conversion_spec(char mod, CharT type) { assert(mod == expected_chrono_specs[curr_index]._Modifier); - assert(type == expected_chrono_specs[curr_index]._Type); + assert(static_cast(type) == expected_chrono_specs[curr_index]._Type); assert(expected_chrono_specs[curr_index]._Lit_char == CharT{0}); // not set ++curr_index; } @@ -308,7 +308,7 @@ template void test_month_formatter() { assert(format(STR("{}"), month{1}) == STR("Jan")); assert(format(STR("{}"), month{12}) == STR("Dec")); - assert(format(STR("{}"), month{0}) == STR("00 is not a valid month")); + assert(format(STR("{}"), month{0}) == STR("0 is not a valid month")); assert(format(STR("{}"), month{20}) == STR("20 is not a valid month")); // Specs @@ -328,7 +328,7 @@ void test_month_formatter() { // Op << stream_helper(STR("Jan"), month{1}); stream_helper(STR("Dec"), month{12}); - stream_helper(STR("00 is not a valid month"), month{0}); + stream_helper(STR("0 is not a valid month"), month{0}); stream_helper(STR("20 is not a valid month"), month{20}); } From 0aeb5ea4a3d09dcbe2700a3e8cd4c5f8e4bb0ed9 Mon Sep 17 00:00:00 2001 From: Elnar Dakeshov <55715127+eldakesh-ms@users.noreply.github.com> Date: Thu, 15 Apr 2021 15:03:11 -0700 Subject: [PATCH 04/41] chronat: Add year and year_month_day formatting (#1840) * : Add year and year_month_day formatting This one is a biggie. The main things changed are the way that specifiers are handled and delegated. The general idea behind formatting time is that you can take segments of a type and format them individually. For example, you can take the year out of year_month_day and do the exact same operations you can do with a normal year. The `_Is_type_valid` function recursively checks if a parent type can be formatted by its children. The other functions didn't really need much more finessing, the `tm` structure already has all the fields we need to hold all the time info (simultaniously) and the formatters work off that. The big change here is in moving some "basic" formatters into our own function and not relying on `get_time` to format them. The main reason is that `get_time` does not play with invalid ranges, at all. A day of `40` is always illegal, but we need to be able to format it, especially in the face of `operator <<`. We could have kept what we had before, but then it becomes a clear problem that we cannot use `%F` for a `year_month_day` that has an invalid day, so I am seperating all the integral formatters out into that function. Again, because of the nested nature of times, we recurse in this function. Note that function currently uses `format_to` in probably a very inneficient way. I am all ears on how to improve that. * Clang test * Finesse the tests * review * Comment update Co-authored-by: mnatsuhara <46756417+mnatsuhara@users.noreply.github.com> * PR fixes Co-authored-by: mnatsuhara <46756417+mnatsuhara@users.noreply.github.com> --- stl/inc/chrono | 184 ++++++++++++++++-- tests/libcxx/expected_results.txt | 6 +- tests/libcxx/skipped_tests.txt | 3 - .../test.cpp | 38 +++- 4 files changed, 204 insertions(+), 27 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 008c5526ec..773cb5bb6f 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5408,6 +5408,62 @@ _NODISCARD constexpr const _CharT* _Parse_chrono_format_specs( } namespace chrono { + // This echoes the functionality of put_time, but is able to handle invalid dates (when !ok()) since the Standard + // mandates that invalid dates still be formatted properly. For example, put_time isn't able to handle a tm_mday of + // 40, but format("{:%d}", day{40}) should return "40" and operator<< for day prints "40 is not a valid day". + template + bool _Try_simple_write(basic_ostream<_CharT>& _Os, const char _Type, const tm& _Time) { + const auto _Year = _Time.tm_year + 1900; + const auto _Month = _Time.tm_mon + 1; + switch (_Type) { + case 'd': + case 'e': + if (_Time.tm_mday < 10) { + _Os << (_Type == 'd' ? _CharT{'0'} : _CharT{' '}); + } + _Os << _Time.tm_mday; + return true; + case 'm': + if (_Month < 10) { + _Os << _CharT{'0'}; + } + _Os << _Month; + return true; + case 'Y': + if (_Year < 0) { + _Os << _CharT{'-'}; + } + _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:04}"), _STD abs(_Year)); + return true; + case 'y': + _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:02}"), _Year < 0 ? 100 + (_Year % 100) : _Year % 100); + return true; + case 'C': + if (_Year < 0) { + _Os << _CharT{'-'}; + } + _Os << _STD format( + _STATICALLY_WIDEN(_CharT, "{:02}"), _STD abs(_Time_parse_fields::_Decompose_year(_Year).first) / 100); + return true; + case 'F': + _Try_simple_write(_Os, 'Y', _Time); + _Os << _CharT{'-'}; + _Try_simple_write(_Os, 'm', _Time); + _Os << _CharT{'-'}; + _Try_simple_write(_Os, 'd', _Time); + return true; + case 'D': + _Try_simple_write(_Os, 'm', _Time); + _Os << _CharT{'/'}; + _Try_simple_write(_Os, 'd', _Time); + _Os << _CharT{'/'}; + _Try_simple_write(_Os, 'y', _Time); + return true; + default: + return false; + } + } + /* template // clang-format off @@ -5431,10 +5487,8 @@ namespace chrono { template basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const day& _Val) { - if (static_cast(_Val) < 10) { - _Os << _CharT{'0'}; - } - _Os << static_cast(_Val); + const tm _Time = {.tm_mday = static_cast(static_cast(_Val))}; + _Try_simple_write(_Os, 'd', _Time); if (!_Val.ok()) { _Os << _STATICALLY_WIDEN(_CharT, " is not a valid day"); } @@ -5451,6 +5505,28 @@ namespace chrono { } return _Os << static_cast(_Val) << _STATICALLY_WIDEN(_CharT, " is not a valid month"); } + + template + basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const year& _Val) { + const tm _Time = {.tm_year = static_cast(_Val) - 1900}; + _Try_simple_write(_Os, 'Y', _Time); + if (!_Val.ok()) { + _Os << _STATICALLY_WIDEN(_CharT, " is not a valid year"); + } + return _Os; + } + + template + basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const year_month_day& _Val) { + const tm _Time = {.tm_mday = static_cast(static_cast(_Val.day())), + .tm_mon = static_cast(static_cast(_Val.month())) - 1, + .tm_year = static_cast(_Val.year()) - 1900}; + _Try_simple_write(_Os, 'F', _Time); + if (!_Val.ok()) { + _Os << _STATICALLY_WIDEN(_CharT, " is not a valid date"); + } + return _Os; + } } // namespace chrono /* @@ -5472,7 +5548,8 @@ struct formatter<_CHRONO sys_time<_Duration>, _CharT> { template struct _Chrono_formatter { - auto _Parse(basic_format_parse_context<_CharT>& _Parse_ctx, string_view _Valid_types) { + template + _NODISCARD auto _Parse(basic_format_parse_context<_CharT>& _Parse_ctx) { _Chrono_specs_setter<_CharT, basic_format_parse_context<_CharT>> _Callback{_Specs, _Parse_ctx}; const auto _It = _Parse_chrono_format_specs(_Parse_ctx._Unchecked_begin(), _Parse_ctx._Unchecked_end(), _Callback); @@ -5495,7 +5572,7 @@ struct _Chrono_formatter { } for (const auto& _Spec : _List) { - if (_Spec._Type != '\0' && _RANGES find(_Valid_types, _Spec._Type) == _Valid_types.end()) { + if (_Spec._Type != '\0' && !_Is_valid_type<_Ty>(_Spec._Type)) { _THROW(format_error("Invalid type.")); } _Check_modifier(_Spec._Type, _Spec._Modifier); @@ -5516,7 +5593,8 @@ struct _Chrono_formatter { _Allowed_bit _Allowed; }; - static const _Table_entry _Table[] = {{'d', _O_mod}, {'e', _O_mod}, {'m', _O_mod}}; + static const _Table_entry _Table[] = { + {'d', _O_mod}, {'e', _O_mod}, {'m', _O_mod}, {'Y', _E_mod}, {'y', _EO_mod}, {'C', _E_mod}}; const _Allowed_bit _Mod = _Modifier == 'E' ? _E_mod : _O_mod; @@ -5529,8 +5607,25 @@ struct _Chrono_formatter { _THROW(format_error("Incompatible modifier for type")); } + template + _NODISCARD constexpr bool _Is_valid_type(const char _Type) noexcept { + if constexpr (is_same_v<_Ty, _CHRONO day>) { + return _Type == 'd' || _Type == 'e'; + } else if constexpr (is_same_v<_Ty, _CHRONO month>) { + return _Type == 'b' || _Type == 'B' || _Type == 'h' || _Type == 'm'; + } else if constexpr (is_same_v<_Ty, _CHRONO year>) { + return _Type == 'Y' || _Type == 'y' || _Type == 'C'; + } else if constexpr (is_same_v<_Ty, _CHRONO year_month_day>) { + return _Type == 'D' || _Type == 'F' || _Is_valid_type<_CHRONO year>(_Type) + || _Is_valid_type<_CHRONO month>(_Type) || _Is_valid_type<_CHRONO day>(_Type); + } else { + // TRANSITION, remove when all types are added + static_assert(_Always_false<_Ty>, "unsupported type"); + } + } + template - auto _Write(_FormatContext& _FormatCtx, const _Ty& _Val, const tm& _Time) { + _NODISCARD auto _Write(_FormatContext& _FormatCtx, const _Ty& _Val, const tm& _Time) { basic_stringstream<_CharT> _Stream; if (_No_chrono_specs) { @@ -5543,10 +5638,12 @@ struct _Chrono_formatter { continue; } - // TRANSITION, out of bounds may be legal for non-localized decimal printing, but it isn't very - // consistent. - if (!_Val.ok()) { - _THROW(format_error("Cannot print out-of-bounds time point.")); + if (_Type_needs_bounds_checking(_Spec)) { + if (!_Val.ok()) { + _THROW(format_error("Cannot localize out-of-bounds time point.")); + } + } else if (_Spec._Modifier == '\0' && _CHRONO _Try_simple_write(_Stream, _Spec._Type, _Time)) { + continue; } _CharT _Fmt_str[4]; @@ -5566,18 +5663,35 @@ struct _Chrono_formatter { _Fmt_align::_Left, [&](auto _Out) { return _Fmt_write(_STD move(_Out), _Stream.view()); }); } + _NODISCARD bool _Type_needs_bounds_checking(const _Chrono_specs<_CharT>& _Spec) noexcept { + // [tab:time.format.spec] + switch (_Spec._Type) { + case 'a': + case 'A': + case 'b': + case 'B': + case 'h': + case 'z': + case 'Z': + return true; + + default: + return false; + } + } + _Chrono_format_specs<_CharT> _Specs{}; bool _No_chrono_specs = false; }; template struct formatter<_CHRONO day, _CharT> { - typename basic_format_parse_context<_CharT>::iterator parse(basic_format_parse_context<_CharT>& _Parse_ctx) { - return _Impl._Parse(_Parse_ctx, "de"); + auto parse(basic_format_parse_context<_CharT>& _Parse_ctx) { + return _Impl.template _Parse<_CHRONO day>(_Parse_ctx); } template - typename _FormatContext::iterator format(const _CHRONO day& _Val, _FormatContext& _FormatCtx) { + auto format(const _CHRONO day& _Val, _FormatContext& _FormatCtx) { const tm _Time = {.tm_mday = static_cast(static_cast(_Val))}; return _Impl._Write(_FormatCtx, _Val, _Time); } @@ -5588,17 +5702,51 @@ private: template struct formatter<_CHRONO month, _CharT> { - typename basic_format_parse_context<_CharT>::iterator parse(basic_format_parse_context<_CharT>& _Parse_ctx) { - return _Impl._Parse(_Parse_ctx, "bBhm"); + auto parse(basic_format_parse_context<_CharT>& _Parse_ctx) { + return _Impl.template _Parse<_CHRONO month>(_Parse_ctx); } template - typename _FormatContext::iterator format(const _CHRONO month& _Val, _FormatContext& _FormatCtx) { + auto format(const _CHRONO month& _Val, _FormatContext& _FormatCtx) { // tm_mon is [0, 11] while month is [1, 12] const tm _Time = {.tm_mon = static_cast(static_cast(_Val)) - 1}; return _Impl._Write(_FormatCtx, _Val, _Time); } +private: + _Chrono_formatter<_CharT, false> _Impl; +}; + +template +struct formatter<_CHRONO year, _CharT> { + auto parse(basic_format_parse_context<_CharT>& _Parse_ctx) { + return _Impl.template _Parse<_CHRONO year>(_Parse_ctx); + } + + template + auto format(const _CHRONO year& _Val, _FormatContext& _FormatCtx) { + const tm _Time = {.tm_year = static_cast(_Val) - 1900}; + return _Impl._Write(_FormatCtx, _Val, _Time); + } + +private: + _Chrono_formatter<_CharT, false> _Impl; +}; + +template +struct formatter<_CHRONO year_month_day, _CharT> { + auto parse(basic_format_parse_context<_CharT>& _Parse_ctx) { + return _Impl.template _Parse<_CHRONO year_month_day>(_Parse_ctx); + } + + template + auto format(const _CHRONO year_month_day& _Val, _FormatContext& _FormatCtx) { + const tm _Time = {.tm_mday = static_cast(static_cast(_Val.day())), + .tm_mon = static_cast(static_cast(_Val.month())) - 1, + .tm_year = static_cast(_Val.year()) - 1900}; + return _Impl._Write(_FormatCtx, _Val, _Time); + } + private: _Chrono_formatter<_CharT, false> _Impl; }; diff --git a/tests/libcxx/expected_results.txt b/tests/libcxx/expected_results.txt index 3d5c538b7a..a82a6c1a5b 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -283,10 +283,7 @@ std/utilities/time/time.cal/time.cal.mwdlast/time.cal.mwdlast.nonmembers/streami std/utilities/time/time.cal/time.cal.wdidx/time.cal.wdidx.nonmembers/streaming.pass.cpp FAIL std/utilities/time/time.cal/time.cal.wdlast/time.cal.wdlast.nonmembers/streaming.pass.cpp FAIL std/utilities/time/time.cal/time.cal.weekday/time.cal.weekday.nonmembers/streaming.pass.cpp FAIL -std/utilities/time/time.cal/time.cal.year/time.cal.year.nonmembers/streaming.pass.cpp FAIL std/utilities/time/time.cal/time.cal.ym/time.cal.ym.nonmembers/streaming.pass.cpp FAIL -std/utilities/time/time.cal/time.cal.ymd/time.cal.ymd.nonmembers/streaming.pass.cpp FAIL -std/utilities/time/time.cal/time.cal.ymdlast/time.cal.ymdlast.nonmembers/streaming.pass.cpp FAIL std/utilities/time/time.cal/time.cal.ymwd/time.cal.ymwd.nonmembers/streaming.pass.cpp FAIL std/utilities/time/time.cal/time.cal.ymwdlast/time.cal.ymwdlast.nonmembers/streaming.pass.cpp FAIL @@ -882,3 +879,6 @@ std/utilities/function.objects/func.search/func.search.bmh/pred.pass.cpp PASS # Not yet implemented in libcxx and marked as "XFAIL: *" std/utilities/time/time.cal/time.cal.day/time.cal.day.nonmembers/streaming.pass.cpp SKIPPED std/utilities/time/time.cal/time.cal.month/time.cal.month.nonmembers/streaming.pass.cpp SKIPPED +std/utilities/time/time.cal/time.cal.year/time.cal.year.nonmembers/streaming.pass.cpp SKIPPED +std/utilities/time/time.cal/time.cal.ymd/time.cal.ymd.nonmembers/streaming.pass.cpp SKIPPED +std/utilities/time/time.cal/time.cal.ymdlast/time.cal.ymdlast.nonmembers/streaming.pass.cpp SKIPPED diff --git a/tests/libcxx/skipped_tests.txt b/tests/libcxx/skipped_tests.txt index acda0d4042..eebe1b513e 100644 --- a/tests/libcxx/skipped_tests.txt +++ b/tests/libcxx/skipped_tests.txt @@ -283,10 +283,7 @@ utilities\time\time.cal\time.cal.mwdlast\time.cal.mwdlast.nonmembers\streaming.p utilities\time\time.cal\time.cal.wdidx\time.cal.wdidx.nonmembers\streaming.pass.cpp utilities\time\time.cal\time.cal.wdlast\time.cal.wdlast.nonmembers\streaming.pass.cpp utilities\time\time.cal\time.cal.weekday\time.cal.weekday.nonmembers\streaming.pass.cpp -utilities\time\time.cal\time.cal.year\time.cal.year.nonmembers\streaming.pass.cpp utilities\time\time.cal\time.cal.ym\time.cal.ym.nonmembers\streaming.pass.cpp -utilities\time\time.cal\time.cal.ymd\time.cal.ymd.nonmembers\streaming.pass.cpp -utilities\time\time.cal\time.cal.ymdlast\time.cal.ymdlast.nonmembers\streaming.pass.cpp utilities\time\time.cal\time.cal.ymwd\time.cal.ymwd.nonmembers\streaming.pass.cpp utilities\time\time.cal\time.cal.ymwdlast\time.cal.ymwdlast.nonmembers\streaming.pass.cpp diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index 8c2fccb77c..b115adce82 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -294,7 +294,7 @@ void test_day_formatter() { assert(res == a8); assert(format(STR("{:%d %d %d}"), day{27}) == STR("27 27 27")); - throw_helper(STR("{:%d}"), day{200}); + assert(format(STR("{:%d}"), day{200}) == STR("200")); throw_helper(STR("{:%Ed}"), day{10}); assert(format(STR("{}"), day{0}) == STR("00 is not a valid day")); @@ -316,10 +316,10 @@ void test_month_formatter() { assert(format(STR("{:%m %Om}"), month{1}) == STR("01 01")); // Out of bounds month - throw_helper(STR("{:%m}"), month{0}); + assert(format(STR("{:%m}"), month{0}) == STR("00")); throw_helper(STR("{:%b}"), month{0}); throw_helper(STR("{:%h}"), month{0}); - throw_helper(STR("{::%B}"), month{0}); + throw_helper(STR("{:%B}"), month{0}); // Invalid specs throw_helper(STR("{:%A}"), month{1}); @@ -332,6 +332,32 @@ void test_month_formatter() { stream_helper(STR("20 is not a valid month"), month{20}); } +template +void test_year_formatter() { + assert(format(STR("{}"), year{0}) == STR("0000")); + assert(format(STR("{}"), year{-200}) == STR("-0200")); + assert(format(STR("{}"), year{121}) == STR("0121")); + + assert(format(STR("{:%Y %y%C}"), year{1912}) == STR("1912 1219")); + assert(format(STR("{:%Y %y%C}"), year{-1912}) == STR("-1912 88-20")); + // TRANSITION, add tests for EY Oy Ey EC + + stream_helper(STR("1900"), year{1900}); + stream_helper(STR("2000"), year{2000}); + stream_helper(STR("-32768 is not a valid year"), year{-32768}); +} + +template +void test_year_month_day_formatter() { + year_month_day invalid{year{1234}, month{0}, day{31}}; + assert(format(STR("{}"), year_month_day{year{1900}, month{2}, day{1}}) == STR("1900-02-01")); + stream_helper(STR("1900-02-01"), year_month_day{year{1900}, month{2}, day{1}}); + stream_helper(STR("1234-00-31 is not a valid date"), invalid); + + assert(format(STR("{:%Y %b %d}"), year_month_day{year{1234}, month{5}, day{6}}) == STR("1234 May 06")); + assert(format(STR("{:%F %D}"), invalid) == STR("1234-00-31 00/31/34")); +} + int main() { test_parse_conversion_spec(); test_parse_conversion_spec(); @@ -344,4 +370,10 @@ int main() { test_month_formatter(); test_month_formatter(); + + test_year_formatter(); + test_year_formatter(); + + test_year_month_day_formatter(); + test_year_month_day_formatter(); } From 206727eebb6c0be49e417b844401ee34429e83e0 Mon Sep 17 00:00:00 2001 From: Elnar D Date: Thu, 15 Apr 2021 15:04:34 -0700 Subject: [PATCH 05/41] Add hh_mm_ss operator<< --- stl/inc/chrono | 45 ++++++++++++++++++- .../test.cpp | 11 +++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 773cb5bb6f..99aa6e8573 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -2204,6 +2204,12 @@ namespace chrono { precision _Sub_secs; }; + template + constexpr inline bool _Is_hh_mm_ss = false; + + template + constexpr inline bool _Is_hh_mm_ss> = true; + _NODISCARD constexpr bool is_am(const hours& _Hours) noexcept { return _Hours >= hours{0} && _Hours <= hours{11}; } @@ -5464,6 +5470,16 @@ namespace chrono { } } + template + void _Write_seconds(basic_ostream<_CharT, _Traits>& _Os, const hh_mm_ss<_Duration>& _Val) { + _Os << format(_STATICALLY_WIDEN(_CharT, "{:02}"), _Val.seconds().count()); + if constexpr (_Val.fractional_width > 0) { + _Os << format(_STATICALLY_WIDEN(_CharT, "{0}{1:0{2}}"), + _STD use_facet>(_Os.getloc()).decimal_point(), _Val.subseconds().count(), + _Val.fractional_width); + } + } + /* template // clang-format off @@ -5527,6 +5543,18 @@ namespace chrono { } return _Os; } + + template + basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const hh_mm_ss<_Duration>& _Val) { + const tm _Time = { + .tm_min = static_cast(_Val.minutes().count()), .tm_hour = static_cast(_Val.hours().count())}; + if (_Val.is_negative()) { + _Os << _CharT{'-'}; + } + _Os << _STD put_time(&_Time, _STATICALLY_WIDEN(_CharT, "%H:%M:")); + _Write_seconds(_Os, _Val); + return _Os; + } } // namespace chrono /* @@ -5632,6 +5660,12 @@ struct _Chrono_formatter { _Stream << _Val; } else { _Stream.imbue(_FormatCtx.locale()); + if constexpr (_Is_hh_mm_ss<_Ty>) { + if (_Val.is_negative()) { + _Stream << _CharT{'-'}; + } + } + for (const auto& _Spec : _Specs._Chrono_specs_list) { if (_Spec._Lit_char != _CharT{'\0'}) { _Stream << _Spec._Lit_char; @@ -5642,8 +5676,15 @@ struct _Chrono_formatter { if (!_Val.ok()) { _THROW(format_error("Cannot localize out-of-bounds time point.")); } - } else if (_Spec._Modifier == '\0' && _CHRONO _Try_simple_write(_Stream, _Spec._Type, _Time)) { - continue; + } else if (_Spec._Modifier == '\0') { + if (_CHRONO _Try_simple_write(_Stream, _Spec._Type, _Time)) { + continue; + } else if constexpr (_Is_hh_mm_ss<_Ty>) { + if (_Spec._Type == 'S') { + _CHRONO _Write_seconds(_Stream, _Val); + continue; + } + } } _CharT _Fmt_str[4]; diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index b115adce82..34e5fbd705 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -358,6 +358,14 @@ void test_year_month_day_formatter() { assert(format(STR("{:%F %D}"), invalid) == STR("1234-00-31 00/31/34")); } +template +void test_hh_mm_ss_formatter() { + stream_helper(STR("-01:08:03.007"), hh_mm_ss{-4083007ms}); + stream_helper(STR("01:08:03.007"), hh_mm_ss{4083007ms}); + stream_helper(STR("18:15:45.123"), hh_mm_ss{65745123ms}); + stream_helper(STR("18:15:45"), hh_mm_ss{65745s}); +} + int main() { test_parse_conversion_spec(); test_parse_conversion_spec(); @@ -376,4 +384,7 @@ int main() { test_year_month_day_formatter(); test_year_month_day_formatter(); + + test_hh_mm_ss_formatter(); + test_hh_mm_ss_formatter(); } From 1cb654b50b8f1686a45401425d153f8c30513389 Mon Sep 17 00:00:00 2001 From: Elnar D Date: Thu, 15 Apr 2021 15:22:12 -0700 Subject: [PATCH 06/41] Fix tests --- stl/inc/chrono | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 99aa6e8573..6b6d49adfc 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5473,7 +5473,7 @@ namespace chrono { template void _Write_seconds(basic_ostream<_CharT, _Traits>& _Os, const hh_mm_ss<_Duration>& _Val) { _Os << format(_STATICALLY_WIDEN(_CharT, "{:02}"), _Val.seconds().count()); - if constexpr (_Val.fractional_width > 0) { + if constexpr (hh_mm_ss<_Duration>::fractional_width > 0) { _Os << format(_STATICALLY_WIDEN(_CharT, "{0}{1:0{2}}"), _STD use_facet>(_Os.getloc()).decimal_point(), _Val.subseconds().count(), _Val.fractional_width); @@ -5660,7 +5660,7 @@ struct _Chrono_formatter { _Stream << _Val; } else { _Stream.imbue(_FormatCtx.locale()); - if constexpr (_Is_hh_mm_ss<_Ty>) { + if constexpr (_CHRONO _Is_hh_mm_ss<_Ty>) { if (_Val.is_negative()) { _Stream << _CharT{'-'}; } @@ -5679,7 +5679,7 @@ struct _Chrono_formatter { } else if (_Spec._Modifier == '\0') { if (_CHRONO _Try_simple_write(_Stream, _Spec._Type, _Time)) { continue; - } else if constexpr (_Is_hh_mm_ss<_Ty>) { + } else if constexpr (_CHRONO _Is_hh_mm_ss<_Ty>) { if (_Spec._Type == 'S') { _CHRONO _Write_seconds(_Stream, _Val); continue; From e7b705adbb38f91318c69d219f307ef8a54a01e2 Mon Sep 17 00:00:00 2001 From: Miya Natsuhara Date: Thu, 15 Apr 2021 16:14:06 -0700 Subject: [PATCH 07/41] ymd has info for weekday too --- stl/inc/chrono | 20 +++++++++++++++++-- .../test.cpp | 1 + 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 6b6d49adfc..b3872a1d0b 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -1433,6 +1433,18 @@ namespace chrono { return _Day >= _CHRONO day{1} && _Day <= _Last_day(_Year, _Month); } + _NODISCARD constexpr int _Calculate_weekday() const noexcept { + const int _Day_int = static_cast(static_cast(_Day)); + const int _Month_int = static_cast(static_cast(_Month)); + + const int _Era_year = static_cast(_Year) - (_Month_int <= 2); + const int _Era = (_Era_year >= 0 ? _Era_year : _Era_year - 399) / 400; + const int _Yoe = _Era_year - _Era * 400; + const int _Yday_era = ((979 * (_Month_int + (_Month_int > 2 ? -3 : 9)) + 19) >> 5) + _Day_int - 1; + const int _Doe = ((1461 * _Yoe) >> 2) - _Yoe / 100 + _Yday_era; + return (_Doe + 3) % 7; // the era began on a Wednesday + } + private: _CHRONO year _Year; _CHRONO month _Month; @@ -5643,9 +5655,12 @@ struct _Chrono_formatter { return _Type == 'b' || _Type == 'B' || _Type == 'h' || _Type == 'm'; } else if constexpr (is_same_v<_Ty, _CHRONO year>) { return _Type == 'Y' || _Type == 'y' || _Type == 'C'; + } else if constexpr (is_same_v<_Ty, _CHRONO weekday>) { + return _Type == 'a' || _Type == 'A' || _Type == 'u' || _Type == 'w'; } else if constexpr (is_same_v<_Ty, _CHRONO year_month_day>) { return _Type == 'D' || _Type == 'F' || _Is_valid_type<_CHRONO year>(_Type) - || _Is_valid_type<_CHRONO month>(_Type) || _Is_valid_type<_CHRONO day>(_Type); + || _Is_valid_type<_CHRONO month>(_Type) || _Is_valid_type<_CHRONO day>(_Type) + || _Is_valid_type<_CHRONO weekday>(_Type); } else { // TRANSITION, remove when all types are added static_assert(_Always_false<_Ty>, "unsupported type"); @@ -5784,7 +5799,8 @@ struct formatter<_CHRONO year_month_day, _CharT> { auto format(const _CHRONO year_month_day& _Val, _FormatContext& _FormatCtx) { const tm _Time = {.tm_mday = static_cast(static_cast(_Val.day())), .tm_mon = static_cast(static_cast(_Val.month())) - 1, - .tm_year = static_cast(_Val.year()) - 1900}; + .tm_year = static_cast(_Val.year()) - 1900, + .tm_wday = _Val._Calculate_weekday()}; return _Impl._Write(_FormatCtx, _Val, _Time); } diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index 34e5fbd705..c414bd2110 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -356,6 +356,7 @@ void test_year_month_day_formatter() { assert(format(STR("{:%Y %b %d}"), year_month_day{year{1234}, month{5}, day{6}}) == STR("1234 May 06")); assert(format(STR("{:%F %D}"), invalid) == STR("1234-00-31 00/31/34")); + assert(format(STR("{:%a %A}"), year_month_day{year{1900}, month{1}, day{4}}) == STR("Thu Thursday")); } template From 5505ea314629d429629c407bb50022c9b8667f80 Mon Sep 17 00:00:00 2001 From: Miya Natsuhara Date: Thu, 15 Apr 2021 16:20:00 -0700 Subject: [PATCH 08/41] add test for u w --- .../tests/P0355R7_calendars_and_time_zones_formatting/test.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index c414bd2110..4df9eefa16 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -357,6 +357,7 @@ void test_year_month_day_formatter() { assert(format(STR("{:%Y %b %d}"), year_month_day{year{1234}, month{5}, day{6}}) == STR("1234 May 06")); assert(format(STR("{:%F %D}"), invalid) == STR("1234-00-31 00/31/34")); assert(format(STR("{:%a %A}"), year_month_day{year{1900}, month{1}, day{4}}) == STR("Thu Thursday")); + assert(format(STR("{:%u %w}"), year_month_day{year{1900}, month{1}, day{4}}) == STR("4 4")); } template From 4c236a3ea19074c0a402a1595cd4d54e8c16d680 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Thu, 15 Apr 2021 19:46:50 -0700 Subject: [PATCH 09/41] Various cleanups. (#1842) --- stl/inc/chrono | 66 ++++++++----------- .../test.cpp | 4 +- 2 files changed, 29 insertions(+), 41 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index b3872a1d0b..ba820bfe33 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -11,7 +11,6 @@ #include #include #include -#include #include #include @@ -22,10 +21,10 @@ #include #include #include -#include #include #include #include +#include #include #include #include @@ -34,8 +33,7 @@ #ifdef __cpp_lib_concepts #include #include -#include -#include +#include #endif // defined(__cpp_lib_concepts) #endif // _HAS_CXX20 @@ -2216,12 +2214,6 @@ namespace chrono { precision _Sub_secs; }; - template - constexpr inline bool _Is_hh_mm_ss = false; - - template - constexpr inline bool _Is_hh_mm_ss> = true; - _NODISCARD constexpr bool is_am(const hours& _Hours) noexcept { return _Hours >= hours{0} && _Hours <= hours{11}; } @@ -5207,17 +5199,15 @@ namespace chrono { // [time.format] template -struct _Choose_literal { - static constexpr const _CharT* _Choose(const char* _Str, const wchar_t* _WStr) { - if constexpr (is_same_v<_CharT, char>) { - return _Str; - } else { - return _WStr; - } +_NODISCARD constexpr const _CharT* _Choose_literal(const char* const _Str, const wchar_t* const _WStr) noexcept { + if constexpr (is_same_v<_CharT, char>) { + return _Str; + } else { + return _WStr; } -}; +} -#define _STATICALLY_WIDEN(_CharT, _Literal) (_Choose_literal<_CharT>::_Choose(_Literal, L##_Literal)) +#define _STATICALLY_WIDEN(_CharT, _Literal) (_Choose_literal<_CharT>(_Literal, L##_Literal)) // clang-format off @@ -5291,7 +5281,7 @@ public: _Specs._Dynamic_width_index = _Verify_dynamic_arg_index_in_range(_Arg_id); } - constexpr void _On_dynamic_width(const _Auto_id_tag) { + constexpr void _On_dynamic_width(_Auto_id_tag) { _Specs._Dynamic_width_index = _Verify_dynamic_arg_index_in_range(_Parse_ctx.next_arg_id()); } @@ -5300,7 +5290,7 @@ public: _Specs._Dynamic_precision_index = _Verify_dynamic_arg_index_in_range(_Arg_id); } - constexpr void _On_dynamic_precision(const _Auto_id_tag) { + constexpr void _On_dynamic_precision(_Auto_id_tag) { _Specs._Dynamic_precision_index = _Verify_dynamic_arg_index_in_range(_Parse_ctx.next_arg_id()); } @@ -5323,10 +5313,8 @@ public: _Specs._Chrono_specs_list.push_back(_Lit_char_spec); } -protected: - _Chrono_format_specs<_CharT>& _Specs; - private: + _Chrono_format_specs<_CharT>& _Specs; _ParseContext& _Parse_ctx; _NODISCARD static constexpr int _Verify_dynamic_arg_index_in_range(const size_t _Idx) { @@ -5387,8 +5375,8 @@ _NODISCARD constexpr const _CharT* _Parse_chrono_format_specs( } } - if (_Begin != _End && *_Begin != '}' && *_Begin != '%') { - _THROW(format_error("Invalid format string - chrono-specs must begin with conversion-specs")); + if (*_Begin != '}' && *_Begin != '%') { + _THROW(format_error("Invalid format string - chrono-specs must begin with conversion-spec")); } // chrono-spec @@ -5398,8 +5386,7 @@ _NODISCARD constexpr const _CharT* _Parse_chrono_format_specs( _THROW(format_error("Invalid format string - missing type after %")); } - _CharT _Next_ch = *_Begin; - switch (_Next_ch) { + switch (*_Begin) { case 'n': _Callbacks._On_lit_char('\n'); ++_Begin; @@ -5484,9 +5471,9 @@ namespace chrono { template void _Write_seconds(basic_ostream<_CharT, _Traits>& _Os, const hh_mm_ss<_Duration>& _Val) { - _Os << format(_STATICALLY_WIDEN(_CharT, "{:02}"), _Val.seconds().count()); + _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:02}"), _Val.seconds().count()); if constexpr (hh_mm_ss<_Duration>::fractional_width > 0) { - _Os << format(_STATICALLY_WIDEN(_CharT, "{0}{1:0{2}}"), + _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{0}{1:0{2}}"), _STD use_facet>(_Os.getloc()).decimal_point(), _Val.subseconds().count(), _Val.fractional_width); } @@ -5633,7 +5620,7 @@ struct _Chrono_formatter { _Allowed_bit _Allowed; }; - static const _Table_entry _Table[] = { + static constexpr _Table_entry _Table[] = { {'d', _O_mod}, {'e', _O_mod}, {'m', _O_mod}, {'Y', _E_mod}, {'y', _EO_mod}, {'C', _E_mod}}; const _Allowed_bit _Mod = _Modifier == 'E' ? _E_mod : _O_mod; @@ -5669,13 +5656,13 @@ struct _Chrono_formatter { template _NODISCARD auto _Write(_FormatContext& _FormatCtx, const _Ty& _Val, const tm& _Time) { - basic_stringstream<_CharT> _Stream; + basic_ostringstream<_CharT> _Stream; if (_No_chrono_specs) { _Stream << _Val; } else { _Stream.imbue(_FormatCtx.locale()); - if constexpr (_CHRONO _Is_hh_mm_ss<_Ty>) { + if constexpr (_Is_specialization_v<_Ty, _CHRONO hh_mm_ss>) { if (_Val.is_negative()) { _Stream << _CharT{'-'}; } @@ -5694,7 +5681,7 @@ struct _Chrono_formatter { } else if (_Spec._Modifier == '\0') { if (_CHRONO _Try_simple_write(_Stream, _Spec._Type, _Time)) { continue; - } else if constexpr (_CHRONO _Is_hh_mm_ss<_Ty>) { + } else if constexpr (_Is_specialization_v<_Ty, _CHRONO hh_mm_ss>) { if (_Spec._Type == 'S') { _CHRONO _Write_seconds(_Stream, _Val); continue; @@ -5864,8 +5851,7 @@ inline namespace literals { return _CHRONO duration(_Val); } - _NODISCARD constexpr _CHRONO milliseconds operator"" ms(unsigned long long _Val) noexcept - /* strengthened */ { + _NODISCARD constexpr _CHRONO milliseconds operator"" ms(unsigned long long _Val) noexcept /* strengthened */ { return _CHRONO milliseconds(_Val); } @@ -5874,8 +5860,7 @@ inline namespace literals { return _CHRONO duration(_Val); } - _NODISCARD constexpr _CHRONO microseconds operator"" us(unsigned long long _Val) noexcept - /* strengthened */ { + _NODISCARD constexpr _CHRONO microseconds operator"" us(unsigned long long _Val) noexcept /* strengthened */ { return _CHRONO microseconds(_Val); } @@ -5884,8 +5869,7 @@ inline namespace literals { return _CHRONO duration(_Val); } - _NODISCARD constexpr _CHRONO nanoseconds operator"" ns(unsigned long long _Val) noexcept - /* strengthened */ { + _NODISCARD constexpr _CHRONO nanoseconds operator"" ns(unsigned long long _Val) noexcept /* strengthened */ { return _CHRONO nanoseconds(_Val); } @@ -5908,6 +5892,8 @@ namespace chrono { using namespace literals::chrono_literals; } // namespace chrono +#undef _STATICALLY_WIDEN + _STD_END #pragma pop_macro("new") _STL_RESTORE_CLANG_WARNINGS diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index 4df9eefa16..709ebbee1a 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -205,12 +206,13 @@ void throw_helper(const charT* fmt, const Args&... vals) { template void stream_helper(const charT* expect, const Args&... vals) { - basic_stringstream stream; + basic_ostringstream stream; (stream << ... << vals); assert(stream.str() == expect); assert(stream); } +// FIXME: TEMPORARY CODE FOR WRITING TESTS, REMOVE BEFORE MERGING template constexpr void print(Str str) { if constexpr (is_same_v) { From 5bf0c50b1dc5188e77eb669ac8a31bf70c64bfef Mon Sep 17 00:00:00 2001 From: Elnar D Date: Fri, 16 Apr 2021 00:06:35 -0700 Subject: [PATCH 10/41] Modifier table update --- stl/inc/chrono | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index ba820bfe33..505b023a37 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5621,7 +5621,25 @@ struct _Chrono_formatter { }; static constexpr _Table_entry _Table[] = { - {'d', _O_mod}, {'e', _O_mod}, {'m', _O_mod}, {'Y', _E_mod}, {'y', _EO_mod}, {'C', _E_mod}}; + {'Y', _E_mod}, + {'y', _EO_mod}, + {'C', _E_mod}, + {'m', _O_mod}, + {'U', _O_mod}, + {'W', _O_mod}, + {'V', _O_mod}, + {'d', _O_mod}, + {'e', _O_mod}, + {'w', _O_mod}, + {'u', _O_mod}, + {'H', _O_mod}, + {'I', _O_mod}, + {'M', _O_mod}, + {'S', _O_mod}, + {'c', _E_mod}, + {'x', _E_mod}, + {'X', _E_mod}, + }; const _Allowed_bit _Mod = _Modifier == 'E' ? _E_mod : _O_mod; From 1187292cfdc86fabecf43a97b17fe60f2b31564a Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Thu, 15 Apr 2021 19:52:39 -0700 Subject: [PATCH 11/41] Update the modifier table, use uint8_t. This follows N4885 [tab:time.format.spec]'s order, and adds {'z', _EO_mod} which was missing. --- stl/inc/chrono | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 505b023a37..6b9941b9b8 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5613,7 +5613,7 @@ struct _Chrono_formatter { return; } - enum _Allowed_bit { _E_mod = 1, _O_mod = 2, _EO_mod = _E_mod | _O_mod }; + enum _Allowed_bit : uint8_t { _E_mod = 1, _O_mod = 2, _EO_mod = _E_mod | _O_mod }; struct _Table_entry { char _Type; @@ -5621,24 +5621,25 @@ struct _Chrono_formatter { }; static constexpr _Table_entry _Table[] = { - {'Y', _E_mod}, - {'y', _EO_mod}, + {'c', _E_mod}, {'C', _E_mod}, - {'m', _O_mod}, - {'U', _O_mod}, - {'W', _O_mod}, - {'V', _O_mod}, {'d', _O_mod}, {'e', _O_mod}, - {'w', _O_mod}, - {'u', _O_mod}, {'H', _O_mod}, {'I', _O_mod}, + {'m', _O_mod}, {'M', _O_mod}, {'S', _O_mod}, - {'c', _E_mod}, + {'u', _O_mod}, + {'U', _O_mod}, + {'V', _O_mod}, + {'w', _O_mod}, + {'W', _O_mod}, {'x', _E_mod}, {'X', _E_mod}, + {'y', _EO_mod}, + {'Y', _E_mod}, + {'z', _EO_mod}, }; const _Allowed_bit _Mod = _Modifier == 'E' ? _E_mod : _O_mod; From 838cd1f65fbd3d5fcdf00e6014a616bff222fad4 Mon Sep 17 00:00:00 2001 From: Elnar D Date: Fri, 16 Apr 2021 00:48:05 -0700 Subject: [PATCH 12/41] Robuster OOB handling --- stl/inc/chrono | 28 ++++--------------- .../test.cpp | 1 + 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 6b9941b9b8..f3b71ee620 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5693,11 +5693,8 @@ struct _Chrono_formatter { continue; } - if (_Type_needs_bounds_checking(_Spec)) { - if (!_Val.ok()) { - _THROW(format_error("Cannot localize out-of-bounds time point.")); - } - } else if (_Spec._Modifier == '\0') { + // For non-localized writes, we can sometimes avoid calling put_time. + if (_Spec._Modifier == '\0') { if (_CHRONO _Try_simple_write(_Stream, _Spec._Type, _Time)) { continue; } else if constexpr (_Is_specialization_v<_Ty, _CHRONO hh_mm_ss>) { @@ -5707,6 +5704,10 @@ struct _Chrono_formatter { } } } + // Otherwise, we should throw to avoid triggering asserts within put_time machinery. + if (!_Val.ok()) { + _THROW(format_error("Cannot localize out-of-bounds time point.")); + } _CharT _Fmt_str[4]; size_t _Next_idx = 0; @@ -5725,23 +5726,6 @@ struct _Chrono_formatter { _Fmt_align::_Left, [&](auto _Out) { return _Fmt_write(_STD move(_Out), _Stream.view()); }); } - _NODISCARD bool _Type_needs_bounds_checking(const _Chrono_specs<_CharT>& _Spec) noexcept { - // [tab:time.format.spec] - switch (_Spec._Type) { - case 'a': - case 'A': - case 'b': - case 'B': - case 'h': - case 'z': - case 'Z': - return true; - - default: - return false; - } - } - _Chrono_format_specs<_CharT> _Specs{}; bool _No_chrono_specs = false; }; diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index 709ebbee1a..4bd8330122 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -298,6 +298,7 @@ void test_day_formatter() { assert(format(STR("{:%d %d %d}"), day{27}) == STR("27 27 27")); assert(format(STR("{:%d}"), day{200}) == STR("200")); throw_helper(STR("{:%Ed}"), day{10}); + throw_helper(STR("{:%Od}"), day{40}); assert(format(STR("{}"), day{0}) == STR("00 is not a valid day")); // Op << From 3a19af78482d22fc62cb3b190d7235d11c05ac35 Mon Sep 17 00:00:00 2001 From: Elnar D Date: Fri, 16 Apr 2021 02:41:22 -0700 Subject: [PATCH 13/41] Add general time filler --- stl/inc/chrono | 89 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 65 insertions(+), 24 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index f3b71ee620..3a8b98caf9 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5500,10 +5500,65 @@ namespace chrono { return _Os << format(_STATICALLY_WIDEN(_CharT, "{:%F %T}"), _Clock); }*/ + template + concept _Has_day = requires(const _Ty& _At) { + { _At.day() } + ->same_as; + }; + + template + concept _Has_month = requires(const _Ty& _At) { + { _At.month() } + ->same_as; + }; + + template + concept _Has_year = requires(const _Ty& _At) { + { _At.year() } + ->same_as; + }; + + template + tm _Fill_tm(const _Ty& _Val) { + unsigned int _Day = 0; + unsigned int _Month = 0; + int _Year = 0; + int _Weekday = 0; + int _Hours = 0; + int _Minutes = 0; + int _Seconds = 0; + + if constexpr (is_same_v<_Ty, day>) { + _Day = static_cast(_Val); + } else if constexpr (is_same_v<_Ty, month>) { + _Month = static_cast(_Val); + } else if constexpr (is_same_v<_Ty, year>) { + _Year = static_cast(_Val); + } else if constexpr (is_same_v<_Ty, year_month_day>) { + _Day = static_cast(_Val.day()); + _Month = static_cast(_Val.month()); + _Year = static_cast(_Val.year()); + _Weekday = _Val._Calculate_weekday(); + } else if constexpr (_Is_specialization_v<_Ty, hh_mm_ss>) { + _Hours = _Val.hours().count(); + _Minutes = _Val.minutes().count(); + _Seconds = static_cast(_Val.seconds().count()); + } + + tm _Time; + _Time.tm_sec = _Seconds; + _Time.tm_min = _Minutes; + _Time.tm_hour = _Hours; + _Time.tm_mday = static_cast(_Day); + _Time.tm_mon = static_cast(_Month) - 1; + _Time.tm_year = _Year - 1900; + _Time.tm_wday = _Weekday; + return _Time; + } + template basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const day& _Val) { - const tm _Time = {.tm_mday = static_cast(static_cast(_Val))}; - _Try_simple_write(_Os, 'd', _Time); + _Try_simple_write(_Os, 'd', _Fill_tm(_Val)); if (!_Val.ok()) { _Os << _STATICALLY_WIDEN(_CharT, " is not a valid day"); } @@ -5514,8 +5569,7 @@ namespace chrono { template basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const month& _Val) { if (_Val.ok()) { - // tm_mon is [0, 11] while month is [1, 12] - const tm _Time = {.tm_mon = static_cast(static_cast(_Val)) - 1}; + const tm _Time = _Fill_tm(_Val); return _Os << _STD put_time(&_Time, _STATICALLY_WIDEN(_CharT, "%b")); } return _Os << static_cast(_Val) << _STATICALLY_WIDEN(_CharT, " is not a valid month"); @@ -5523,8 +5577,7 @@ namespace chrono { template basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const year& _Val) { - const tm _Time = {.tm_year = static_cast(_Val) - 1900}; - _Try_simple_write(_Os, 'Y', _Time); + _Try_simple_write(_Os, 'Y', _Fill_tm(_Val)); if (!_Val.ok()) { _Os << _STATICALLY_WIDEN(_CharT, " is not a valid year"); } @@ -5533,10 +5586,7 @@ namespace chrono { template basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const year_month_day& _Val) { - const tm _Time = {.tm_mday = static_cast(static_cast(_Val.day())), - .tm_mon = static_cast(static_cast(_Val.month())) - 1, - .tm_year = static_cast(_Val.year()) - 1900}; - _Try_simple_write(_Os, 'F', _Time); + _Try_simple_write(_Os, 'F', _Fill_tm(_Val)); if (!_Val.ok()) { _Os << _STATICALLY_WIDEN(_CharT, " is not a valid date"); } @@ -5545,8 +5595,7 @@ namespace chrono { template basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const hh_mm_ss<_Duration>& _Val) { - const tm _Time = { - .tm_min = static_cast(_Val.minutes().count()), .tm_hour = static_cast(_Val.hours().count())}; + const tm _Time = _Fill_tm(_Val); if (_Val.is_negative()) { _Os << _CharT{'-'}; } @@ -5738,8 +5787,7 @@ struct formatter<_CHRONO day, _CharT> { template auto format(const _CHRONO day& _Val, _FormatContext& _FormatCtx) { - const tm _Time = {.tm_mday = static_cast(static_cast(_Val))}; - return _Impl._Write(_FormatCtx, _Val, _Time); + return _Impl._Write(_FormatCtx, _Val, _Fill_tm(_Val)); } private: @@ -5754,9 +5802,7 @@ struct formatter<_CHRONO month, _CharT> { template auto format(const _CHRONO month& _Val, _FormatContext& _FormatCtx) { - // tm_mon is [0, 11] while month is [1, 12] - const tm _Time = {.tm_mon = static_cast(static_cast(_Val)) - 1}; - return _Impl._Write(_FormatCtx, _Val, _Time); + return _Impl._Write(_FormatCtx, _Val, _Fill_tm(_Val)); } private: @@ -5771,8 +5817,7 @@ struct formatter<_CHRONO year, _CharT> { template auto format(const _CHRONO year& _Val, _FormatContext& _FormatCtx) { - const tm _Time = {.tm_year = static_cast(_Val) - 1900}; - return _Impl._Write(_FormatCtx, _Val, _Time); + return _Impl._Write(_FormatCtx, _Val, _Fill_tm(_Val)); } private: @@ -5787,11 +5832,7 @@ struct formatter<_CHRONO year_month_day, _CharT> { template auto format(const _CHRONO year_month_day& _Val, _FormatContext& _FormatCtx) { - const tm _Time = {.tm_mday = static_cast(static_cast(_Val.day())), - .tm_mon = static_cast(static_cast(_Val.month())) - 1, - .tm_year = static_cast(_Val.year()) - 1900, - .tm_wday = _Val._Calculate_weekday()}; - return _Impl._Write(_FormatCtx, _Val, _Time); + return _Impl._Write(_FormatCtx, _Val, _Fill_tm(_Val)); } private: From 889fe20c7f2a6a2a6071e19e54d006cfa75cf633 Mon Sep 17 00:00:00 2001 From: Elnar D Date: Fri, 16 Apr 2021 02:45:05 -0700 Subject: [PATCH 14/41] Remove failed type checks --- stl/inc/chrono | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 3a8b98caf9..b7ccebc18d 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5500,24 +5500,6 @@ namespace chrono { return _Os << format(_STATICALLY_WIDEN(_CharT, "{:%F %T}"), _Clock); }*/ - template - concept _Has_day = requires(const _Ty& _At) { - { _At.day() } - ->same_as; - }; - - template - concept _Has_month = requires(const _Ty& _At) { - { _At.month() } - ->same_as; - }; - - template - concept _Has_year = requires(const _Ty& _At) { - { _At.year() } - ->same_as; - }; - template tm _Fill_tm(const _Ty& _Val) { unsigned int _Day = 0; From 7ee91b1d2ab28c015b31696259e2941be8a8205e Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Fri, 16 Apr 2021 02:34:05 -0700 Subject: [PATCH 15/41] Add/centralize formatters, replace operators. * Mark _Fill_tm() as _NODISCARD. * Teach _Fill_tm() to handle year_month_day_MEOW by directly extracting components when possible, and constructing a year_month_day{} temporary only when necessary. * Replace all of the operator<<() implementations for calendrical types with their "Effects Equivalent To" implementations from the Standard (modified to follow our conventions and actually compile). This should handle the setw(8) case mentioned by P1361R2 section 6 item 5, whereas implementing operator<<() by calling other operator<<() is (apparently) doomed. * Note that each operator<<() won't compile until all of the formatters that it depends on have been implemented. * Teach _Is_valid_type() to accept year_month_day_MEOW. * Now that _Fill_tm() centralizes the "decompose a calendrical type into its components" logic, we can further centralize the formatter definitions with _Fill_tm_formatter. Unlike the operator<<() overloads (where I added all types), I'm not adding formatters for unimplemented types here. When they're implemented, they should also be able to use _Fill_tm_formatter, unless they need unusual processing beyond what _Fill_tm() provides. * In P0355R7_calendars_and_time_zones_formatting/test.cpp, add placeholder tests, and comment out test_hh_mm_ss_formatter()'s implementation for now. --- stl/inc/chrono | 183 +++++++++++------- .../test.cpp | 27 +++ 2 files changed, 142 insertions(+), 68 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index b7ccebc18d..834b444765 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5501,7 +5501,7 @@ namespace chrono { }*/ template - tm _Fill_tm(const _Ty& _Val) { + _NODISCARD tm _Fill_tm(const _Ty& _Val) { unsigned int _Day = 0; unsigned int _Month = 0; int _Year = 0; @@ -5521,6 +5521,21 @@ namespace chrono { _Month = static_cast(_Val.month()); _Year = static_cast(_Val.year()); _Weekday = _Val._Calculate_weekday(); + } else if constexpr (is_same_v<_Ty, year_month_day_last>) { + _Day = static_cast(_Val.day()); + _Month = static_cast(_Val.month()); + _Year = static_cast(_Val.year()); + _Weekday = year_month_day{_Val}._Calculate_weekday(); + } else if constexpr (is_same_v<_Ty, year_month_weekday>) { + _Day = static_cast(year_month_day{_Val}.day()); + _Month = static_cast(_Val.month()); + _Year = static_cast(_Val.year()); + _Weekday = static_cast(_Val.weekday().c_encoding()); + } else if constexpr (is_same_v<_Ty, year_month_weekday_last>) { + _Day = static_cast(year_month_day{_Val}.day()); + _Month = static_cast(_Val.month()); + _Year = static_cast(_Val.year()); + _Weekday = static_cast(_Val.weekday().c_encoding()); } else if constexpr (_Is_specialization_v<_Ty, hh_mm_ss>) { _Hours = _Val.hours().count(); _Minutes = _Val.minutes().count(); @@ -5540,50 +5555,97 @@ namespace chrono { template basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const day& _Val) { - _Try_simple_write(_Os, 'd', _Fill_tm(_Val)); - if (!_Val.ok()) { - _Os << _STATICALLY_WIDEN(_CharT, " is not a valid day"); - } - - return _Os; + return _Os << (_Val.ok() ? _STD format(_STATICALLY_WIDEN(_CharT, "{:%d}"), _Val) + : _STD format(_STATICALLY_WIDEN(_CharT, "{:%d} is not a valid day"), _Val)); } template basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const month& _Val) { - if (_Val.ok()) { - const tm _Time = _Fill_tm(_Val); - return _Os << _STD put_time(&_Time, _STATICALLY_WIDEN(_CharT, "%b")); - } - return _Os << static_cast(_Val) << _STATICALLY_WIDEN(_CharT, " is not a valid month"); + return _Os << (_Val.ok() ? _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:%b}"), _Val) + : _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{} is not a valid month"), + static_cast(_Val))); } template basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const year& _Val) { - _Try_simple_write(_Os, 'Y', _Fill_tm(_Val)); - if (!_Val.ok()) { - _Os << _STATICALLY_WIDEN(_CharT, " is not a valid year"); - } - return _Os; + return _Os << (_Val.ok() ? _STD format(_STATICALLY_WIDEN(_CharT, "{:%Y}"), _Val) + : _STD format(_STATICALLY_WIDEN(_CharT, "{:%Y} is not a valid year"), _Val)); + } + + template + basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const weekday& _Val) { + return _Os << (_Val.ok() ? _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:%a}"), _Val) + : _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{} is not a valid weekday"), + _Val.c_encoding())); + } + + template + basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const weekday_indexed& _Val) { + const auto _Idx = _Val.index(); + return _Os << (_Idx >= 1 && _Idx <= 5 + ? _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{}[{}]"), _Val.weekday(), _Idx) + : _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{}[{} is not a valid index]"), + _Val.weekday(), _Idx)); + } + + template + basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const weekday_last& _Val) { + return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{}[last]"), _Val.weekday()); + } + + template + basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const month_day& _Val) { + return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{}/{}"), _Val.month(), _Val.day()); + } + + template + basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const month_day_last& _Val) { + return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{}/last"), _Val.month()); + } + + template + basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const month_weekday& _Val) { + return _Os << _STD format( + _Os.getloc(), _STATICALLY_WIDEN(_CharT, "{}/{}"), _Val.month(), _Val.weekday_indexed()); + } + + template + basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const month_weekday_last& _Val) { + return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{}/{}"), _Val.month(), _Val.weekday_last()); + } + + template + basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const year_month& _Val) { + return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{}/{}"), _Val.year(), _Val.month()); } template basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const year_month_day& _Val) { - _Try_simple_write(_Os, 'F', _Fill_tm(_Val)); - if (!_Val.ok()) { - _Os << _STATICALLY_WIDEN(_CharT, " is not a valid date"); - } - return _Os; + return _Os << (_Val.ok() ? _STD format(_STATICALLY_WIDEN(_CharT, "{:%F}"), _Val) + : _STD format(_STATICALLY_WIDEN(_CharT, "{:%F} is not a valid date"), _Val)); + } + + template + basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const year_month_day_last& _Val) { + return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{}/{}"), _Val.year(), _Val.month_day_last()); + } + + template + basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const year_month_weekday& _Val) { + return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{}/{}/{}"), _Val.year(), _Val.month(), + _Val.weekday_indexed()); + } + + template + basic_ostream<_CharT, _Traits>& operator<<( + basic_ostream<_CharT, _Traits>& _Os, const year_month_weekday_last& _Val) { + return _Os << _STD format( + _Os.getloc(), _STATICALLY_WIDEN(_CharT, "{}/{}/{}"), _Val.year(), _Val.month(), _Val.weekday_last()); } template basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const hh_mm_ss<_Duration>& _Val) { - const tm _Time = _Fill_tm(_Val); - if (_Val.is_negative()) { - _Os << _CharT{'-'}; - } - _Os << _STD put_time(&_Time, _STATICALLY_WIDEN(_CharT, "%H:%M:")); - _Write_seconds(_Os, _Val); - return _Os; + return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:%T}"), _Val); } } // namespace chrono @@ -5694,7 +5756,8 @@ struct _Chrono_formatter { return _Type == 'Y' || _Type == 'y' || _Type == 'C'; } else if constexpr (is_same_v<_Ty, _CHRONO weekday>) { return _Type == 'a' || _Type == 'A' || _Type == 'u' || _Type == 'w'; - } else if constexpr (is_same_v<_Ty, _CHRONO year_month_day>) { + } else if constexpr (_Is_any_of_v<_Ty, _CHRONO year_month_day, _CHRONO year_month_day_last, + _CHRONO year_month_weekday, _CHRONO year_month_weekday_last>) { return _Type == 'D' || _Type == 'F' || _Is_valid_type<_CHRONO year>(_Type) || _Is_valid_type<_CHRONO month>(_Type) || _Is_valid_type<_CHRONO day>(_Type) || _Is_valid_type<_CHRONO weekday>(_Type); @@ -5761,14 +5824,14 @@ struct _Chrono_formatter { bool _No_chrono_specs = false; }; -template -struct formatter<_CHRONO day, _CharT> { +template +struct _Fill_tm_formatter { auto parse(basic_format_parse_context<_CharT>& _Parse_ctx) { - return _Impl.template _Parse<_CHRONO day>(_Parse_ctx); + return _Impl.template _Parse<_Ty>(_Parse_ctx); } template - auto format(const _CHRONO day& _Val, _FormatContext& _FormatCtx) { + auto format(const _Ty& _Val, _FormatContext& _FormatCtx) { return _Impl._Write(_FormatCtx, _Val, _Fill_tm(_Val)); } @@ -5777,49 +5840,33 @@ private: }; template -struct formatter<_CHRONO month, _CharT> { - auto parse(basic_format_parse_context<_CharT>& _Parse_ctx) { - return _Impl.template _Parse<_CHRONO month>(_Parse_ctx); - } - - template - auto format(const _CHRONO month& _Val, _FormatContext& _FormatCtx) { - return _Impl._Write(_FormatCtx, _Val, _Fill_tm(_Val)); - } +struct formatter<_CHRONO day, _CharT> // + : _Fill_tm_formatter<_CHRONO day, _CharT> {}; -private: - _Chrono_formatter<_CharT, false> _Impl; -}; +template +struct formatter<_CHRONO month, _CharT> // + : _Fill_tm_formatter<_CHRONO month, _CharT> {}; template -struct formatter<_CHRONO year, _CharT> { - auto parse(basic_format_parse_context<_CharT>& _Parse_ctx) { - return _Impl.template _Parse<_CHRONO year>(_Parse_ctx); - } +struct formatter<_CHRONO year, _CharT> // + : _Fill_tm_formatter<_CHRONO year, _CharT> {}; - template - auto format(const _CHRONO year& _Val, _FormatContext& _FormatCtx) { - return _Impl._Write(_FormatCtx, _Val, _Fill_tm(_Val)); - } +template +struct formatter<_CHRONO year_month_day, _CharT> // + : _Fill_tm_formatter<_CHRONO year_month_day, _CharT> {}; -private: - _Chrono_formatter<_CharT, false> _Impl; -}; +template +struct formatter<_CHRONO year_month_day_last, _CharT> // + : _Fill_tm_formatter<_CHRONO year_month_day_last, _CharT> {}; template -struct formatter<_CHRONO year_month_day, _CharT> { - auto parse(basic_format_parse_context<_CharT>& _Parse_ctx) { - return _Impl.template _Parse<_CHRONO year_month_day>(_Parse_ctx); - } +struct formatter<_CHRONO year_month_weekday, _CharT> // + : _Fill_tm_formatter<_CHRONO year_month_weekday, _CharT> {}; - template - auto format(const _CHRONO year_month_day& _Val, _FormatContext& _FormatCtx) { - return _Impl._Write(_FormatCtx, _Val, _Fill_tm(_Val)); - } +template +struct formatter<_CHRONO year_month_weekday_last, _CharT> + : _Fill_tm_formatter<_CHRONO year_month_weekday_last, _CharT> {}; -private: - _Chrono_formatter<_CharT, false> _Impl; -}; #endif // __cpp_lib_concepts #endif // _HAS_CXX20 diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index 4bd8330122..b304268043 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -363,12 +363,30 @@ void test_year_month_day_formatter() { assert(format(STR("{:%u %w}"), year_month_day{year{1900}, month{1}, day{4}}) == STR("4 4")); } +template +void test_year_month_day_last_formatter() { + // FIXME, TEST format() AND operator<< WHEN formatter FOR month_day_last IS IMPLEMENTED + // assert(format(STR("{:%F}"), 2021y / April / last) == STR("2021-04-30")); +} + +template +void test_year_month_weekday_formatter() { + // FIXME, TEST format() AND operator<< WHEN formatter FOR weekday_indexed IS IMPLEMENTED +} + +template +void test_year_month_weekday_last_formatter() { + // FIXME, TEST format() AND operator<< WHEN formatter FOR weekday_last IS IMPLEMENTED +} + template void test_hh_mm_ss_formatter() { +#if 0 // FIXME, TEST format() AND operator<< WHEN formatter FOR hh_mm_ss IS IMPLEMENTED stream_helper(STR("-01:08:03.007"), hh_mm_ss{-4083007ms}); stream_helper(STR("01:08:03.007"), hh_mm_ss{4083007ms}); stream_helper(STR("18:15:45.123"), hh_mm_ss{65745123ms}); stream_helper(STR("18:15:45"), hh_mm_ss{65745s}); +#endif // FIXME } int main() { @@ -390,6 +408,15 @@ int main() { test_year_month_day_formatter(); test_year_month_day_formatter(); + test_year_month_day_last_formatter(); + test_year_month_day_last_formatter(); + + test_year_month_weekday_formatter(); + test_year_month_weekday_formatter(); + + test_year_month_weekday_last_formatter(); + test_year_month_weekday_last_formatter(); + test_hh_mm_ss_formatter(); test_hh_mm_ss_formatter(); } From 48eb7f88d167beb87b848966c5d359f40d35029f Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Fri, 16 Apr 2021 05:30:50 -0700 Subject: [PATCH 16/41] Update libcxx skips. --- tests/libcxx/expected_results.txt | 6 +++--- tests/libcxx/skipped_tests.txt | 3 --- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/libcxx/expected_results.txt b/tests/libcxx/expected_results.txt index a82a6c1a5b..5707322559 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -276,14 +276,11 @@ std/utilities/memory/default.allocator/allocator.members/allocate.verify.cpp SKI # *** MISSING STL FEATURES *** # C++20 P0355R7 " Calendars And Time Zones" -std/utilities/time/time.cal/time.cal.md/time.cal.md.nonmembers/streaming.pass.cpp FAIL -std/utilities/time/time.cal/time.cal.mdlast/streaming.pass.cpp FAIL std/utilities/time/time.cal/time.cal.mwd/time.cal.mwd.nonmembers/streaming.pass.cpp FAIL std/utilities/time/time.cal/time.cal.mwdlast/time.cal.mwdlast.nonmembers/streaming.pass.cpp FAIL std/utilities/time/time.cal/time.cal.wdidx/time.cal.wdidx.nonmembers/streaming.pass.cpp FAIL std/utilities/time/time.cal/time.cal.wdlast/time.cal.wdlast.nonmembers/streaming.pass.cpp FAIL std/utilities/time/time.cal/time.cal.weekday/time.cal.weekday.nonmembers/streaming.pass.cpp FAIL -std/utilities/time/time.cal/time.cal.ym/time.cal.ym.nonmembers/streaming.pass.cpp FAIL std/utilities/time/time.cal/time.cal.ymwd/time.cal.ymwd.nonmembers/streaming.pass.cpp FAIL std/utilities/time/time.cal/time.cal.ymwdlast/time.cal.ymwdlast.nonmembers/streaming.pass.cpp FAIL @@ -878,7 +875,10 @@ std/utilities/function.objects/func.search/func.search.bmh/pred.pass.cpp PASS # Not yet implemented in libcxx and marked as "XFAIL: *" std/utilities/time/time.cal/time.cal.day/time.cal.day.nonmembers/streaming.pass.cpp SKIPPED +std/utilities/time/time.cal/time.cal.md/time.cal.md.nonmembers/streaming.pass.cpp SKIPPED +std/utilities/time/time.cal/time.cal.mdlast/streaming.pass.cpp SKIPPED std/utilities/time/time.cal/time.cal.month/time.cal.month.nonmembers/streaming.pass.cpp SKIPPED std/utilities/time/time.cal/time.cal.year/time.cal.year.nonmembers/streaming.pass.cpp SKIPPED +std/utilities/time/time.cal/time.cal.ym/time.cal.ym.nonmembers/streaming.pass.cpp SKIPPED std/utilities/time/time.cal/time.cal.ymd/time.cal.ymd.nonmembers/streaming.pass.cpp SKIPPED std/utilities/time/time.cal/time.cal.ymdlast/time.cal.ymdlast.nonmembers/streaming.pass.cpp SKIPPED diff --git a/tests/libcxx/skipped_tests.txt b/tests/libcxx/skipped_tests.txt index eebe1b513e..a8bd7e8b84 100644 --- a/tests/libcxx/skipped_tests.txt +++ b/tests/libcxx/skipped_tests.txt @@ -276,14 +276,11 @@ utilities\memory\default.allocator\allocator.members\allocate.verify.cpp # *** MISSING STL FEATURES *** # C++20 P0355R7 " Calendars And Time Zones" -utilities\time\time.cal\time.cal.md\time.cal.md.nonmembers\streaming.pass.cpp -utilities\time\time.cal\time.cal.mdlast\streaming.pass.cpp utilities\time\time.cal\time.cal.mwd\time.cal.mwd.nonmembers\streaming.pass.cpp utilities\time\time.cal\time.cal.mwdlast\time.cal.mwdlast.nonmembers\streaming.pass.cpp utilities\time\time.cal\time.cal.wdidx\time.cal.wdidx.nonmembers\streaming.pass.cpp utilities\time\time.cal\time.cal.wdlast\time.cal.wdlast.nonmembers\streaming.pass.cpp utilities\time\time.cal\time.cal.weekday\time.cal.weekday.nonmembers\streaming.pass.cpp -utilities\time\time.cal\time.cal.ym\time.cal.ym.nonmembers\streaming.pass.cpp utilities\time\time.cal\time.cal.ymwd\time.cal.ymwd.nonmembers\streaming.pass.cpp utilities\time\time.cal\time.cal.ymwdlast\time.cal.ymwdlast.nonmembers\streaming.pass.cpp From 84760ea831fd937a148d15c49a7849b305ba497b Mon Sep 17 00:00:00 2001 From: Elnar D Date: Fri, 16 Apr 2021 06:23:07 -0700 Subject: [PATCH 17/41] Add hh_mm_ss formatter Adds the formatter with special bounds check so that `put_time` doesn't assert. Small bugfix/simplification for when `ok()` needs to be checked. Before we call `put_time`, we should always bounds check. Custom %S writer added (and %T calls it too). This is necessary for proper fractional second printing. --- stl/inc/chrono | 141 +++++++++--------- .../test.cpp | 12 +- 2 files changed, 84 insertions(+), 69 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 834b444765..089598c03e 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5223,6 +5223,13 @@ concept _Chrono_parse_spec_callbacks = _Parse_align_callbacks<_Ty, _CharT> }; // clang-format on +// clang-format off +template +concept _Has_ok = requires (_Ty _At) { + {_At.ok()} -> same_as; +}; +// clang-format on + // A chrono spec is either a type (with an optional modifier), OR a literal character, never both. template struct _Chrono_specs { @@ -5413,11 +5420,33 @@ _NODISCARD constexpr const _CharT* _Parse_chrono_format_specs( } namespace chrono { + // Replacement for %S, as put_time does not honor writing fractional seconds. + template + void _Write_seconds(basic_ostream<_CharT, _Traits>&, const _Ty&) { + _STL_INTERNAL_CHECK(false); + } + + template + void _Write_seconds(basic_ostream<_CharT, _Traits>& _Os, const hh_mm_ss<_Duration>& _Val) { + _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:02}"), _Val.seconds().count()); + if constexpr (hh_mm_ss<_Duration>::fractional_width > 0) { + _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{0}{1:0{2}}"), + _STD use_facet>(_Os.getloc()).decimal_point(), _Val.subseconds().count(), + _Val.fractional_width); + } + } + + template + void _Write_seconds(basic_ostream<_CharT, _Traits>& _Os, const time_point<_Clock, _Duration>& _Val) { + const auto _Dp = _CHRONO floor(_Val); + _Write_seconds(_Os, hh_mm_ss{_Val - _Dp}); + } + // This echoes the functionality of put_time, but is able to handle invalid dates (when !ok()) since the Standard // mandates that invalid dates still be formatted properly. For example, put_time isn't able to handle a tm_mday of // 40, but format("{:%d}", day{40}) should return "40" and operator<< for day prints "40 is not a valid day". - template - bool _Try_simple_write(basic_ostream<_CharT>& _Os, const char _Type, const tm& _Time) { + template + bool _Try_simple_write(basic_ostream<_CharT>& _Os, const char _Type, const tm& _Time, const _Ty& _Val) { const auto _Year = _Time.tm_year + 1900; const auto _Month = _Time.tm_mon + 1; switch (_Type) { @@ -5451,55 +5480,31 @@ namespace chrono { _STATICALLY_WIDEN(_CharT, "{:02}"), _STD abs(_Time_parse_fields::_Decompose_year(_Year).first) / 100); return true; case 'F': - _Try_simple_write(_Os, 'Y', _Time); + _Try_simple_write(_Os, 'Y', _Time, _Val); _Os << _CharT{'-'}; - _Try_simple_write(_Os, 'm', _Time); + _Try_simple_write(_Os, 'm', _Time, _Val); _Os << _CharT{'-'}; - _Try_simple_write(_Os, 'd', _Time); + _Try_simple_write(_Os, 'd', _Time, _Val); return true; case 'D': - _Try_simple_write(_Os, 'm', _Time); + _Try_simple_write(_Os, 'm', _Time, _Val); _Os << _CharT{'/'}; - _Try_simple_write(_Os, 'd', _Time); + _Try_simple_write(_Os, 'd', _Time, _Val); _Os << _CharT{'/'}; - _Try_simple_write(_Os, 'y', _Time); + _Try_simple_write(_Os, 'y', _Time, _Val); + return true; + case 'T': + // Alias for %H:%M:%S but we need to rewrite %S to display fractions of a second. + _Os << _STD put_time(&_Time, _STATICALLY_WIDEN(_CharT, "%H:%M:")); + [[fallthrough]]; + case 'S': + _Write_seconds(_Os, _Val); return true; default: return false; } } - template - void _Write_seconds(basic_ostream<_CharT, _Traits>& _Os, const hh_mm_ss<_Duration>& _Val) { - _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:02}"), _Val.seconds().count()); - if constexpr (hh_mm_ss<_Duration>::fractional_width > 0) { - _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{0}{1:0{2}}"), - _STD use_facet>(_Os.getloc()).decimal_point(), _Val.subseconds().count(), - _Val.fractional_width); - } - } - - /* - template - // clang-format off - requires (!treat_as_floating_point_v && (_Duration{1} < days{1})) - basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const sys_time<_Duration>& _Clock) { - // clang-format on - const auto _Dp = floor(_Clock); - return _Os << year_month_day{_Dp} << _STATICALLY_WIDEN(_CharT, " ") << hh_mm_ss{_Clock - _Dp}; - } - - template - basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const sys_days& _Clock) { - return _Os << year_month_day{_Clock}; - } - - template - basic_ostream<_CharT, _Traits>& operator<<( - basic_ostream<_CharT, _Traits>& _Os, const utc_clock<_Duration>& _Clock) { - return _Os << format(_STATICALLY_WIDEN(_CharT, "{:%F %T}"), _Clock); - }*/ - template _NODISCARD tm _Fill_tm(const _Ty& _Val) { unsigned int _Day = 0; @@ -5649,23 +5654,6 @@ namespace chrono { } } // namespace chrono -/* -template -struct formatter<_CHRONO sys_time<_Duration>, _CharT> { - typename basic_format_parse_context<_CharT>::iterator parse(basic_format_parse_context<_CharT>& _Parse_ctx) { - _Chrono_format_specs<_CharT> _Specs{}; - _Chrono_specs_setter<_CharT, basic_format_parse_context<_CharT>> _Callback{_Specs, _Parse_ctx}; - const auto _It = - _Parse_chrono_format_specs(_Parse_ctx._Unchecked_begin(), _Parse_ctx._Unchecked_end(), _Callback); - const auto _Res_iter = _Parse_ctx.begin() + (_It - _Parse_ctx._Unchecked_begin()); - - if (_It != _Parse_ctx._Unchecked_end() && *_It != '}') { - _THROW(format_error("Missing '}' in format string.")); - } - } -}; -*/ - template struct _Chrono_formatter { template @@ -5761,6 +5749,9 @@ struct _Chrono_formatter { return _Type == 'D' || _Type == 'F' || _Is_valid_type<_CHRONO year>(_Type) || _Is_valid_type<_CHRONO month>(_Type) || _Is_valid_type<_CHRONO day>(_Type) || _Is_valid_type<_CHRONO weekday>(_Type); + } else if constexpr (_Is_specialization_v<_Ty, _CHRONO hh_mm_ss>) { + return _Type == 'H' || _Type == 'I' || _Type == 'M' || _Type == 'S' || _Type == 'r' || _Type == 'R' + || _Type == 'T' || _Type == 'p'; } else { // TRANSITION, remove when all types are added static_assert(_Always_false<_Ty>, "unsupported type"); @@ -5788,19 +5779,14 @@ struct _Chrono_formatter { } // For non-localized writes, we can sometimes avoid calling put_time. - if (_Spec._Modifier == '\0') { - if (_CHRONO _Try_simple_write(_Stream, _Spec._Type, _Time)) { - continue; - } else if constexpr (_Is_specialization_v<_Ty, _CHRONO hh_mm_ss>) { - if (_Spec._Type == 'S') { - _CHRONO _Write_seconds(_Stream, _Val); - continue; - } - } + if (_Spec._Modifier == '\0' && _CHRONO _Try_simple_write(_Stream, _Spec._Type, _Time, _Val)) { + continue; } // Otherwise, we should throw to avoid triggering asserts within put_time machinery. - if (!_Val.ok()) { - _THROW(format_error("Cannot localize out-of-bounds time point.")); + if constexpr (_Has_ok<_Ty>) { + if (!_Val.ok()) { + _THROW(format_error("Cannot localize out-of-bounds time point.")); + } } _CharT _Fmt_str[4]; @@ -5867,6 +5853,27 @@ template struct formatter<_CHRONO year_month_weekday_last, _CharT> : _Fill_tm_formatter<_CHRONO year_month_weekday_last, _CharT> {}; +template +struct formatter<_CHRONO hh_mm_ss<_CHRONO duration<_Rep, _Period>>, _CharT> { + using _Ty = _CHRONO hh_mm_ss<_CHRONO duration<_Rep, _Period>>; + + auto parse(basic_format_parse_context<_CharT>& _Parse_ctx) { + return _Impl.template _Parse<_Ty>(_Parse_ctx); + } + + template + auto format(const _Ty& _Val, _FormatContext& _FormatCtx) { + if (-_CHRONO hours{24} >= _Val.hours() || _Val.hours() >= _CHRONO hours{24}) { + _THROW(format_error("Cannot localize hh_mm_ss longer than 24 hours.")); + } + return _Impl._Write(_FormatCtx, _Val, _Fill_tm(_Val)); + } + +private: + _Chrono_formatter<_CharT, false> _Impl; +}; + + #endif // __cpp_lib_concepts #endif // _HAS_CXX20 diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index b304268043..88ebfd99e0 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -299,6 +299,7 @@ void test_day_formatter() { assert(format(STR("{:%d}"), day{200}) == STR("200")); throw_helper(STR("{:%Ed}"), day{10}); throw_helper(STR("{:%Od}"), day{40}); + throw_helper(STR("{:%Ed}"), day{40}); assert(format(STR("{}"), day{0}) == STR("00 is not a valid day")); // Op << @@ -381,12 +382,19 @@ void test_year_month_weekday_last_formatter() { template void test_hh_mm_ss_formatter() { -#if 0 // FIXME, TEST format() AND operator<< WHEN formatter FOR hh_mm_ss IS IMPLEMENTED stream_helper(STR("-01:08:03.007"), hh_mm_ss{-4083007ms}); stream_helper(STR("01:08:03.007"), hh_mm_ss{4083007ms}); stream_helper(STR("18:15:45.123"), hh_mm_ss{65745123ms}); stream_helper(STR("18:15:45"), hh_mm_ss{65745s}); -#endif // FIXME + + assert(format(STR("{:%H %I %M %S %r %R %T %p}"), hh_mm_ss{13h + 14min + 15351ms}) + == STR("13 01 14 15.351 13:14:15 13:14 13:14:15.351 PM")); + + assert(format(STR("{:%H %I %M %S %r %R %T %p}"), hh_mm_ss{-13h - 14min - 15351ms}) + == STR("-13 01 14 15.351 13:14:15 13:14 13:14:15.351 PM")); + + throw_helper(STR("{}"), hh_mm_ss{24h}); + throw_helper(STR("{}"), hh_mm_ss{-24h}); } int main() { From 29b70943b6bb63ba88032cc44ea50ad021b60150 Mon Sep 17 00:00:00 2001 From: Elnar D Date: Fri, 16 Apr 2021 07:16:02 -0700 Subject: [PATCH 18/41] Add month_day, month_day_last, and year_month formatting Not too many tests but they reuse a lot of the same pathways. Wouldn't hurt to test more but the operator<< are fully passthrough to format so it's not too scary. --- stl/inc/chrono | 26 +++++++++++++- .../test.cpp | 34 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 089598c03e..f9514ce166 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5521,6 +5521,14 @@ namespace chrono { _Month = static_cast(_Val); } else if constexpr (is_same_v<_Ty, year>) { _Year = static_cast(_Val); + } else if constexpr (is_same_v<_Ty, month_day>) { + _Day = static_cast(_Val.day()); + _Month = static_cast(_Val.month()); + } else if constexpr (is_same_v<_Ty, month_day_last>) { + _Month = static_cast(_Val.month()); + } else if constexpr (is_same_v<_Ty, year_month>) { + _Month = static_cast(_Val.month()); + _Year = static_cast(_Val.year()); } else if constexpr (is_same_v<_Ty, year_month_day>) { _Day = static_cast(_Val.day()); _Month = static_cast(_Val.month()); @@ -5738,12 +5746,16 @@ struct _Chrono_formatter { _NODISCARD constexpr bool _Is_valid_type(const char _Type) noexcept { if constexpr (is_same_v<_Ty, _CHRONO day>) { return _Type == 'd' || _Type == 'e'; - } else if constexpr (is_same_v<_Ty, _CHRONO month>) { + } else if constexpr (_Is_any_of_v<_Ty, _CHRONO month, _CHRONO month_day_last>) { return _Type == 'b' || _Type == 'B' || _Type == 'h' || _Type == 'm'; } else if constexpr (is_same_v<_Ty, _CHRONO year>) { return _Type == 'Y' || _Type == 'y' || _Type == 'C'; } else if constexpr (is_same_v<_Ty, _CHRONO weekday>) { return _Type == 'a' || _Type == 'A' || _Type == 'u' || _Type == 'w'; + } else if constexpr (is_same_v<_Ty, _CHRONO month_day>) { + return _Is_valid_type<_CHRONO month>(_Type) || _Is_valid_type<_CHRONO day>(_Type); + } else if constexpr (is_same_v<_Ty, _CHRONO year_month>) { + return _Is_valid_type<_CHRONO year>(_Type) || _Is_valid_type<_CHRONO month>(_Type); } else if constexpr (_Is_any_of_v<_Ty, _CHRONO year_month_day, _CHRONO year_month_day_last, _CHRONO year_month_weekday, _CHRONO year_month_weekday_last>) { return _Type == 'D' || _Type == 'F' || _Is_valid_type<_CHRONO year>(_Type) @@ -5837,6 +5849,18 @@ template struct formatter<_CHRONO year, _CharT> // : _Fill_tm_formatter<_CHRONO year, _CharT> {}; +template +struct formatter<_CHRONO month_day, _CharT> // + : _Fill_tm_formatter<_CHRONO month_day, _CharT> {}; + +template +struct formatter<_CHRONO month_day_last, _CharT> // + : _Fill_tm_formatter<_CHRONO month_day_last, _CharT> {}; + +template +struct formatter<_CHRONO year_month, _CharT> // + : _Fill_tm_formatter<_CHRONO year_month, _CharT> {}; + template struct formatter<_CHRONO year_month_day, _CharT> // : _Fill_tm_formatter<_CHRONO year_month_day, _CharT> {}; diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index 88ebfd99e0..cb14051fd9 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -397,6 +397,31 @@ void test_hh_mm_ss_formatter() { throw_helper(STR("{}"), hh_mm_ss{-24h}); } +template +void test_month_day_formatter() { + stream_helper(STR("Jan/16"), January / 16); + stream_helper(STR("13 is not a valid month/40 is not a valid day"), month{13} / day{40}); + + assert(format(STR("{:%B %d}"), June / 17) == STR("June 17")); + throw_helper(STR("{:%Y}"), June / 17); +} + +template +void test_month_day_last_formatter() { + stream_helper(STR("Feb/last"), February / last); + + assert(format(STR("{:%B}"), June / last) == STR("June")); + throw_helper(STR("{:%d}"), June / last); +} + +template +void test_year_month_formatter() { + stream_helper(STR("1444/Oct"), 1444y / October); + + assert(format(STR("{:%Y %B}"), 2000y / July) == STR("2000 July")); + throw_helper(STR("{:%d}"), 2000y / July); +} + int main() { test_parse_conversion_spec(); test_parse_conversion_spec(); @@ -427,4 +452,13 @@ int main() { test_hh_mm_ss_formatter(); test_hh_mm_ss_formatter(); + + test_month_day_formatter(); + test_month_day_formatter(); + + test_month_day_last_formatter(); + test_month_day_last_formatter(); + + test_year_month_formatter(); + test_year_month_formatter(); } From f6ba00def1546bfc561a52a7c66f4c89c94779d2 Mon Sep 17 00:00:00 2001 From: Elnar D Date: Fri, 16 Apr 2021 09:48:08 -0700 Subject: [PATCH 19/41] Expand functionality of _Try_simple_write into _Custom_write --- stl/inc/chrono | 49 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index f9514ce166..262c3bd8dd 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5446,33 +5446,50 @@ namespace chrono { // mandates that invalid dates still be formatted properly. For example, put_time isn't able to handle a tm_mday of // 40, but format("{:%d}", day{40}) should return "40" and operator<< for day prints "40 is not a valid day". template - bool _Try_simple_write(basic_ostream<_CharT>& _Os, const char _Type, const tm& _Time, const _Ty& _Val) { - const auto _Year = _Time.tm_year + 1900; - const auto _Month = _Time.tm_mon + 1; - switch (_Type) { + bool _Custom_write( + basic_ostream<_CharT>& _Os, const _Chrono_specs<_CharT>& _Specs, const tm& _Time, const _Ty& _Val) { + const auto _Year = _Time.tm_year + 1900; + const auto _Month = _Time.tm_mon + 1; + const bool _Has_modifier = _Specs._Modifier != '\0'; + switch (_Specs._Type) { case 'd': case 'e': + if (_Has_modifier) { + return false; + } if (_Time.tm_mday < 10) { - _Os << (_Type == 'd' ? _CharT{'0'} : _CharT{' '}); + _Os << (_Specs._Type == 'd' ? _CharT{'0'} : _CharT{' '}); } _Os << _Time.tm_mday; return true; case 'm': + if (_Has_modifier) { + return false; + } if (_Month < 10) { _Os << _CharT{'0'}; } _Os << _Month; return true; case 'Y': + if (_Has_modifier) { + return false; + } if (_Year < 0) { _Os << _CharT{'-'}; } _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:04}"), _STD abs(_Year)); return true; case 'y': + if (_Has_modifier) { + return false; + } _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:02}"), _Year < 0 ? 100 + (_Year % 100) : _Year % 100); return true; case 'C': + if (_Has_modifier) { + return false; + } if (_Year < 0) { _Os << _CharT{'-'}; } @@ -5480,24 +5497,27 @@ namespace chrono { _STATICALLY_WIDEN(_CharT, "{:02}"), _STD abs(_Time_parse_fields::_Decompose_year(_Year).first) / 100); return true; case 'F': - _Try_simple_write(_Os, 'Y', _Time, _Val); + _Custom_write(_Os, {._Type = 'Y'}, _Time, _Val); _Os << _CharT{'-'}; - _Try_simple_write(_Os, 'm', _Time, _Val); + _Custom_write(_Os, {._Type = 'm'}, _Time, _Val); _Os << _CharT{'-'}; - _Try_simple_write(_Os, 'd', _Time, _Val); + _Custom_write(_Os, {._Type = 'd'}, _Time, _Val); return true; case 'D': - _Try_simple_write(_Os, 'm', _Time, _Val); + _Custom_write(_Os, {._Type = 'm'}, _Time, _Val); _Os << _CharT{'/'}; - _Try_simple_write(_Os, 'd', _Time, _Val); + _Custom_write(_Os, {._Type = 'd'}, _Time, _Val); _Os << _CharT{'/'}; - _Try_simple_write(_Os, 'y', _Time, _Val); + _Custom_write(_Os, {._Type = 'y'}, _Time, _Val); return true; case 'T': // Alias for %H:%M:%S but we need to rewrite %S to display fractions of a second. _Os << _STD put_time(&_Time, _STATICALLY_WIDEN(_CharT, "%H:%M:")); [[fallthrough]]; case 'S': + if (_Has_modifier) { + return false; + } _Write_seconds(_Os, _Val); return true; default: @@ -5790,11 +5810,12 @@ struct _Chrono_formatter { continue; } - // For non-localized writes, we can sometimes avoid calling put_time. - if (_Spec._Modifier == '\0' && _CHRONO _Try_simple_write(_Stream, _Spec._Type, _Time, _Val)) { + // We need to manually do certain writes, either because the specification is different from put_time or + // custom logic is needed. + if (_CHRONO _Custom_write(_Stream, _Spec, _Time, _Val)) { continue; } - // Otherwise, we should throw to avoid triggering asserts within put_time machinery. + // Otherwise, we should throw on out-of-bounds to avoid triggering asserts within put_time machinery. if constexpr (_Has_ok<_Ty>) { if (!_Val.ok()) { _THROW(format_error("Cannot localize out-of-bounds time point.")); From 0814ca2020c222cf01d12b045d0637de3658df2e Mon Sep 17 00:00:00 2001 From: Elnar Dakeshov <55715127+eldakesh-ms@users.noreply.github.com> Date: Fri, 16 Apr 2021 13:09:31 -0700 Subject: [PATCH 20/41] chronat: Allow day from month_day_last conditionally (#1845) Thanks to matt and stat on discord for mentioning these edge cases. I originally thought that you can't get a day from `month_day_last`, but it clearly makes sense, for most months. I changed some machinery around so that we can always intercept a specifier, which means that we need to do manual checking in the writer for localization. This PR is more to illustrate how a specifier may be intercepted, and that testing need only be done for specific types. --- stl/inc/chrono | 11 +++++++++-- .../test.cpp | 3 ++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 262c3bd8dd..c982303a40 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5454,6 +5454,12 @@ namespace chrono { switch (_Specs._Type) { case 'd': case 'e': + // Most months have a proper last day, but February depends on the year. + if constexpr (is_same_v<_Ty, month_day_last>) { + if (_Val.month() == February) { + _THROW(format_error("Cannot print the last day of February without a year")); + } + } if (_Has_modifier) { return false; } @@ -5546,6 +5552,7 @@ namespace chrono { _Month = static_cast(_Val.month()); } else if constexpr (is_same_v<_Ty, month_day_last>) { _Month = static_cast(_Val.month()); + _Day = static_cast(_Last_day_table[_Month - 1]); } else if constexpr (is_same_v<_Ty, year_month>) { _Month = static_cast(_Val.month()); _Year = static_cast(_Val.year()); @@ -5766,13 +5773,13 @@ struct _Chrono_formatter { _NODISCARD constexpr bool _Is_valid_type(const char _Type) noexcept { if constexpr (is_same_v<_Ty, _CHRONO day>) { return _Type == 'd' || _Type == 'e'; - } else if constexpr (_Is_any_of_v<_Ty, _CHRONO month, _CHRONO month_day_last>) { + } else if constexpr (is_same_v<_Ty, _CHRONO month>) { return _Type == 'b' || _Type == 'B' || _Type == 'h' || _Type == 'm'; } else if constexpr (is_same_v<_Ty, _CHRONO year>) { return _Type == 'Y' || _Type == 'y' || _Type == 'C'; } else if constexpr (is_same_v<_Ty, _CHRONO weekday>) { return _Type == 'a' || _Type == 'A' || _Type == 'u' || _Type == 'w'; - } else if constexpr (is_same_v<_Ty, _CHRONO month_day>) { + } else if constexpr (_Is_any_of_v<_Ty, _CHRONO month_day, _CHRONO month_day_last>) { return _Is_valid_type<_CHRONO month>(_Type) || _Is_valid_type<_CHRONO day>(_Type); } else if constexpr (is_same_v<_Ty, _CHRONO year_month>) { return _Is_valid_type<_CHRONO year>(_Type) || _Is_valid_type<_CHRONO month>(_Type); diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index cb14051fd9..c591027dac 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -411,7 +411,8 @@ void test_month_day_last_formatter() { stream_helper(STR("Feb/last"), February / last); assert(format(STR("{:%B}"), June / last) == STR("June")); - throw_helper(STR("{:%d}"), June / last); + assert(format(STR("{:%d}"), June / last) == STR("30")); + throw_helper(STR("{:%d}"), February / last); } template From a0f655821509f2b276ede1f7ab775529c5a3740f Mon Sep 17 00:00:00 2001 From: Elnar Dakeshov <55715127+eldakesh-ms@users.noreply.github.com> Date: Fri, 16 Apr 2021 16:39:54 -0700 Subject: [PATCH 21/41] chronat: Clock formatting (#1846) * chronat: Clock formatting Adds formatting for clocks! Moved _Custom_write into the formatter so it can do special things (write timezones). _Write_seconds now writes leap seconds as 60 for utc clock time points. Taught _Fill_tm to work with time_points (in reality only system_clock and local_clock work). Add operator<< for all clocks except local-time-format-t (because I'm still not sure what that is). The base formatter stores a timezone abbreviation. This is only useful for the clocks, but it seemed like the simplest way to implement this feature. * Remove unnecessary _CharT param for _Custom_write. This is a member function of _Chrono_formatter which is already templated on _CharT. * typename _Ty::clock. Co-authored-by: Stephan T. Lavavej --- stl/inc/chrono | 361 +++++++++++++----- .../test.cpp | 24 ++ 2 files changed, 295 insertions(+), 90 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index c982303a40..e73947a1ec 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5438,97 +5438,14 @@ namespace chrono { template void _Write_seconds(basic_ostream<_CharT, _Traits>& _Os, const time_point<_Clock, _Duration>& _Val) { - const auto _Dp = _CHRONO floor(_Val); - _Write_seconds(_Os, hh_mm_ss{_Val - _Dp}); - } - - // This echoes the functionality of put_time, but is able to handle invalid dates (when !ok()) since the Standard - // mandates that invalid dates still be formatted properly. For example, put_time isn't able to handle a tm_mday of - // 40, but format("{:%d}", day{40}) should return "40" and operator<< for day prints "40 is not a valid day". - template - bool _Custom_write( - basic_ostream<_CharT>& _Os, const _Chrono_specs<_CharT>& _Specs, const tm& _Time, const _Ty& _Val) { - const auto _Year = _Time.tm_year + 1900; - const auto _Month = _Time.tm_mon + 1; - const bool _Has_modifier = _Specs._Modifier != '\0'; - switch (_Specs._Type) { - case 'd': - case 'e': - // Most months have a proper last day, but February depends on the year. - if constexpr (is_same_v<_Ty, month_day_last>) { - if (_Val.month() == February) { - _THROW(format_error("Cannot print the last day of February without a year")); - } - } - if (_Has_modifier) { - return false; - } - if (_Time.tm_mday < 10) { - _Os << (_Specs._Type == 'd' ? _CharT{'0'} : _CharT{' '}); - } - _Os << _Time.tm_mday; - return true; - case 'm': - if (_Has_modifier) { - return false; - } - if (_Month < 10) { - _Os << _CharT{'0'}; - } - _Os << _Month; - return true; - case 'Y': - if (_Has_modifier) { - return false; - } - if (_Year < 0) { - _Os << _CharT{'-'}; - } - _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:04}"), _STD abs(_Year)); - return true; - case 'y': - if (_Has_modifier) { - return false; - } - _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:02}"), _Year < 0 ? 100 + (_Year % 100) : _Year % 100); - return true; - case 'C': - if (_Has_modifier) { - return false; - } - if (_Year < 0) { - _Os << _CharT{'-'}; - } - _Os << _STD format( - _STATICALLY_WIDEN(_CharT, "{:02}"), _STD abs(_Time_parse_fields::_Decompose_year(_Year).first) / 100); - return true; - case 'F': - _Custom_write(_Os, {._Type = 'Y'}, _Time, _Val); - _Os << _CharT{'-'}; - _Custom_write(_Os, {._Type = 'm'}, _Time, _Val); - _Os << _CharT{'-'}; - _Custom_write(_Os, {._Type = 'd'}, _Time, _Val); - return true; - case 'D': - _Custom_write(_Os, {._Type = 'm'}, _Time, _Val); - _Os << _CharT{'/'}; - _Custom_write(_Os, {._Type = 'd'}, _Time, _Val); - _Os << _CharT{'/'}; - _Custom_write(_Os, {._Type = 'y'}, _Time, _Val); - return true; - case 'T': - // Alias for %H:%M:%S but we need to rewrite %S to display fractions of a second. - _Os << _STD put_time(&_Time, _STATICALLY_WIDEN(_CharT, "%H:%M:")); - [[fallthrough]]; - case 'S': - if (_Has_modifier) { - return false; + if constexpr (is_same_v<_Clock, utc_clock>) { + if (_CHRONO get_leap_second_info(_Val).is_leap_second) { + _Os << _STATICALLY_WIDEN(_CharT, "60"); + return; } - _Write_seconds(_Os, _Val); - return true; - default: - return false; } + const auto _Dp = _CHRONO floor(_Val); + _Write_seconds(_Os, hh_mm_ss{_Val - _Dp}); } template @@ -5580,6 +5497,16 @@ namespace chrono { _Hours = _Val.hours().count(); _Minutes = _Val.minutes().count(); _Seconds = static_cast(_Val.seconds().count()); + } else if constexpr (_Is_specialization_v<_Ty, time_point>) { + const auto _Dp = _CHRONO floor(_Val); + const year_month_day _Ymd{_Dp}; + const hh_mm_ss _Time{_Val - _Dp}; + const auto _Hms = _Fill_tm(_Time); + auto _Tm = _Fill_tm(_Ymd); + _Tm.tm_sec = _Hms.tm_sec; + _Tm.tm_min = _Hms.tm_min; + _Tm.tm_hour = _Hms.tm_hour; + return _Tm; } tm _Time; @@ -5687,6 +5614,46 @@ namespace chrono { basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const hh_mm_ss<_Duration>& _Val) { return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:%T}"), _Val); } + + template + // clang-format off + requires (!treat_as_floating_point_v && (_Duration{1} < days{1})) + basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const sys_time<_Duration>& _Val) { + // clang-format on + const auto _Dp = _CHRONO floor(_Val); + return _Os << _STD format( + _Os.getloc(), _STATICALLY_WIDEN(_CharT, "{} {}"), year_month_day{_Dp}, hh_mm_ss{_Val - _Dp}); + } + + template + basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const sys_days& _Val) { + return _Os << year_month_day{_Val}; + } + + template + basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const utc_time<_Duration>& _Val) { + return _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:%F %T}"), _Val); + } + + template + basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const tai_time<_Duration>& _Val) { + return _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:%F %T}"), _Val); + } + + template + basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const gps_time<_Duration>& _Val) { + return _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:%F %T}"), _Val); + } + + template + basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const file_time<_Duration>& _Val) { + return _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:%F %T}"), _Val); + } + + template + basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const local_time<_Duration>& _Val) { + return _Os << sys_time<_Duration>{_Val.time_since_epoch()}; + } } // namespace chrono template @@ -5791,6 +5758,14 @@ struct _Chrono_formatter { } else if constexpr (_Is_specialization_v<_Ty, _CHRONO hh_mm_ss>) { return _Type == 'H' || _Type == 'I' || _Type == 'M' || _Type == 'S' || _Type == 'r' || _Type == 'R' || _Type == 'T' || _Type == 'p'; + } else if constexpr (_Is_specialization_v<_Ty, _CHRONO time_point>) { + if constexpr (!is_same_v) { + if (_Type == 'z' || _Type == 'Z') { + return true; + } + } + return _Type == 'c' || _Type == 'x' || _Type == 'X' || _Is_valid_type<_CHRONO year_month_day>(_Type) + || _Is_valid_type<_CHRONO hh_mm_ss<_CHRONO seconds>>(_Type); } else { // TRANSITION, remove when all types are added static_assert(_Always_false<_Ty>, "unsupported type"); @@ -5819,7 +5794,7 @@ struct _Chrono_formatter { // We need to manually do certain writes, either because the specification is different from put_time or // custom logic is needed. - if (_CHRONO _Custom_write(_Stream, _Spec, _Time, _Val)) { + if (_Custom_write(_Stream, _Spec, _Time, _Val)) { continue; } // Otherwise, we should throw on out-of-bounds to avoid triggering asserts within put_time machinery. @@ -5846,8 +5821,108 @@ struct _Chrono_formatter { _Fmt_align::_Left, [&](auto _Out) { return _Fmt_write(_STD move(_Out), _Stream.view()); }); } + // This echoes the functionality of put_time, but is able to handle invalid dates (when !ok()) since the Standard + // mandates that invalid dates still be formatted properly. For example, put_time isn't able to handle a tm_mday of + // 40, but format("{:%d}", day{40}) should return "40" and operator<< for day prints "40 is not a valid day". + template + bool _Custom_write( + basic_ostream<_CharT>& _Os, const _Chrono_specs<_CharT>& _Specs, const tm& _Time, const _Ty& _Val) { + const auto _Year = _Time.tm_year + 1900; + const auto _Month = _Time.tm_mon + 1; + const bool _Has_modifier = _Specs._Modifier != '\0'; + switch (_Specs._Type) { + case 'd': + case 'e': + // Most months have a proper last day, but February depends on the year. + if constexpr (is_same_v<_Ty, _CHRONO month_day_last>) { + if (_Val.month() == _CHRONO February) { + _THROW(format_error("Cannot print the last day of February without a year")); + } + } + if (_Has_modifier) { + return false; + } + if (_Time.tm_mday < 10) { + _Os << (_Specs._Type == 'd' ? _CharT{'0'} : _CharT{' '}); + } + _Os << _Time.tm_mday; + return true; + case 'm': + if (_Has_modifier) { + return false; + } + if (_Month < 10) { + _Os << _CharT{'0'}; + } + _Os << _Month; + return true; + case 'Y': + if (_Has_modifier) { + return false; + } + if (_Year < 0) { + _Os << _CharT{'-'}; + } + _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:04}"), _STD abs(_Year)); + return true; + case 'y': + if (_Has_modifier) { + return false; + } + _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:02}"), _Year < 0 ? 100 + (_Year % 100) : _Year % 100); + return true; + case 'C': + if (_Has_modifier) { + return false; + } + if (_Year < 0) { + _Os << _CharT{'-'}; + } + _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:02}"), + _STD abs(_CHRONO _Time_parse_fields::_Decompose_year(_Year).first) / 100); + return true; + case 'F': + _Custom_write(_Os, {._Type = 'Y'}, _Time, _Val); + _Os << _CharT{'-'}; + _Custom_write(_Os, {._Type = 'm'}, _Time, _Val); + _Os << _CharT{'-'}; + _Custom_write(_Os, {._Type = 'd'}, _Time, _Val); + return true; + case 'D': + _Custom_write(_Os, {._Type = 'm'}, _Time, _Val); + _Os << _CharT{'/'}; + _Custom_write(_Os, {._Type = 'd'}, _Time, _Val); + _Os << _CharT{'/'}; + _Custom_write(_Os, {._Type = 'y'}, _Time, _Val); + return true; + case 'T': + // Alias for %H:%M:%S but we need to rewrite %S to display fractions of a second. + _Os << _STD put_time(&_Time, _STATICALLY_WIDEN(_CharT, "%H:%M:")); + [[fallthrough]]; + case 'S': + if (_Has_modifier) { + return false; + } + _Write_seconds(_Os, _Val); + return true; + case 'Z': + _Os << _Time_zone_abbreviation; + return true; + case 'z': + _Os << _STATICALLY_WIDEN(_CharT, "+00"); + if (_Has_modifier) { + _Os << _CharT{':'}; + } + _Os << _STATICALLY_WIDEN(_CharT, "00"); + return true; + default: + return false; + } + } + _Chrono_format_specs<_CharT> _Specs{}; bool _No_chrono_specs = false; + basic_string_view<_CharT> _Time_zone_abbreviation; }; template @@ -5925,6 +6000,112 @@ private: _Chrono_formatter<_CharT, false> _Impl; }; +template +struct formatter<_CHRONO sys_time<_Duration>, _CharT> { + formatter() { + _Impl._Time_zone_abbreviation = _STATICALLY_WIDEN(_CharT, "UTC"); + } + + auto parse(basic_format_parse_context<_CharT>& _Parse_ctx) { + return _Impl.template _Parse<_CHRONO sys_time<_Duration>>(_Parse_ctx); + } + + template + auto format(const _CHRONO sys_time<_Duration>& _Val, _FormatContext& _FormatCtx) { + return _Impl._Write(_FormatCtx, _Val, _Fill_tm(_Val)); + } + +private: + _Chrono_formatter<_CharT, false> _Impl; +}; + +template +struct formatter<_CHRONO utc_time<_Duration>, _CharT> { + formatter() { + _Impl._Time_zone_abbreviation = _STATICALLY_WIDEN(_CharT, "UTC"); + } + + auto parse(basic_format_parse_context<_CharT>& _Parse_ctx) { + return _Impl.template _Parse<_CHRONO utc_time<_Duration>>(_Parse_ctx); + } + + template + auto format(const _CHRONO utc_time<_Duration>& _Val, _FormatContext& _FormatCtx) { + const auto _Sys = _CHRONO utc_clock::to_sys(_Val); + return _Impl._Write(_FormatCtx, _Val, _Fill_tm(_Sys)); + } + +private: + _Chrono_formatter<_CharT, false> _Impl; +}; + +template +struct formatter<_CHRONO tai_time<_Duration>, _CharT> { + formatter() { + _Impl._Time_zone_abbreviation = _STATICALLY_WIDEN(_CharT, "TAI"); + } + + auto parse(basic_format_parse_context<_CharT>& _Parse_ctx) { + return _Impl.template _Parse<_CHRONO tai_time<_Duration>>(_Parse_ctx); + } + + template + auto format(const _CHRONO tai_time<_Duration>& _Val, _FormatContext& _FormatCtx) { + const auto _Sys = _CHRONO sys_time<_Duration>{_Val.time_since_epoch()} + - (_CHRONO sys_days{_CHRONO year{1970} / 1 / 1} - _CHRONO sys_days{_CHRONO year{1958} / 1 / 1}); + return _Impl._Write(_FormatCtx, _Val, _Fill_tm(_Sys)); + } + +private: + _Chrono_formatter<_CharT, false> _Impl; +}; + +template +struct formatter<_CHRONO gps_time<_Duration>, _CharT> { + formatter() { + _Impl._Time_zone_abbreviation = _STATICALLY_WIDEN(_CharT, "GPS"); + } + + auto parse(basic_format_parse_context<_CharT>& _Parse_ctx) { + return _Impl.template _Parse<_CHRONO gps_time<_Duration>>(_Parse_ctx); + } + + template + auto format(const _CHRONO gps_time<_Duration>& _Val, _FormatContext& _FormatCtx) { + const auto _Sys = _CHRONO sys_time<_Duration>{_Val.time_since_epoch()} + + (_CHRONO sys_days{_CHRONO year{1980} / 1 / _CHRONO Sunday[1]} + - _CHRONO sys_days{_CHRONO year{1970} / 1 / 1}); + return _Impl._Write(_FormatCtx, _Val, _Fill_tm(_Sys)); + } + +private: + _Chrono_formatter<_CharT, false> _Impl; +}; + +template +struct formatter<_CHRONO file_time<_Duration>, _CharT> { + formatter() { + _Impl._Time_zone_abbreviation = _STATICALLY_WIDEN(_CharT, "UTC"); + } + + auto parse(basic_format_parse_context<_CharT>& _Parse_ctx) { + return _Impl.template _Parse<_CHRONO file_time<_Duration>>(_Parse_ctx); + } + + template + auto format(const _CHRONO file_time<_Duration>& _Val, _FormatContext& _FormatCtx) { + const auto _Sys = _CHRONO clock_cast<_CHRONO system_clock>(_Val); + return _Impl._Write(_FormatCtx, _Val, _Fill_tm(_Sys)); + } + +private: + _Chrono_formatter<_CharT, false> _Impl; +}; + +template +struct formatter<_CHRONO local_time<_Duration>, _CharT> // + : _Fill_tm_formatter<_CHRONO local_time<_Duration>, _CharT> {}; + #endif // __cpp_lib_concepts #endif // _HAS_CXX20 diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index c591027dac..742a8049a6 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -423,6 +423,27 @@ void test_year_month_formatter() { throw_helper(STR("{:%d}"), 2000y / July); } +template +void test_clock_formatter() { + stream_helper(STR("1970-01-01 00:00:00"), sys_seconds{}); + stream_helper(STR("1970-01-01"), sys_days{}); + stream_helper(STR("1970-01-01 00:00:00"), utc_seconds{}); + stream_helper(STR("1958-01-01 00:00:00"), tai_seconds{}); + stream_helper(STR("1980-01-06 00:00:00"), gps_seconds{}); + stream_helper(STR("1601-01-01 00:00:00"), file_time{}); + stream_helper(STR("1970-01-01 00:00:00"), local_seconds{}); + + assert(format(STR("{:%Z %z %Oz %Ez}"), sys_seconds{}) == STR("UTC +0000 +00:00 +00:00")); + assert(format(STR("{:%Z %z %Oz %Ez}"), sys_days{}) == STR("UTC +0000 +00:00 +00:00")); + assert(format(STR("{:%Z %z %Oz %Ez}"), utc_seconds{}) == STR("UTC +0000 +00:00 +00:00")); + assert(format(STR("{:%Z %z %Oz %Ez}"), tai_seconds{}) == STR("TAI +0000 +00:00 +00:00")); + assert(format(STR("{:%Z %z %Oz %Ez}"), gps_seconds{}) == STR("GPS +0000 +00:00 +00:00")); + assert(format(STR("{:%Z %z %Oz %Ez}"), file_time{}) == STR("UTC +0000 +00:00 +00:00")); + throw_helper(STR("{:%Z %z %Oz %Ez}"), local_seconds{}); + + assert(format(STR("{:%S}"), utc_clock::from_sys(get_tzdb().leap_seconds.front().date()) - 1s) == STR("60")); +} + int main() { test_parse_conversion_spec(); test_parse_conversion_spec(); @@ -462,4 +483,7 @@ int main() { test_year_month_formatter(); test_year_month_formatter(); + + test_clock_formatter(); + test_clock_formatter(); } From d4183663518aa4bc341150915f8caa5daabc81ef Mon Sep 17 00:00:00 2001 From: mnatsuhara <46756417+mnatsuhara@users.noreply.github.com> Date: Fri, 16 Apr 2021 18:27:43 -0700 Subject: [PATCH 22/41] wd, wdi formatting (#1847) * wd, wdi formatting * Update libcxx skips. * Arrange formatters in Standard order. Co-authored-by: Stephan T. Lavavej --- stl/inc/chrono | 15 ++++++-- tests/libcxx/expected_results.txt | 6 ++-- tests/libcxx/skipped_tests.txt | 3 -- .../test.cpp | 34 +++++++++++++++++++ 4 files changed, 50 insertions(+), 8 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index e73947a1ec..a068eb3a98 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5464,6 +5464,10 @@ namespace chrono { _Month = static_cast(_Val); } else if constexpr (is_same_v<_Ty, year>) { _Year = static_cast(_Val); + } else if constexpr (is_same_v<_Ty, weekday>) { + _Weekday = static_cast(_Val.c_encoding()); + } else if constexpr (is_same_v<_Ty, weekday_indexed>) { + _Weekday = static_cast(_Val.weekday().c_encoding()); } else if constexpr (is_same_v<_Ty, month_day>) { _Day = static_cast(_Val.day()); _Month = static_cast(_Val.month()); @@ -5744,7 +5748,7 @@ struct _Chrono_formatter { return _Type == 'b' || _Type == 'B' || _Type == 'h' || _Type == 'm'; } else if constexpr (is_same_v<_Ty, _CHRONO year>) { return _Type == 'Y' || _Type == 'y' || _Type == 'C'; - } else if constexpr (is_same_v<_Ty, _CHRONO weekday>) { + } else if constexpr (_Is_any_of_v<_Ty, _CHRONO weekday, _CHRONO weekday_indexed>) { return _Type == 'a' || _Type == 'A' || _Type == 'u' || _Type == 'w'; } else if constexpr (_Is_any_of_v<_Ty, _CHRONO month_day, _CHRONO month_day_last>) { return _Is_valid_type<_CHRONO month>(_Type) || _Is_valid_type<_CHRONO day>(_Type); @@ -5952,6 +5956,14 @@ template struct formatter<_CHRONO year, _CharT> // : _Fill_tm_formatter<_CHRONO year, _CharT> {}; +template +struct formatter<_CHRONO weekday, _CharT> // + : _Fill_tm_formatter<_CHRONO weekday, _CharT> {}; + +template +struct formatter<_CHRONO weekday_indexed, _CharT> // + : _Fill_tm_formatter<_CHRONO weekday_indexed, _CharT> {}; + template struct formatter<_CHRONO month_day, _CharT> // : _Fill_tm_formatter<_CHRONO month_day, _CharT> {}; @@ -6106,7 +6118,6 @@ template struct formatter<_CHRONO local_time<_Duration>, _CharT> // : _Fill_tm_formatter<_CHRONO local_time<_Duration>, _CharT> {}; - #endif // __cpp_lib_concepts #endif // _HAS_CXX20 diff --git a/tests/libcxx/expected_results.txt b/tests/libcxx/expected_results.txt index 5707322559..9bd7270d66 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -276,11 +276,8 @@ std/utilities/memory/default.allocator/allocator.members/allocate.verify.cpp SKI # *** MISSING STL FEATURES *** # C++20 P0355R7 " Calendars And Time Zones" -std/utilities/time/time.cal/time.cal.mwd/time.cal.mwd.nonmembers/streaming.pass.cpp FAIL std/utilities/time/time.cal/time.cal.mwdlast/time.cal.mwdlast.nonmembers/streaming.pass.cpp FAIL std/utilities/time/time.cal/time.cal.wdidx/time.cal.wdidx.nonmembers/streaming.pass.cpp FAIL -std/utilities/time/time.cal/time.cal.wdlast/time.cal.wdlast.nonmembers/streaming.pass.cpp FAIL -std/utilities/time/time.cal/time.cal.weekday/time.cal.weekday.nonmembers/streaming.pass.cpp FAIL std/utilities/time/time.cal/time.cal.ymwd/time.cal.ymwd.nonmembers/streaming.pass.cpp FAIL std/utilities/time/time.cal/time.cal.ymwdlast/time.cal.ymwdlast.nonmembers/streaming.pass.cpp FAIL @@ -878,6 +875,9 @@ std/utilities/time/time.cal/time.cal.day/time.cal.day.nonmembers/streaming.pass. std/utilities/time/time.cal/time.cal.md/time.cal.md.nonmembers/streaming.pass.cpp SKIPPED std/utilities/time/time.cal/time.cal.mdlast/streaming.pass.cpp SKIPPED std/utilities/time/time.cal/time.cal.month/time.cal.month.nonmembers/streaming.pass.cpp SKIPPED +std/utilities/time/time.cal/time.cal.mwd/time.cal.mwd.nonmembers/streaming.pass.cpp SKIPPED +std/utilities/time/time.cal/time.cal.wdlast/time.cal.wdlast.nonmembers/streaming.pass.cpp SKIPPED +std/utilities/time/time.cal/time.cal.weekday/time.cal.weekday.nonmembers/streaming.pass.cpp SKIPPED std/utilities/time/time.cal/time.cal.year/time.cal.year.nonmembers/streaming.pass.cpp SKIPPED std/utilities/time/time.cal/time.cal.ym/time.cal.ym.nonmembers/streaming.pass.cpp SKIPPED std/utilities/time/time.cal/time.cal.ymd/time.cal.ymd.nonmembers/streaming.pass.cpp SKIPPED diff --git a/tests/libcxx/skipped_tests.txt b/tests/libcxx/skipped_tests.txt index a8bd7e8b84..ae453bbaed 100644 --- a/tests/libcxx/skipped_tests.txt +++ b/tests/libcxx/skipped_tests.txt @@ -276,11 +276,8 @@ utilities\memory\default.allocator\allocator.members\allocate.verify.cpp # *** MISSING STL FEATURES *** # C++20 P0355R7 " Calendars And Time Zones" -utilities\time\time.cal\time.cal.mwd\time.cal.mwd.nonmembers\streaming.pass.cpp utilities\time\time.cal\time.cal.mwdlast\time.cal.mwdlast.nonmembers\streaming.pass.cpp utilities\time\time.cal\time.cal.wdidx\time.cal.wdidx.nonmembers\streaming.pass.cpp -utilities\time\time.cal\time.cal.wdlast\time.cal.wdlast.nonmembers\streaming.pass.cpp -utilities\time\time.cal\time.cal.weekday\time.cal.weekday.nonmembers\streaming.pass.cpp utilities\time\time.cal\time.cal.ymwd\time.cal.ymwd.nonmembers\streaming.pass.cpp utilities\time\time.cal\time.cal.ymwdlast\time.cal.ymwdlast.nonmembers\streaming.pass.cpp diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index 742a8049a6..4e5d626acf 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -351,6 +351,34 @@ void test_year_formatter() { stream_helper(STR("-32768 is not a valid year"), year{-32768}); } +template +void test_weekday_formatter() { + weekday invalid{10}; + assert(format(STR("{}"), weekday{3}) == STR("Wed")); + stream_helper(STR("Wed"), weekday{3}); + stream_helper(STR("10 is not a valid weekday"), invalid); + + assert(format(STR("{:%a %A}"), weekday{6}) == STR("Sat Saturday")); + assert(format(STR("{:%u %w}"), weekday{6}) == STR("6 6")); + assert(format(STR("{:%u %w}"), weekday{0}) == STR("7 0")); +} + +template +void test_weekday_indexed_formatter() { + weekday_indexed invalid1{Tuesday, 10}; + weekday_indexed invalid2{weekday{10}, 3}; + weekday_indexed invalid3{weekday{14}, 9}; + assert(format(STR("{}"), weekday_indexed{Monday, 1}) == STR("Mon[1]")); + stream_helper(STR("Mon[1]"), weekday_indexed{Monday, 1}); + stream_helper(STR("Tue[10 is not a valid index]"), invalid1); + stream_helper(STR("10 is not a valid weekday[3]"), invalid2); + stream_helper(STR("14 is not a valid weekday[9 is not a valid index]"), invalid3); + + assert(format(STR("{:%a %A}"), weekday_indexed{Monday, 2}) == STR("Mon Monday")); + assert(format(STR("{:%u %w}"), weekday_indexed{Tuesday, 3}) == STR("2 2")); + assert(format(STR("{:%u %w}"), weekday_indexed{Sunday, 4}) == STR("7 0")); +} + template void test_year_month_day_formatter() { year_month_day invalid{year{1234}, month{0}, day{31}}; @@ -460,6 +488,12 @@ int main() { test_year_formatter(); test_year_formatter(); + test_weekday_formatter(); + test_weekday_formatter(); + + test_weekday_indexed_formatter(); + test_weekday_indexed_formatter(); + test_year_month_day_formatter(); test_year_month_day_formatter(); From 8aeda0cf81c8e3832f568078d77dde20f39b23b3 Mon Sep 17 00:00:00 2001 From: Elnar D Date: Fri, 16 Apr 2021 13:03:25 -0700 Subject: [PATCH 23/41] Only throw on hh_mm_ss if the hour field is used and is OOB --- stl/inc/chrono | 28 +++++++------------ .../test.cpp | 1 + 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index a068eb3a98..b2751d4e9f 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5899,8 +5899,16 @@ struct _Chrono_formatter { _Os << _CharT{'/'}; _Custom_write(_Os, {._Type = 'y'}, _Time, _Val); return true; + case 'H': + if constexpr (_Is_specialization_v<_Ty, _CHRONO hh_mm_ss>) { + if (-_CHRONO hours{24} >= _Val.hours() || _Val.hours() >= _CHRONO hours{24}) { + _THROW(format_error("Cannot localize hh_mm_ss longer than 24 hours.")); + } + } + return false; case 'T': // Alias for %H:%M:%S but we need to rewrite %S to display fractions of a second. + _Custom_write(_Os, {._Type = 'H'}, _Time, _Val); _Os << _STD put_time(&_Time, _STATICALLY_WIDEN(_CharT, "%H:%M:")); [[fallthrough]]; case 'S': @@ -5993,24 +6001,8 @@ struct formatter<_CHRONO year_month_weekday_last, _CharT> : _Fill_tm_formatter<_CHRONO year_month_weekday_last, _CharT> {}; template -struct formatter<_CHRONO hh_mm_ss<_CHRONO duration<_Rep, _Period>>, _CharT> { - using _Ty = _CHRONO hh_mm_ss<_CHRONO duration<_Rep, _Period>>; - - auto parse(basic_format_parse_context<_CharT>& _Parse_ctx) { - return _Impl.template _Parse<_Ty>(_Parse_ctx); - } - - template - auto format(const _Ty& _Val, _FormatContext& _FormatCtx) { - if (-_CHRONO hours{24} >= _Val.hours() || _Val.hours() >= _CHRONO hours{24}) { - _THROW(format_error("Cannot localize hh_mm_ss longer than 24 hours.")); - } - return _Impl._Write(_FormatCtx, _Val, _Fill_tm(_Val)); - } - -private: - _Chrono_formatter<_CharT, false> _Impl; -}; +struct formatter<_CHRONO hh_mm_ss<_CHRONO duration<_Rep, _Period>>, _CharT> + : _Fill_tm_formatter<_CHRONO hh_mm_ss<_CHRONO duration<_Rep, _Period>>, _CharT> {}; template struct formatter<_CHRONO sys_time<_Duration>, _CharT> { diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index 4e5d626acf..7666759b7e 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -423,6 +423,7 @@ void test_hh_mm_ss_formatter() { throw_helper(STR("{}"), hh_mm_ss{24h}); throw_helper(STR("{}"), hh_mm_ss{-24h}); + assert(format(STR("{:%M %S}"), hh_mm_ss{27h + 12min + 30s}) == STR("12 30")); } template From 6c06f2392c3531fedf9c92fa02c31b160a0cb296 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Fri, 16 Apr 2021 22:57:02 -0700 Subject: [PATCH 24/41] `` formatting: fix UB, various cleanups (#1848) * Fix UB, various cleanups. * Optimize with common_type_t<_Duration, days>. Co-authored-by: MattStephanson <68978048+MattStephanson@users.noreply.github.com> --- stl/inc/chrono | 85 ++++++++++++++++++++++++++------------------------ 1 file changed, 44 insertions(+), 41 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index b2751d4e9f..75f1331e55 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5225,8 +5225,8 @@ concept _Chrono_parse_spec_callbacks = _Parse_align_callbacks<_Ty, _CharT> // clang-format off template -concept _Has_ok = requires (_Ty _At) { - {_At.ok()} -> same_as; +concept _Has_ok = requires(_Ty _At) { + { _At.ok() } -> same_as; }; // clang-format on @@ -5473,7 +5473,7 @@ namespace chrono { _Month = static_cast(_Val.month()); } else if constexpr (is_same_v<_Ty, month_day_last>) { _Month = static_cast(_Val.month()); - _Day = static_cast(_Last_day_table[_Month - 1]); + _Day = static_cast(_Last_day_table[(_Month - 1) & 0xF]); } else if constexpr (is_same_v<_Ty, year_month>) { _Month = static_cast(_Val.month()); _Year = static_cast(_Val.year()); @@ -5621,7 +5621,7 @@ namespace chrono { template // clang-format off - requires (!treat_as_floating_point_v && (_Duration{1} < days{1})) + requires (!treat_as_floating_point_v && _Duration{1} < days{1}) basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const sys_time<_Duration>& _Val) { // clang-format on const auto _Dp = _CHRONO floor(_Val); @@ -5660,8 +5660,13 @@ namespace chrono { } } // namespace chrono -template +template struct _Chrono_formatter { + _Chrono_formatter() = default; + + explicit _Chrono_formatter(const basic_string_view<_CharT> _Time_zone_abbreviation_) + : _Time_zone_abbreviation{_Time_zone_abbreviation_} {} + template _NODISCARD auto _Parse(basic_format_parse_context<_CharT>& _Parse_ctx) { _Chrono_specs_setter<_CharT, basic_format_parse_context<_CharT>> _Callback{_Specs, _Parse_ctx}; @@ -5673,8 +5678,18 @@ struct _Chrono_formatter { _THROW(format_error("Missing '}' in format string.")); } - if (!_Allow_precision && _Specs._Precision != -1) { - _THROW(format_error("Precision specification invalid for type.")); + if constexpr (_Is_specialization_v<_Ty, _CHRONO duration>) { + if constexpr (!_CHRONO treat_as_floating_point_v) { + if (_Specs._Precision != -1) { + _THROW(format_error("Precision specification invalid for chrono::duration type with " + "integral representation type, see N4885 [time.format]/1.")); + } + } + } else { + if (_Specs._Precision != -1) { + _THROW(format_error("Precision specification invalid for non-chrono::duration type, " + "see N4885 [time.format]/1.")); + } } const auto& _List = _Specs._Chrono_specs_list; @@ -5843,9 +5858,11 @@ struct _Chrono_formatter { _THROW(format_error("Cannot print the last day of February without a year")); } } + if (_Has_modifier) { return false; } + if (_Time.tm_mday < 10) { _Os << (_Specs._Type == 'd' ? _CharT{'0'} : _CharT{' '}); } @@ -5855,6 +5872,7 @@ struct _Chrono_formatter { if (_Has_modifier) { return false; } + if (_Month < 10) { _Os << _CharT{'0'}; } @@ -5864,6 +5882,7 @@ struct _Chrono_formatter { if (_Has_modifier) { return false; } + if (_Year < 0) { _Os << _CharT{'-'}; } @@ -5879,6 +5898,7 @@ struct _Chrono_formatter { if (_Has_modifier) { return false; } + if (_Year < 0) { _Os << _CharT{'-'}; } @@ -5901,8 +5921,8 @@ struct _Chrono_formatter { return true; case 'H': if constexpr (_Is_specialization_v<_Ty, _CHRONO hh_mm_ss>) { - if (-_CHRONO hours{24} >= _Val.hours() || _Val.hours() >= _CHRONO hours{24}) { - _THROW(format_error("Cannot localize hh_mm_ss longer than 24 hours.")); + if (_Val.hours() <= -_CHRONO hours{24} || _CHRONO hours{24} <= _Val.hours()) { + _THROW(format_error("Cannot localize hh_mm_ss with an absolute value of 24 hours or more.")); } } return false; @@ -5934,7 +5954,7 @@ struct _Chrono_formatter { _Chrono_format_specs<_CharT> _Specs{}; bool _No_chrono_specs = false; - basic_string_view<_CharT> _Time_zone_abbreviation; + basic_string_view<_CharT> _Time_zone_abbreviation{}; }; template @@ -5949,7 +5969,7 @@ struct _Fill_tm_formatter { } private: - _Chrono_formatter<_CharT, false> _Impl; + _Chrono_formatter<_CharT> _Impl; }; template @@ -6006,10 +6026,6 @@ struct formatter<_CHRONO hh_mm_ss<_CHRONO duration<_Rep, _Period>>, _CharT> template struct formatter<_CHRONO sys_time<_Duration>, _CharT> { - formatter() { - _Impl._Time_zone_abbreviation = _STATICALLY_WIDEN(_CharT, "UTC"); - } - auto parse(basic_format_parse_context<_CharT>& _Parse_ctx) { return _Impl.template _Parse<_CHRONO sys_time<_Duration>>(_Parse_ctx); } @@ -6020,15 +6036,11 @@ struct formatter<_CHRONO sys_time<_Duration>, _CharT> { } private: - _Chrono_formatter<_CharT, false> _Impl; + _Chrono_formatter<_CharT> _Impl{_STATICALLY_WIDEN(_CharT, "UTC")}; }; template struct formatter<_CHRONO utc_time<_Duration>, _CharT> { - formatter() { - _Impl._Time_zone_abbreviation = _STATICALLY_WIDEN(_CharT, "UTC"); - } - auto parse(basic_format_parse_context<_CharT>& _Parse_ctx) { return _Impl.template _Parse<_CHRONO utc_time<_Duration>>(_Parse_ctx); } @@ -6040,58 +6052,49 @@ struct formatter<_CHRONO utc_time<_Duration>, _CharT> { } private: - _Chrono_formatter<_CharT, false> _Impl; + _Chrono_formatter<_CharT> _Impl{_STATICALLY_WIDEN(_CharT, "UTC")}; }; template struct formatter<_CHRONO tai_time<_Duration>, _CharT> { - formatter() { - _Impl._Time_zone_abbreviation = _STATICALLY_WIDEN(_CharT, "TAI"); - } - auto parse(basic_format_parse_context<_CharT>& _Parse_ctx) { return _Impl.template _Parse<_CHRONO tai_time<_Duration>>(_Parse_ctx); } template auto format(const _CHRONO tai_time<_Duration>& _Val, _FormatContext& _FormatCtx) { - const auto _Sys = _CHRONO sys_time<_Duration>{_Val.time_since_epoch()} - - (_CHRONO sys_days{_CHRONO year{1970} / 1 / 1} - _CHRONO sys_days{_CHRONO year{1958} / 1 / 1}); + using namespace chrono; + using _Common = common_type_t<_Duration, days>; // slightly optimize by performing conversion at compile time + constexpr _Common _Offset{sys_days{year{1970} / January / 1} - sys_days{year{1958} / January / 1}}; + const auto _Sys = sys_time<_Duration>{_Val.time_since_epoch()} - _Offset; return _Impl._Write(_FormatCtx, _Val, _Fill_tm(_Sys)); } private: - _Chrono_formatter<_CharT, false> _Impl; + _Chrono_formatter<_CharT> _Impl{_STATICALLY_WIDEN(_CharT, "TAI")}; }; template struct formatter<_CHRONO gps_time<_Duration>, _CharT> { - formatter() { - _Impl._Time_zone_abbreviation = _STATICALLY_WIDEN(_CharT, "GPS"); - } - auto parse(basic_format_parse_context<_CharT>& _Parse_ctx) { return _Impl.template _Parse<_CHRONO gps_time<_Duration>>(_Parse_ctx); } template auto format(const _CHRONO gps_time<_Duration>& _Val, _FormatContext& _FormatCtx) { - const auto _Sys = _CHRONO sys_time<_Duration>{_Val.time_since_epoch()} - + (_CHRONO sys_days{_CHRONO year{1980} / 1 / _CHRONO Sunday[1]} - - _CHRONO sys_days{_CHRONO year{1970} / 1 / 1}); + using namespace chrono; + using _Common = common_type_t<_Duration, days>; // slightly optimize by performing conversion at compile time + constexpr _Common _Offset{sys_days{year{1980} / January / Sunday[1]} - sys_days{year{1970} / January / 1}}; + const auto _Sys = sys_time<_Duration>{_Val.time_since_epoch()} + _Offset; return _Impl._Write(_FormatCtx, _Val, _Fill_tm(_Sys)); } private: - _Chrono_formatter<_CharT, false> _Impl; + _Chrono_formatter<_CharT> _Impl{_STATICALLY_WIDEN(_CharT, "GPS")}; }; template struct formatter<_CHRONO file_time<_Duration>, _CharT> { - formatter() { - _Impl._Time_zone_abbreviation = _STATICALLY_WIDEN(_CharT, "UTC"); - } - auto parse(basic_format_parse_context<_CharT>& _Parse_ctx) { return _Impl.template _Parse<_CHRONO file_time<_Duration>>(_Parse_ctx); } @@ -6103,7 +6106,7 @@ struct formatter<_CHRONO file_time<_Duration>, _CharT> { } private: - _Chrono_formatter<_CharT, false> _Impl; + _Chrono_formatter<_CharT> _Impl{_STATICALLY_WIDEN(_CharT, "UTC")}; }; template From d718499b365ad71033f20573c9890eef2fb50d1f Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Sat, 17 Apr 2021 02:47:27 -0700 Subject: [PATCH 25/41] ``: Implement exception class constructors (#1849) * hh_mm_ss::hours() is already an absolute value. Co-authored-by: statementreply * Implement nonexistent_local_time/ambiguous_local_time. * Test nonexistent_local_time/ambiguous_local_time. * Fix P0355R7_calendars_and_time_zones_time_zones. We can't use floating-point durations because the exception constructors in [time.zone.exception] will stream local_time, and [time.clock.local] implements that by streaming sys_time, and that's constrained by [time.clock.system.nonmembers] to reject floating-point durations. Co-authored-by: statementreply --- stl/inc/chrono | 57 +++++++++++++++++-- .../test.cpp | 36 ++++++++++++ .../test.cpp | 42 ++++++-------- 3 files changed, 105 insertions(+), 30 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 75f1331e55..5cd18ec27d 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -2266,16 +2266,38 @@ namespace chrono { class nonexistent_local_time : public runtime_error { public: template - nonexistent_local_time(const local_time<_Duration>&, const local_info&) - : runtime_error("TRANSITION: work in progress") {} + nonexistent_local_time(const local_time<_Duration>& _Tp, const local_info& _Info) + : runtime_error(_Make_string(_Tp, _Info)) {} + + private: +#ifdef __cpp_lib_concepts + template + _NODISCARD static string _Make_string(const local_time<_Duration>& _Tp, const local_info& _Info); +#else // ^^^ no workaround / workaround vvv + template + _NODISCARD static string _Make_string(const local_time<_Duration>&, const local_info&) { + return "nonexistent_local_time"; + } +#endif // ^^^ workaround ^^^ }; // CLASS ambiguous_local_time class ambiguous_local_time : public runtime_error { public: template - ambiguous_local_time(const local_time<_Duration>&, const local_info&) - : runtime_error("TRANSITION: work in progress") {} + ambiguous_local_time(const local_time<_Duration>& _Tp, const local_info& _Info) + : runtime_error(_Make_string(_Tp, _Info)) {} + + private: +#ifdef __cpp_lib_concepts + template + _NODISCARD static string _Make_string(const local_time<_Duration>& _Tp, const local_info& _Info); +#else // ^^^ no workaround / workaround vvv + template + _NODISCARD static string _Make_string(const local_time<_Duration>&, const local_info&) { + return "ambiguous_local_time"; + } +#endif // ^^^ workaround ^^^ }; // [time.zone.timezone] @@ -3180,7 +3202,7 @@ namespace chrono { #ifdef __cpp_lib_concepts const auto _Leap_cmp = _Utc_leap_second <=> _Time_floor; #else // ^^^ __cpp_lib_concepts / TRANSITION, GH-395 workaround vvv - const auto _Leap_cmp = _Utc_leap_second > _Time_floor ? strong_ordering::greater + const auto _Leap_cmp = _Utc_leap_second > _Time_floor ? strong_ordering::greater : _Utc_leap_second == _Time_floor ? strong_ordering::equal : strong_ordering::less; #endif // ^^^ workaround @@ -5921,7 +5943,7 @@ struct _Chrono_formatter { return true; case 'H': if constexpr (_Is_specialization_v<_Ty, _CHRONO hh_mm_ss>) { - if (_Val.hours() <= -_CHRONO hours{24} || _CHRONO hours{24} <= _Val.hours()) { + if (_Val.hours() >= _CHRONO hours{24}) { _THROW(format_error("Cannot localize hh_mm_ss with an absolute value of 24 hours or more.")); } } @@ -6113,6 +6135,29 @@ template struct formatter<_CHRONO local_time<_Duration>, _CharT> // : _Fill_tm_formatter<_CHRONO local_time<_Duration>, _CharT> {}; +namespace chrono { + template + _NODISCARD string nonexistent_local_time::_Make_string(const local_time<_Duration>& _Tp, const local_info& _Info) { + ostringstream _Os; + _Os << _Tp << " is in a gap between\n" + << local_seconds{_Info.first.end.time_since_epoch()} + _Info.first.offset << ' ' << _Info.first.abbrev + << " and\n" + << local_seconds{_Info.second.begin.time_since_epoch()} + _Info.second.offset << ' ' << _Info.second.abbrev + << " which are both equivalent to\n" + << _Info.first.end << " UTC"; + return _STD move(_Os).str(); + } + + template + _NODISCARD string ambiguous_local_time::_Make_string(const local_time<_Duration>& _Tp, const local_info& _Info) { + ostringstream _Os; + _Os << _Tp << " is ambiguous. It could be\n" + << _Tp << ' ' << _Info.first.abbrev << " == " << _Tp - _Info.first.offset << " UTC or\n" + << _Tp << ' ' << _Info.second.abbrev << " == " << _Tp - _Info.second.offset << " UTC"; + return _STD move(_Os).str(); + } +} // namespace chrono + #endif // __cpp_lib_concepts #endif // _HAS_CXX20 diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index 7666759b7e..c401013896 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -473,6 +474,39 @@ void test_clock_formatter() { assert(format(STR("{:%S}"), utc_clock::from_sys(get_tzdb().leap_seconds.front().date()) - 1s) == STR("60")); } +void test_exception_classes() { + { // N4885 [time.zone.exception.nonexist]/4 + string s; + + try { + (void) zoned_time{"America/New_York", local_days{Sunday[2] / March / 2016} + 2h + 30min}; + } catch (const nonexistent_local_time& e) { + s = e.what(); + } + + assert(s + == "2016-03-13 02:30:00 is in a gap between\n" + "2016-03-13 02:00:00 EST and\n" + "2016-03-13 03:00:00 EDT which are both equivalent to\n" + "2016-03-13 07:00:00 UTC"); + } + + { // N4885 [time.zone.exception.ambig]/4 + string s; + + try { + (void) zoned_time{"America/New_York", local_days{Sunday[1] / November / 2016} + 1h + 30min}; + } catch (const ambiguous_local_time& e) { + s = e.what(); + } + + assert(s + == "2016-11-06 01:30:00 is ambiguous. It could be\n" + "2016-11-06 01:30:00 EDT == 2016-11-06 05:30:00 UTC or\n" + "2016-11-06 01:30:00 EST == 2016-11-06 06:30:00 UTC"); + } +} + int main() { test_parse_conversion_spec(); test_parse_conversion_spec(); @@ -521,4 +555,6 @@ int main() { test_clock_formatter(); test_clock_formatter(); + + test_exception_classes(); } diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp index a84824e67d..d94cc005c2 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_time_zones/test.cpp @@ -360,46 +360,40 @@ void validate_precision(const time_zone* tz, const pair& void timezone_precision_test() { const auto& my_tzdb = get_tzdb(); - using MilliDur = duration; - using MicroDur = duration; { using namespace Sydney; auto tz = my_tzdb.locate_zone(Tz_name); validate_precision(tz, Std_to_Day, sys_seconds::duration{1}); - validate_precision(tz, Std_to_Day, MilliDur{1}); - validate_precision(tz, Std_to_Day, MilliDur{0.5}); - validate_precision(tz, Std_to_Day, MilliDur{0.05}); - validate_precision(tz, Std_to_Day, MilliDur{0.005}); - validate_precision(tz, Std_to_Day, MilliDur{0.0005}); - // precision limit... - validate_precision(tz, Std_to_Day, MicroDur{1}); - validate_precision(tz, Std_to_Day, MicroDur{0.5}); - // precision limit... + validate_precision(tz, Std_to_Day, milliseconds{100}); + validate_precision(tz, Std_to_Day, milliseconds{10}); + validate_precision(tz, Std_to_Day, milliseconds{1}); + + validate_precision(tz, Std_to_Day, microseconds{100}); + validate_precision(tz, Std_to_Day, microseconds{10}); + validate_precision(tz, Std_to_Day, microseconds{1}); // validate opposite transition - validate_precision(tz, Day_to_Std, MicroDur{0.5}); - validate_precision(tz, Day_to_Std, MilliDur{0.0005}); + validate_precision(tz, Day_to_Std, milliseconds{1}); + validate_precision(tz, Day_to_Std, microseconds{1}); } { using namespace LA; auto tz = my_tzdb.locate_zone(Tz_name); validate_precision(tz, Std_to_Day, sys_seconds::duration{1}); - validate_precision(tz, Std_to_Day, MilliDur{1}); - validate_precision(tz, Std_to_Day, MilliDur{0.5}); - validate_precision(tz, Std_to_Day, MilliDur{0.05}); - validate_precision(tz, Std_to_Day, MilliDur{0.005}); - validate_precision(tz, Std_to_Day, MilliDur{0.0005}); - // precision limit... - validate_precision(tz, Std_to_Day, MicroDur{1}); - validate_precision(tz, Std_to_Day, MicroDur{0.5}); - // precision limit... + validate_precision(tz, Std_to_Day, milliseconds{100}); + validate_precision(tz, Std_to_Day, milliseconds{10}); + validate_precision(tz, Std_to_Day, milliseconds{1}); + + validate_precision(tz, Std_to_Day, microseconds{100}); + validate_precision(tz, Std_to_Day, microseconds{10}); + validate_precision(tz, Std_to_Day, microseconds{1}); // validate opposite transition - validate_precision(tz, Day_to_Std, MicroDur{0.5}); - validate_precision(tz, Day_to_Std, MilliDur{0.0005}); + validate_precision(tz, Day_to_Std, milliseconds{1}); + validate_precision(tz, Day_to_Std, microseconds{1}); } } From d705b61837e4fcca7e5ec1cdf6af417650f0495d Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Sun, 18 Apr 2021 03:50:41 -0700 Subject: [PATCH 26/41] `` formatting: `weekday_last`, `month_weekday`, `month_weekday_last` (#1854) * Add/test weekday_last, month_weekday, month_weekday_last. * In _Fill_tm(): + weekday_indexed and weekday_last can share code. + month_weekday and month_weekday_last have different accessors. + Cleanup: Unify the code for year_month_weekday and year_month_weekday_last. * In _Is_valid_type(): + weekday, weekday_indexed, and weekday_last all support the "weekday types". + month_weekday and month_weekday_last support "month types" and "weekday types". (As mentioned above, their accessors are actually weekday_indexed() and weekday_last(), but it seemed pointless to have separate cases to "recurse" into the weekday_indexed and weekday_last types, when the answer is always the same.) + Remove TRANSITION and change the final static_assert to "should be unreachable", which is the pattern that we use elsewhere. * Add the new formatters, all powered by _Fill_tm_formatter. In P0355R7_calendars_and_time_zones_formatting/test.cpp: * Rename charT to CharT for consistency (this is needed by the STR macro, if it were ever used in these functions). * Add empty_braces_helper() to test both format("{}") and operator<<. This should supersede stream_helper() but I'm not making that change here. * Test the new types. * Implement tests for year_month_day_last, year_month_weekday, and year_month_weekday_last now that the necessary formatters are available. * Call the new test functions. * Update libcxx skips for C++20 features. --- stl/inc/chrono | 34 +++-- tests/libcxx/expected_results.txt | 60 ++++---- tests/libcxx/skipped_tests.txt | 58 +++---- .../test.cpp | 141 ++++++++++++++++-- 4 files changed, 219 insertions(+), 74 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 5cd18ec27d..cc33b40ac0 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5488,7 +5488,7 @@ namespace chrono { _Year = static_cast(_Val); } else if constexpr (is_same_v<_Ty, weekday>) { _Weekday = static_cast(_Val.c_encoding()); - } else if constexpr (is_same_v<_Ty, weekday_indexed>) { + } else if constexpr (_Is_any_of_v<_Ty, weekday_indexed, weekday_last>) { _Weekday = static_cast(_Val.weekday().c_encoding()); } else if constexpr (is_same_v<_Ty, month_day>) { _Day = static_cast(_Val.day()); @@ -5496,6 +5496,12 @@ namespace chrono { } else if constexpr (is_same_v<_Ty, month_day_last>) { _Month = static_cast(_Val.month()); _Day = static_cast(_Last_day_table[(_Month - 1) & 0xF]); + } else if constexpr (is_same_v<_Ty, month_weekday>) { + _Month = static_cast(_Val.month()); + _Weekday = static_cast(_Val.weekday_indexed().weekday().c_encoding()); + } else if constexpr (is_same_v<_Ty, month_weekday_last>) { + _Month = static_cast(_Val.month()); + _Weekday = static_cast(_Val.weekday_last().weekday().c_encoding()); } else if constexpr (is_same_v<_Ty, year_month>) { _Month = static_cast(_Val.month()); _Year = static_cast(_Val.year()); @@ -5509,12 +5515,7 @@ namespace chrono { _Month = static_cast(_Val.month()); _Year = static_cast(_Val.year()); _Weekday = year_month_day{_Val}._Calculate_weekday(); - } else if constexpr (is_same_v<_Ty, year_month_weekday>) { - _Day = static_cast(year_month_day{_Val}.day()); - _Month = static_cast(_Val.month()); - _Year = static_cast(_Val.year()); - _Weekday = static_cast(_Val.weekday().c_encoding()); - } else if constexpr (is_same_v<_Ty, year_month_weekday_last>) { + } else if constexpr (_Is_any_of_v<_Ty, year_month_weekday, year_month_weekday_last>) { _Day = static_cast(year_month_day{_Val}.day()); _Month = static_cast(_Val.month()); _Year = static_cast(_Val.year()); @@ -5785,10 +5786,12 @@ struct _Chrono_formatter { return _Type == 'b' || _Type == 'B' || _Type == 'h' || _Type == 'm'; } else if constexpr (is_same_v<_Ty, _CHRONO year>) { return _Type == 'Y' || _Type == 'y' || _Type == 'C'; - } else if constexpr (_Is_any_of_v<_Ty, _CHRONO weekday, _CHRONO weekday_indexed>) { + } else if constexpr (_Is_any_of_v<_Ty, _CHRONO weekday, _CHRONO weekday_indexed, _CHRONO weekday_last>) { return _Type == 'a' || _Type == 'A' || _Type == 'u' || _Type == 'w'; } else if constexpr (_Is_any_of_v<_Ty, _CHRONO month_day, _CHRONO month_day_last>) { return _Is_valid_type<_CHRONO month>(_Type) || _Is_valid_type<_CHRONO day>(_Type); + } else if constexpr (_Is_any_of_v<_Ty, _CHRONO month_weekday, _CHRONO month_weekday_last>) { + return _Is_valid_type<_CHRONO month>(_Type) || _Is_valid_type<_CHRONO weekday>(_Type); } else if constexpr (is_same_v<_Ty, _CHRONO year_month>) { return _Is_valid_type<_CHRONO year>(_Type) || _Is_valid_type<_CHRONO month>(_Type); } else if constexpr (_Is_any_of_v<_Ty, _CHRONO year_month_day, _CHRONO year_month_day_last, @@ -5808,8 +5811,7 @@ struct _Chrono_formatter { return _Type == 'c' || _Type == 'x' || _Type == 'X' || _Is_valid_type<_CHRONO year_month_day>(_Type) || _Is_valid_type<_CHRONO hh_mm_ss<_CHRONO seconds>>(_Type); } else { - // TRANSITION, remove when all types are added - static_assert(_Always_false<_Ty>, "unsupported type"); + static_assert(_Always_false<_Ty>, "should be unreachable"); } } @@ -6014,6 +6016,10 @@ template struct formatter<_CHRONO weekday_indexed, _CharT> // : _Fill_tm_formatter<_CHRONO weekday_indexed, _CharT> {}; +template +struct formatter<_CHRONO weekday_last, _CharT> // + : _Fill_tm_formatter<_CHRONO weekday_last, _CharT> {}; + template struct formatter<_CHRONO month_day, _CharT> // : _Fill_tm_formatter<_CHRONO month_day, _CharT> {}; @@ -6022,6 +6028,14 @@ template struct formatter<_CHRONO month_day_last, _CharT> // : _Fill_tm_formatter<_CHRONO month_day_last, _CharT> {}; +template +struct formatter<_CHRONO month_weekday, _CharT> // + : _Fill_tm_formatter<_CHRONO month_weekday, _CharT> {}; + +template +struct formatter<_CHRONO month_weekday_last, _CharT> // + : _Fill_tm_formatter<_CHRONO month_weekday_last, _CharT> {}; + template struct formatter<_CHRONO year_month, _CharT> // : _Fill_tm_formatter<_CHRONO year_month, _CharT> {}; diff --git a/tests/libcxx/expected_results.txt b/tests/libcxx/expected_results.txt index 9bd7270d66..67bc234887 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -275,32 +275,6 @@ std/utilities/memory/default.allocator/allocator.members/allocate.verify.cpp SKI # *** MISSING STL FEATURES *** -# C++20 P0355R7 " Calendars And Time Zones" -std/utilities/time/time.cal/time.cal.mwdlast/time.cal.mwdlast.nonmembers/streaming.pass.cpp FAIL -std/utilities/time/time.cal/time.cal.wdidx/time.cal.wdidx.nonmembers/streaming.pass.cpp FAIL -std/utilities/time/time.cal/time.cal.ymwd/time.cal.ymwd.nonmembers/streaming.pass.cpp FAIL -std/utilities/time/time.cal/time.cal.ymwdlast/time.cal.ymwdlast.nonmembers/streaming.pass.cpp FAIL - -# C++20 P0466R5 "Layout-Compatibility And Pointer-Interconvertibility Traits" -std/language.support/support.limits/support.limits.general/type_traits.version.pass.cpp:1 FAIL - -# C++20 P0608R3 "Improving variant's Converting Constructor/Assignment" -std/utilities/variant/variant.variant/variant.assign/conv.pass.cpp FAIL -std/utilities/variant/variant.variant/variant.assign/T.pass.cpp FAIL -std/utilities/variant/variant.variant/variant.ctor/conv.pass.cpp FAIL -std/utilities/variant/variant.variant/variant.ctor/T.pass.cpp FAIL - -# C++20 P0784R7 "More constexpr containers" -std/utilities/memory/allocator.traits/allocator.traits.members/construct.pass.cpp FAIL -std/utilities/memory/allocator.traits/allocator.traits.members/destroy.pass.cpp FAIL -std/utilities/memory/specialized.algorithms/specialized.construct/construct_at.pass.cpp FAIL - -# C++20 P0896R4 "" -std/language.support/support.limits/support.limits.general/algorithm.version.pass.cpp FAIL -std/language.support/support.limits/support.limits.general/functional.version.pass.cpp FAIL -std/language.support/support.limits/support.limits.general/iterator.version.pass.cpp FAIL -std/language.support/support.limits/support.limits.general/memory.version.pass.cpp FAIL - # C++23 P1048R1 "is_scoped_enum" std/utilities/meta/meta.unary/meta.unary.prop/is_scoped_enum.pass.cpp FAIL @@ -651,6 +625,25 @@ std/utilities/allocator.adaptor/allocator.adaptor.members/construct_pair_rvalue. std/utilities/allocator.adaptor/allocator.adaptor.members/construct_pair_values.pass.cpp FAIL std/utilities/allocator.adaptor/allocator.adaptor.members/construct_type.pass.cpp FAIL +# Bogus test uses std::cout without including . +std/utilities/time/time.cal/time.cal.wdidx/time.cal.wdidx.nonmembers/streaming.pass.cpp FAIL + +# Bogus test constructs year_month_weekday from weekday, but the constructor actually takes weekday_indexed. +std/utilities/time/time.cal/time.cal.ymwd/time.cal.ymwd.nonmembers/streaming.pass.cpp FAIL + +# We define __cpp_lib_has_unique_object_representations in C++17 mode; test error says it +# "should not be defined when TEST_HAS_BUILTIN_IDENTIFIER(__has_unique_object_representations) || TEST_GCC_VER >= 700 is not defined!" +std/language.support/support.limits/support.limits.general/type_traits.version.pass.cpp:1 FAIL + +# Tests expect __cpp_lib_ranges to have the old value 201811L for P0896R4; we define the C++20 value 201911L for P1716R3. +std/language.support/support.limits/support.limits.general/algorithm.version.pass.cpp FAIL +std/language.support/support.limits/support.limits.general/functional.version.pass.cpp FAIL +std/language.support/support.limits/support.limits.general/iterator.version.pass.cpp FAIL + +# We unconditionally define __cpp_lib_addressof_constexpr; test error says it +# "should not be defined when TEST_HAS_BUILTIN(__builtin_addressof) || TEST_GCC_VER >= 700 is not defined!" +std/language.support/support.limits/support.limits.general/memory.version.pass.cpp FAIL + # *** LIKELY STL BUGS *** # Not yet analyzed, likely STL bugs. Assertions and other runtime failures. @@ -857,6 +850,19 @@ std/re/re.alg/re.alg.search/basic.locale.pass.cpp FAIL std/re/re.alg/re.alg.search/ecma.locale.pass.cpp FAIL std/re/re.alg/re.alg.search/extended.locale.pass.cpp FAIL +# Not yet analyzed. Various static_asserts. +std/utilities/variant/variant.variant/variant.assign/conv.pass.cpp FAIL +std/utilities/variant/variant.variant/variant.assign/T.pass.cpp FAIL +std/utilities/variant/variant.variant/variant.ctor/conv.pass.cpp FAIL +std/utilities/variant/variant.variant/variant.ctor/T.pass.cpp FAIL + +# Not yet analyzed. Involves incomplete types. +std/utilities/memory/allocator.traits/allocator.traits.members/construct.pass.cpp FAIL +std/utilities/memory/allocator.traits/allocator.traits.members/destroy.pass.cpp FAIL + +# Not yet analyzed. Error mentions allocator. +std/utilities/memory/specialized.algorithms/specialized.construct/construct_at.pass.cpp FAIL + # *** XFAILs WHICH PASS *** # Not yet implemented in libcxx and marked as "XFAIL: libc++" @@ -876,9 +882,11 @@ std/utilities/time/time.cal/time.cal.md/time.cal.md.nonmembers/streaming.pass.cp std/utilities/time/time.cal/time.cal.mdlast/streaming.pass.cpp SKIPPED std/utilities/time/time.cal/time.cal.month/time.cal.month.nonmembers/streaming.pass.cpp SKIPPED std/utilities/time/time.cal/time.cal.mwd/time.cal.mwd.nonmembers/streaming.pass.cpp SKIPPED +std/utilities/time/time.cal/time.cal.mwdlast/time.cal.mwdlast.nonmembers/streaming.pass.cpp SKIPPED std/utilities/time/time.cal/time.cal.wdlast/time.cal.wdlast.nonmembers/streaming.pass.cpp SKIPPED std/utilities/time/time.cal/time.cal.weekday/time.cal.weekday.nonmembers/streaming.pass.cpp SKIPPED std/utilities/time/time.cal/time.cal.year/time.cal.year.nonmembers/streaming.pass.cpp SKIPPED std/utilities/time/time.cal/time.cal.ym/time.cal.ym.nonmembers/streaming.pass.cpp SKIPPED std/utilities/time/time.cal/time.cal.ymd/time.cal.ymd.nonmembers/streaming.pass.cpp SKIPPED std/utilities/time/time.cal/time.cal.ymdlast/time.cal.ymdlast.nonmembers/streaming.pass.cpp SKIPPED +std/utilities/time/time.cal/time.cal.ymwdlast/time.cal.ymwdlast.nonmembers/streaming.pass.cpp SKIPPED diff --git a/tests/libcxx/skipped_tests.txt b/tests/libcxx/skipped_tests.txt index ae453bbaed..fedbf725d2 100644 --- a/tests/libcxx/skipped_tests.txt +++ b/tests/libcxx/skipped_tests.txt @@ -275,32 +275,6 @@ utilities\memory\default.allocator\allocator.members\allocate.verify.cpp # *** MISSING STL FEATURES *** -# C++20 P0355R7 " Calendars And Time Zones" -utilities\time\time.cal\time.cal.mwdlast\time.cal.mwdlast.nonmembers\streaming.pass.cpp -utilities\time\time.cal\time.cal.wdidx\time.cal.wdidx.nonmembers\streaming.pass.cpp -utilities\time\time.cal\time.cal.ymwd\time.cal.ymwd.nonmembers\streaming.pass.cpp -utilities\time\time.cal\time.cal.ymwdlast\time.cal.ymwdlast.nonmembers\streaming.pass.cpp - -# C++20 P0466R5 "Layout-Compatibility And Pointer-Interconvertibility Traits" -language.support\support.limits\support.limits.general\type_traits.version.pass.cpp - -# C++20 P0608R3 "Improving variant's Converting Constructor/Assignment" -utilities\variant\variant.variant\variant.assign\conv.pass.cpp -utilities\variant\variant.variant\variant.assign\T.pass.cpp -utilities\variant\variant.variant\variant.ctor\conv.pass.cpp -utilities\variant\variant.variant\variant.ctor\T.pass.cpp - -# C++20 P0784R7 "More constexpr containers" -utilities\memory\allocator.traits\allocator.traits.members\construct.pass.cpp -utilities\memory\allocator.traits\allocator.traits.members\destroy.pass.cpp -utilities\memory\specialized.algorithms\specialized.construct\construct_at.pass.cpp - -# C++20 P0896R4 "" -language.support\support.limits\support.limits.general\algorithm.version.pass.cpp -language.support\support.limits\support.limits.general\functional.version.pass.cpp -language.support\support.limits\support.limits.general\iterator.version.pass.cpp -language.support\support.limits\support.limits.general\memory.version.pass.cpp - # C++23 P1048R1 "is_scoped_enum" utilities\meta\meta.unary\meta.unary.prop\is_scoped_enum.pass.cpp @@ -651,6 +625,25 @@ utilities\allocator.adaptor\allocator.adaptor.members\construct_pair_rvalue.pass utilities\allocator.adaptor\allocator.adaptor.members\construct_pair_values.pass.cpp utilities\allocator.adaptor\allocator.adaptor.members\construct_type.pass.cpp +# Bogus test uses std::cout without including . +utilities\time\time.cal\time.cal.wdidx\time.cal.wdidx.nonmembers\streaming.pass.cpp + +# Bogus test constructs year_month_weekday from weekday, but the constructor actually takes weekday_indexed. +utilities\time\time.cal\time.cal.ymwd\time.cal.ymwd.nonmembers\streaming.pass.cpp + +# We define __cpp_lib_has_unique_object_representations in C++17 mode; test error says it +# "should not be defined when TEST_HAS_BUILTIN_IDENTIFIER(__has_unique_object_representations) || TEST_GCC_VER >= 700 is not defined!" +language.support\support.limits\support.limits.general\type_traits.version.pass.cpp + +# Tests expect __cpp_lib_ranges to have the old value 201811L for P0896R4; we define the C++20 value 201911L for P1716R3. +language.support\support.limits\support.limits.general\algorithm.version.pass.cpp +language.support\support.limits\support.limits.general\functional.version.pass.cpp +language.support\support.limits\support.limits.general\iterator.version.pass.cpp + +# We unconditionally define __cpp_lib_addressof_constexpr; test error says it +# "should not be defined when TEST_HAS_BUILTIN(__builtin_addressof) || TEST_GCC_VER >= 700 is not defined!" +language.support\support.limits\support.limits.general\memory.version.pass.cpp + # *** LIKELY STL BUGS *** # Not yet analyzed, likely STL bugs. Assertions and other runtime failures. @@ -857,6 +850,19 @@ re\re.alg\re.alg.search\basic.locale.pass.cpp re\re.alg\re.alg.search\ecma.locale.pass.cpp re\re.alg\re.alg.search\extended.locale.pass.cpp +# Not yet analyzed. Various static_asserts. +utilities\variant\variant.variant\variant.assign\conv.pass.cpp +utilities\variant\variant.variant\variant.assign\T.pass.cpp +utilities\variant\variant.variant\variant.ctor\conv.pass.cpp +utilities\variant\variant.variant\variant.ctor\T.pass.cpp + +# Not yet analyzed. Involves incomplete types. +utilities\memory\allocator.traits\allocator.traits.members\construct.pass.cpp +utilities\memory\allocator.traits\allocator.traits.members\destroy.pass.cpp + +# Not yet analyzed. Error mentions allocator. +utilities\memory\specialized.algorithms\specialized.construct\construct_at.pass.cpp + # *** SKIPPED FOR MSVC-INTERNAL CONTEST ONLY *** # Our machinery doesn't understand compile-only `.compile.pass.cpp` tests. diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index c401013896..30a4cc253e 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -191,8 +191,8 @@ bool test_parse_chrono_format_specs() { return true; } -template -void throw_helper(const basic_string_view fmt, const Args&... vals) { +template +void throw_helper(const basic_string_view fmt, const Args&... vals) { try { (void) format(fmt, vals...); assert(false); @@ -200,19 +200,29 @@ void throw_helper(const basic_string_view fmt, const Args&... vals) { } } -template -void throw_helper(const charT* fmt, const Args&... vals) { - throw_helper(basic_string_view{fmt}, vals...); +template +void throw_helper(const CharT* fmt, const Args&... vals) { + throw_helper(basic_string_view{fmt}, vals...); } -template -void stream_helper(const charT* expect, const Args&... vals) { - basic_ostringstream stream; +template +void stream_helper(const CharT* expect, const Args&... vals) { + basic_ostringstream stream; (stream << ... << vals); assert(stream.str() == expect); assert(stream); } +template +void empty_braces_helper(const Arg& val, const CharT* const expected) { + // N4885 [time.format]/6: "If the chrono-specs is omitted, the chrono object is formatted + // as if by streaming it to std::ostringstream os and copying os.str() through the output iterator + // of the context with additional padding and adjustments as specified by the format specifiers." + assert(format(STR("{}"), val) == expected); + + stream_helper(expected, val); +} + // FIXME: TEMPORARY CODE FOR WRITING TESTS, REMOVE BEFORE MERGING template constexpr void print(Str str) { @@ -380,6 +390,59 @@ void test_weekday_indexed_formatter() { assert(format(STR("{:%u %w}"), weekday_indexed{Sunday, 4}) == STR("7 0")); } +template +void test_weekday_last_formatter() { + constexpr weekday_last invalid{weekday{10}}; + + empty_braces_helper(Wednesday[last], STR("Wed[last]")); + empty_braces_helper(invalid, STR("10 is not a valid weekday[last]")); + + assert(format(STR("{:%a %A %u %w}"), Saturday[last]) == STR("Sat Saturday 6 6")); + assert(format(STR("{:%a %A %u %w}"), Sunday[last]) == STR("Sun Sunday 7 0")); +} + +template +void test_month_weekday_formatter() { + constexpr month_weekday mwd1 = August / Tuesday[3]; + constexpr month_weekday mwd2 = December / Sunday[4]; + + constexpr month_weekday invalid1 = March / Friday[9]; + constexpr month_weekday invalid2 = March / weekday{8}[2]; + constexpr month_weekday invalid3 = month{20} / Friday[2]; + constexpr month_weekday invalid4 = month{20} / weekday{8}[9]; + + empty_braces_helper(mwd1, STR("Aug/Tue[3]")); + empty_braces_helper(mwd2, STR("Dec/Sun[4]")); + + empty_braces_helper(invalid1, STR("Mar/Fri[9 is not a valid index]")); + empty_braces_helper(invalid2, STR("Mar/8 is not a valid weekday[2]")); + empty_braces_helper(invalid3, STR("20 is not a valid month/Fri[2]")); + empty_braces_helper(invalid4, STR("20 is not a valid month/8 is not a valid weekday[9 is not a valid index]")); + + assert(format(STR("{:%b %B %h %m %a %A %u %w}"), mwd1) == STR("Aug August Aug 08 Tue Tuesday 2 2")); + assert(format(STR("{:%b %B %h %m %a %A %u %w}"), mwd2) == STR("Dec December Dec 12 Sun Sunday 7 0")); +} + +template +void test_month_weekday_last_formatter() { + constexpr month_weekday_last mwdl1 = August / Tuesday[last]; + constexpr month_weekday_last mwdl2 = December / Sunday[last]; + + constexpr month_weekday_last invalid1 = March / weekday{8}[last]; + constexpr month_weekday_last invalid2 = month{20} / Friday[last]; + constexpr month_weekday_last invalid3 = month{20} / weekday{8}[last]; + + empty_braces_helper(mwdl1, STR("Aug/Tue[last]")); + empty_braces_helper(mwdl2, STR("Dec/Sun[last]")); + + empty_braces_helper(invalid1, STR("Mar/8 is not a valid weekday[last]")); + empty_braces_helper(invalid2, STR("20 is not a valid month/Fri[last]")); + empty_braces_helper(invalid3, STR("20 is not a valid month/8 is not a valid weekday[last]")); + + assert(format(STR("{:%b %B %h %m %a %A %u %w}"), mwdl1) == STR("Aug August Aug 08 Tue Tuesday 2 2")); + assert(format(STR("{:%b %B %h %m %a %A %u %w}"), mwdl2) == STR("Dec December Dec 12 Sun Sunday 7 0")); +} + template void test_year_month_day_formatter() { year_month_day invalid{year{1234}, month{0}, day{31}}; @@ -395,18 +458,63 @@ void test_year_month_day_formatter() { template void test_year_month_day_last_formatter() { - // FIXME, TEST format() AND operator<< WHEN formatter FOR month_day_last IS IMPLEMENTED - // assert(format(STR("{:%F}"), 2021y / April / last) == STR("2021-04-30")); + constexpr year_month_day_last ymdl1 = 2021y / April / last; + constexpr year_month_day_last ymdl2 = 2004y / February / last; + + constexpr year_month_day_last invalid = 1999y / month{20} / last; + + empty_braces_helper(ymdl1, STR("2021/Apr/last")); + empty_braces_helper(ymdl2, STR("2004/Feb/last")); + + empty_braces_helper(invalid, STR("1999/20 is not a valid month/last")); + + constexpr auto fmt = STR("{:%D %F, %Y %C %y, %b %B %h %m, %d %e, %a %A %u %w}"); + assert(format(fmt, ymdl1) == STR("04/30/21 2021-04-30, 2021 20 21, Apr April Apr 04, 30 30, Fri Friday 5 5")); + assert(format(fmt, ymdl2) == STR("02/29/04 2004-02-29, 2004 20 04, Feb February Feb 02, 29 29, Sun Sunday 7 0")); } template void test_year_month_weekday_formatter() { - // FIXME, TEST format() AND operator<< WHEN formatter FOR weekday_indexed IS IMPLEMENTED + constexpr year_month_weekday ymwd1 = 2021y / April / Friday[5]; + constexpr year_month_weekday ymwd2 = 2004y / February / Sunday[5]; + + constexpr year_month_weekday invalid1 = 2015y / March / Friday[9]; + constexpr year_month_weekday invalid2 = 2015y / March / weekday{8}[2]; + constexpr year_month_weekday invalid3 = 2015y / month{20} / Friday[2]; + constexpr year_month_weekday invalid4 = 2015y / month{20} / weekday{8}[9]; + + empty_braces_helper(ymwd1, STR("2021/Apr/Fri[5]")); + empty_braces_helper(ymwd2, STR("2004/Feb/Sun[5]")); + + empty_braces_helper(invalid1, STR("2015/Mar/Fri[9 is not a valid index]")); + empty_braces_helper(invalid2, STR("2015/Mar/8 is not a valid weekday[2]")); + empty_braces_helper(invalid3, STR("2015/20 is not a valid month/Fri[2]")); + empty_braces_helper(invalid4, STR("2015/20 is not a valid month/8 is not a valid weekday[9 is not a valid index]")); + + constexpr auto fmt = STR("{:%D %F, %Y %C %y, %b %B %h %m, %d %e, %a %A %u %w}"); + assert(format(fmt, ymwd1) == STR("04/30/21 2021-04-30, 2021 20 21, Apr April Apr 04, 30 30, Fri Friday 5 5")); + assert(format(fmt, ymwd2) == STR("02/29/04 2004-02-29, 2004 20 04, Feb February Feb 02, 29 29, Sun Sunday 7 0")); } template void test_year_month_weekday_last_formatter() { - // FIXME, TEST format() AND operator<< WHEN formatter FOR weekday_last IS IMPLEMENTED + constexpr year_month_weekday_last ymwdl1 = 2021y / April / Friday[last]; + constexpr year_month_weekday_last ymwdl2 = 2004y / February / Sunday[last]; + + constexpr year_month_weekday_last invalid1 = 2015y / March / weekday{8}[last]; + constexpr year_month_weekday_last invalid2 = 2015y / month{20} / Friday[last]; + constexpr year_month_weekday_last invalid3 = 2015y / month{20} / weekday{8}[last]; + + empty_braces_helper(ymwdl1, STR("2021/Apr/Fri[last]")); + empty_braces_helper(ymwdl2, STR("2004/Feb/Sun[last]")); + + empty_braces_helper(invalid1, STR("2015/Mar/8 is not a valid weekday[last]")); + empty_braces_helper(invalid2, STR("2015/20 is not a valid month/Fri[last]")); + empty_braces_helper(invalid3, STR("2015/20 is not a valid month/8 is not a valid weekday[last]")); + + constexpr auto fmt = STR("{:%D %F, %Y %C %y, %b %B %h %m, %d %e, %a %A %u %w}"); + assert(format(fmt, ymwdl1) == STR("04/30/21 2021-04-30, 2021 20 21, Apr April Apr 04, 30 30, Fri Friday 5 5")); + assert(format(fmt, ymwdl2) == STR("02/29/04 2004-02-29, 2004 20 04, Feb February Feb 02, 29 29, Sun Sunday 7 0")); } template @@ -529,6 +637,15 @@ int main() { test_weekday_indexed_formatter(); test_weekday_indexed_formatter(); + test_weekday_last_formatter(); + test_weekday_last_formatter(); + + test_month_weekday_formatter(); + test_month_weekday_formatter(); + + test_month_weekday_last_formatter(); + test_month_weekday_last_formatter(); + test_year_month_day_formatter(); test_year_month_day_formatter(); From 1f874ebea090c8fdbc0109d48c58d06d0724181f Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Sun, 18 Apr 2021 05:11:21 -0700 Subject: [PATCH 27/41] `` formatting: More cleanups (#1857) * Rearrange tests to follow Standard order. No other changes. * Move _Chrono_formatter into namespace chrono. No changes other than (greatly reduced!) qualification and formatting. _Fill_tm_formatter is still directly within std, as it's the base class for std::formatter. * Rename _Chrono_specs to _Chrono_spec. Drop "with literal chars" from a comment; this reflected our earlier, incorrect understanding. This also renames _Custom_write()'s parameter from _Specs to _Spec, avoiding shadowing a data member. --- stl/inc/chrono | 529 +++++++++--------- .../test.cpp | 124 ++-- 2 files changed, 327 insertions(+), 326 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index cc33b40ac0..a178fe500a 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5254,7 +5254,7 @@ concept _Has_ok = requires(_Ty _At) { // A chrono spec is either a type (with an optional modifier), OR a literal character, never both. template -struct _Chrono_specs { +struct _Chrono_spec { _CharT _Lit_char = _CharT{'\0'}; // any character other than '{', '}', or '%' char _Modifier = '\0'; // either 'E' or 'O' char _Type = '\0'; @@ -5270,8 +5270,8 @@ struct _Chrono_format_specs { uint8_t _Fill_length = 1; // At most one codepoint (so one char32_t or four utf-8 char8_t) _CharT _Fill[4 / sizeof(_CharT)] = {_CharT{' '}}; - // recursive definition in grammar, so could have any number of these with literal chars - vector<_Chrono_specs<_CharT>> _Chrono_specs_list; + // recursive definition in grammar, so could have any number of these + vector<_Chrono_spec<_CharT>> _Chrono_specs_list; }; // Model of _Chrono_parse_spec_callbacks that fills a _Chrono_format_specs with the parsed data @@ -5333,12 +5333,12 @@ public: _THROW(format_error("Invalid type specification.")); } - _Chrono_specs<_CharT> _Conv_spec{._Modifier = _Modifier, ._Type = static_cast(_Type)}; + _Chrono_spec<_CharT> _Conv_spec{._Modifier = _Modifier, ._Type = static_cast(_Type)}; _Specs._Chrono_specs_list.push_back(_Conv_spec); } constexpr void _On_lit_char(_CharT _Lit_ch) { - _Chrono_specs<_CharT> _Lit_char_spec{._Lit_char = _Lit_ch}; + _Chrono_spec<_CharT> _Lit_char_spec{._Lit_char = _Lit_ch}; _Specs._Chrono_specs_list.push_back(_Lit_char_spec); } @@ -5681,305 +5681,306 @@ namespace chrono { basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const local_time<_Duration>& _Val) { return _Os << sys_time<_Duration>{_Val.time_since_epoch()}; } -} // namespace chrono - -template -struct _Chrono_formatter { - _Chrono_formatter() = default; - explicit _Chrono_formatter(const basic_string_view<_CharT> _Time_zone_abbreviation_) - : _Time_zone_abbreviation{_Time_zone_abbreviation_} {} + template + struct _Chrono_formatter { + _Chrono_formatter() = default; - template - _NODISCARD auto _Parse(basic_format_parse_context<_CharT>& _Parse_ctx) { - _Chrono_specs_setter<_CharT, basic_format_parse_context<_CharT>> _Callback{_Specs, _Parse_ctx}; - const auto _It = - _Parse_chrono_format_specs(_Parse_ctx._Unchecked_begin(), _Parse_ctx._Unchecked_end(), _Callback); - const auto _Res_iter = _Parse_ctx.begin() + (_It - _Parse_ctx._Unchecked_begin()); + explicit _Chrono_formatter(const basic_string_view<_CharT> _Time_zone_abbreviation_) + : _Time_zone_abbreviation{_Time_zone_abbreviation_} {} - if (_It != _Parse_ctx._Unchecked_end() && *_It != '}') { - _THROW(format_error("Missing '}' in format string.")); - } + template + _NODISCARD auto _Parse(basic_format_parse_context<_CharT>& _Parse_ctx) { + _Chrono_specs_setter<_CharT, basic_format_parse_context<_CharT>> _Callback{_Specs, _Parse_ctx}; + const auto _It = + _Parse_chrono_format_specs(_Parse_ctx._Unchecked_begin(), _Parse_ctx._Unchecked_end(), _Callback); + const auto _Res_iter = _Parse_ctx.begin() + (_It - _Parse_ctx._Unchecked_begin()); + + if (_It != _Parse_ctx._Unchecked_end() && *_It != '}') { + _THROW(format_error("Missing '}' in format string.")); + } - if constexpr (_Is_specialization_v<_Ty, _CHRONO duration>) { - if constexpr (!_CHRONO treat_as_floating_point_v) { + if constexpr (_Is_specialization_v<_Ty, duration>) { + if constexpr (!treat_as_floating_point_v) { + if (_Specs._Precision != -1) { + _THROW(format_error("Precision specification invalid for chrono::duration type with " + "integral representation type, see N4885 [time.format]/1.")); + } + } + } else { if (_Specs._Precision != -1) { - _THROW(format_error("Precision specification invalid for chrono::duration type with " - "integral representation type, see N4885 [time.format]/1.")); + _THROW(format_error("Precision specification invalid for non-chrono::duration type, " + "see N4885 [time.format]/1.")); } } - } else { - if (_Specs._Precision != -1) { - _THROW(format_error("Precision specification invalid for non-chrono::duration type, " - "see N4885 [time.format]/1.")); + + const auto& _List = _Specs._Chrono_specs_list; + + // [time.format]/6 + if (_List.empty()) { + _No_chrono_specs = true; + return _Res_iter; } - } - const auto& _List = _Specs._Chrono_specs_list; + for (const auto& _Spec : _List) { + if (_Spec._Type != '\0' && !_Is_valid_type<_Ty>(_Spec._Type)) { + _THROW(format_error("Invalid type.")); + } + _Check_modifier(_Spec._Type, _Spec._Modifier); + } - // [time.format]/6 - if (_List.empty()) { - _No_chrono_specs = true; return _Res_iter; } - for (const auto& _Spec : _List) { - if (_Spec._Type != '\0' && !_Is_valid_type<_Ty>(_Spec._Type)) { - _THROW(format_error("Invalid type.")); + void _Check_modifier(const char _Type, const char _Modifier) { + if (_Modifier == '\0') { + return; } - _Check_modifier(_Spec._Type, _Spec._Modifier); - } - return _Res_iter; - } + enum _Allowed_bit : uint8_t { _E_mod = 1, _O_mod = 2, _EO_mod = _E_mod | _O_mod }; + + struct _Table_entry { + char _Type; + _Allowed_bit _Allowed; + }; + + static constexpr _Table_entry _Table[] = { + {'c', _E_mod}, + {'C', _E_mod}, + {'d', _O_mod}, + {'e', _O_mod}, + {'H', _O_mod}, + {'I', _O_mod}, + {'m', _O_mod}, + {'M', _O_mod}, + {'S', _O_mod}, + {'u', _O_mod}, + {'U', _O_mod}, + {'V', _O_mod}, + {'w', _O_mod}, + {'W', _O_mod}, + {'x', _E_mod}, + {'X', _E_mod}, + {'y', _EO_mod}, + {'Y', _E_mod}, + {'z', _EO_mod}, + }; + + const _Allowed_bit _Mod = _Modifier == 'E' ? _E_mod : _O_mod; + + if (auto _It = _RANGES find(_Table, _Type, &_Table_entry::_Type); _It != _STD end(_Table)) { + if (_It->_Allowed & _Mod) { + return; + } + } - void _Check_modifier(const char _Type, const char _Modifier) { - if (_Modifier == '\0') { - return; + _THROW(format_error("Incompatible modifier for type")); } - enum _Allowed_bit : uint8_t { _E_mod = 1, _O_mod = 2, _EO_mod = _E_mod | _O_mod }; + template + _NODISCARD constexpr bool _Is_valid_type(const char _Type) noexcept { + if constexpr (is_same_v<_Ty, day>) { + return _Type == 'd' || _Type == 'e'; + } else if constexpr (is_same_v<_Ty, month>) { + return _Type == 'b' || _Type == 'B' || _Type == 'h' || _Type == 'm'; + } else if constexpr (is_same_v<_Ty, year>) { + return _Type == 'Y' || _Type == 'y' || _Type == 'C'; + } else if constexpr (_Is_any_of_v<_Ty, weekday, weekday_indexed, weekday_last>) { + return _Type == 'a' || _Type == 'A' || _Type == 'u' || _Type == 'w'; + } else if constexpr (_Is_any_of_v<_Ty, month_day, month_day_last>) { + return _Is_valid_type(_Type) || _Is_valid_type(_Type); + } else if constexpr (_Is_any_of_v<_Ty, month_weekday, month_weekday_last>) { + return _Is_valid_type(_Type) || _Is_valid_type(_Type); + } else if constexpr (is_same_v<_Ty, year_month>) { + return _Is_valid_type(_Type) || _Is_valid_type(_Type); + } else if constexpr (_Is_any_of_v<_Ty, year_month_day, year_month_day_last, year_month_weekday, + year_month_weekday_last>) { + return _Type == 'D' || _Type == 'F' || _Is_valid_type(_Type) || _Is_valid_type(_Type) + || _Is_valid_type(_Type) || _Is_valid_type(_Type); + } else if constexpr (_Is_specialization_v<_Ty, hh_mm_ss>) { + return _Type == 'H' || _Type == 'I' || _Type == 'M' || _Type == 'S' || _Type == 'r' || _Type == 'R' + || _Type == 'T' || _Type == 'p'; + } else if constexpr (_Is_specialization_v<_Ty, time_point>) { + if constexpr (!is_same_v) { + if (_Type == 'z' || _Type == 'Z') { + return true; + } + } + return _Type == 'c' || _Type == 'x' || _Type == 'X' || _Is_valid_type(_Type) + || _Is_valid_type>(_Type); + } else { + static_assert(_Always_false<_Ty>, "should be unreachable"); + } + } - struct _Table_entry { - char _Type; - _Allowed_bit _Allowed; - }; + template + _NODISCARD auto _Write(_FormatContext& _FormatCtx, const _Ty& _Val, const tm& _Time) { + basic_ostringstream<_CharT> _Stream; - static constexpr _Table_entry _Table[] = { - {'c', _E_mod}, - {'C', _E_mod}, - {'d', _O_mod}, - {'e', _O_mod}, - {'H', _O_mod}, - {'I', _O_mod}, - {'m', _O_mod}, - {'M', _O_mod}, - {'S', _O_mod}, - {'u', _O_mod}, - {'U', _O_mod}, - {'V', _O_mod}, - {'w', _O_mod}, - {'W', _O_mod}, - {'x', _E_mod}, - {'X', _E_mod}, - {'y', _EO_mod}, - {'Y', _E_mod}, - {'z', _EO_mod}, - }; + if (_No_chrono_specs) { + _Stream << _Val; + } else { + _Stream.imbue(_FormatCtx.locale()); + if constexpr (_Is_specialization_v<_Ty, hh_mm_ss>) { + if (_Val.is_negative()) { + _Stream << _CharT{'-'}; + } + } - const _Allowed_bit _Mod = _Modifier == 'E' ? _E_mod : _O_mod; + for (const auto& _Spec : _Specs._Chrono_specs_list) { + if (_Spec._Lit_char != _CharT{'\0'}) { + _Stream << _Spec._Lit_char; + continue; + } - if (auto _It = _RANGES find(_Table, _Type, &_Table_entry::_Type); _It != _STD end(_Table)) { - if (_It->_Allowed & _Mod) { - return; - } - } + // We need to manually do certain writes, either because the specification is different from + // put_time or custom logic is needed. + if (_Custom_write(_Stream, _Spec, _Time, _Val)) { + continue; + } + // Otherwise, we should throw on out-of-bounds to avoid triggering asserts within put_time + // machinery. + if constexpr (_Has_ok<_Ty>) { + if (!_Val.ok()) { + _THROW(format_error("Cannot localize out-of-bounds time point.")); + } + } - _THROW(format_error("Incompatible modifier for type")); - } + _CharT _Fmt_str[4]; + size_t _Next_idx = 0; + _Fmt_str[_Next_idx++] = _CharT{'%'}; + if (_Spec._Modifier != '\0') { + _Fmt_str[_Next_idx++] = static_cast<_CharT>(_Spec._Modifier); + } + _Fmt_str[_Next_idx++] = static_cast<_CharT>(_Spec._Type); + _Fmt_str[_Next_idx] = _CharT{'\0'}; - template - _NODISCARD constexpr bool _Is_valid_type(const char _Type) noexcept { - if constexpr (is_same_v<_Ty, _CHRONO day>) { - return _Type == 'd' || _Type == 'e'; - } else if constexpr (is_same_v<_Ty, _CHRONO month>) { - return _Type == 'b' || _Type == 'B' || _Type == 'h' || _Type == 'm'; - } else if constexpr (is_same_v<_Ty, _CHRONO year>) { - return _Type == 'Y' || _Type == 'y' || _Type == 'C'; - } else if constexpr (_Is_any_of_v<_Ty, _CHRONO weekday, _CHRONO weekday_indexed, _CHRONO weekday_last>) { - return _Type == 'a' || _Type == 'A' || _Type == 'u' || _Type == 'w'; - } else if constexpr (_Is_any_of_v<_Ty, _CHRONO month_day, _CHRONO month_day_last>) { - return _Is_valid_type<_CHRONO month>(_Type) || _Is_valid_type<_CHRONO day>(_Type); - } else if constexpr (_Is_any_of_v<_Ty, _CHRONO month_weekday, _CHRONO month_weekday_last>) { - return _Is_valid_type<_CHRONO month>(_Type) || _Is_valid_type<_CHRONO weekday>(_Type); - } else if constexpr (is_same_v<_Ty, _CHRONO year_month>) { - return _Is_valid_type<_CHRONO year>(_Type) || _Is_valid_type<_CHRONO month>(_Type); - } else if constexpr (_Is_any_of_v<_Ty, _CHRONO year_month_day, _CHRONO year_month_day_last, - _CHRONO year_month_weekday, _CHRONO year_month_weekday_last>) { - return _Type == 'D' || _Type == 'F' || _Is_valid_type<_CHRONO year>(_Type) - || _Is_valid_type<_CHRONO month>(_Type) || _Is_valid_type<_CHRONO day>(_Type) - || _Is_valid_type<_CHRONO weekday>(_Type); - } else if constexpr (_Is_specialization_v<_Ty, _CHRONO hh_mm_ss>) { - return _Type == 'H' || _Type == 'I' || _Type == 'M' || _Type == 'S' || _Type == 'r' || _Type == 'R' - || _Type == 'T' || _Type == 'p'; - } else if constexpr (_Is_specialization_v<_Ty, _CHRONO time_point>) { - if constexpr (!is_same_v) { - if (_Type == 'z' || _Type == 'Z') { - return true; + _Stream << _STD put_time<_CharT>(&_Time, _Fmt_str); } } - return _Type == 'c' || _Type == 'x' || _Type == 'X' || _Is_valid_type<_CHRONO year_month_day>(_Type) - || _Is_valid_type<_CHRONO hh_mm_ss<_CHRONO seconds>>(_Type); - } else { - static_assert(_Always_false<_Ty>, "should be unreachable"); - } - } - template - _NODISCARD auto _Write(_FormatContext& _FormatCtx, const _Ty& _Val, const tm& _Time) { - basic_ostringstream<_CharT> _Stream; + return _Write_aligned(_STD move(_FormatCtx.out()), static_cast(_Stream.view().size()), _Specs, + _Fmt_align::_Left, [&](auto _Out) { return _Fmt_write(_STD move(_Out), _Stream.view()); }); + } - if (_No_chrono_specs) { - _Stream << _Val; - } else { - _Stream.imbue(_FormatCtx.locale()); - if constexpr (_Is_specialization_v<_Ty, _CHRONO hh_mm_ss>) { - if (_Val.is_negative()) { - _Stream << _CharT{'-'}; + // This echoes the functionality of put_time, but is able to handle invalid dates (when !ok()) since the + // Standard mandates that invalid dates still be formatted properly. For example, put_time isn't able to handle + // a tm_mday of 40, but format("{:%d}", day{40}) should return "40" and operator<< for day prints + // "40 is not a valid day". + template + bool _Custom_write( + basic_ostream<_CharT>& _Os, const _Chrono_spec<_CharT>& _Spec, const tm& _Time, const _Ty& _Val) { + const auto _Year = _Time.tm_year + 1900; + const auto _Month = _Time.tm_mon + 1; + const bool _Has_modifier = _Spec._Modifier != '\0'; + switch (_Spec._Type) { + case 'd': + case 'e': + // Most months have a proper last day, but February depends on the year. + if constexpr (is_same_v<_Ty, month_day_last>) { + if (_Val.month() == February) { + _THROW(format_error("Cannot print the last day of February without a year")); + } } - } - for (const auto& _Spec : _Specs._Chrono_specs_list) { - if (_Spec._Lit_char != _CharT{'\0'}) { - _Stream << _Spec._Lit_char; - continue; + if (_Has_modifier) { + return false; } - // We need to manually do certain writes, either because the specification is different from put_time or - // custom logic is needed. - if (_Custom_write(_Stream, _Spec, _Time, _Val)) { - continue; + if (_Time.tm_mday < 10) { + _Os << (_Spec._Type == 'd' ? _CharT{'0'} : _CharT{' '}); } - // Otherwise, we should throw on out-of-bounds to avoid triggering asserts within put_time machinery. - if constexpr (_Has_ok<_Ty>) { - if (!_Val.ok()) { - _THROW(format_error("Cannot localize out-of-bounds time point.")); - } + _Os << _Time.tm_mday; + return true; + case 'm': + if (_Has_modifier) { + return false; } - _CharT _Fmt_str[4]; - size_t _Next_idx = 0; - _Fmt_str[_Next_idx++] = _CharT{'%'}; - if (_Spec._Modifier != '\0') { - _Fmt_str[_Next_idx++] = static_cast<_CharT>(_Spec._Modifier); + if (_Month < 10) { + _Os << _CharT{'0'}; } - _Fmt_str[_Next_idx++] = static_cast<_CharT>(_Spec._Type); - _Fmt_str[_Next_idx] = _CharT{'\0'}; - - _Stream << _STD put_time<_CharT>(&_Time, _Fmt_str); - } - } - - return _Write_aligned(_STD move(_FormatCtx.out()), static_cast(_Stream.view().size()), _Specs, - _Fmt_align::_Left, [&](auto _Out) { return _Fmt_write(_STD move(_Out), _Stream.view()); }); - } - - // This echoes the functionality of put_time, but is able to handle invalid dates (when !ok()) since the Standard - // mandates that invalid dates still be formatted properly. For example, put_time isn't able to handle a tm_mday of - // 40, but format("{:%d}", day{40}) should return "40" and operator<< for day prints "40 is not a valid day". - template - bool _Custom_write( - basic_ostream<_CharT>& _Os, const _Chrono_specs<_CharT>& _Specs, const tm& _Time, const _Ty& _Val) { - const auto _Year = _Time.tm_year + 1900; - const auto _Month = _Time.tm_mon + 1; - const bool _Has_modifier = _Specs._Modifier != '\0'; - switch (_Specs._Type) { - case 'd': - case 'e': - // Most months have a proper last day, but February depends on the year. - if constexpr (is_same_v<_Ty, _CHRONO month_day_last>) { - if (_Val.month() == _CHRONO February) { - _THROW(format_error("Cannot print the last day of February without a year")); + _Os << _Month; + return true; + case 'Y': + if (_Has_modifier) { + return false; } - } - - if (_Has_modifier) { - return false; - } - - if (_Time.tm_mday < 10) { - _Os << (_Specs._Type == 'd' ? _CharT{'0'} : _CharT{' '}); - } - _Os << _Time.tm_mday; - return true; - case 'm': - if (_Has_modifier) { - return false; - } - if (_Month < 10) { - _Os << _CharT{'0'}; - } - _Os << _Month; - return true; - case 'Y': - if (_Has_modifier) { - return false; - } + if (_Year < 0) { + _Os << _CharT{'-'}; + } + _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:04}"), _STD abs(_Year)); + return true; + case 'y': + if (_Has_modifier) { + return false; + } + _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:02}"), _Year < 0 ? 100 + (_Year % 100) : _Year % 100); + return true; + case 'C': + if (_Has_modifier) { + return false; + } - if (_Year < 0) { + if (_Year < 0) { + _Os << _CharT{'-'}; + } + _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:02}"), + _STD abs(_Time_parse_fields::_Decompose_year(_Year).first) / 100); + return true; + case 'F': + _Custom_write(_Os, {._Type = 'Y'}, _Time, _Val); _Os << _CharT{'-'}; - } - _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:04}"), _STD abs(_Year)); - return true; - case 'y': - if (_Has_modifier) { - return false; - } - _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:02}"), _Year < 0 ? 100 + (_Year % 100) : _Year % 100); - return true; - case 'C': - if (_Has_modifier) { - return false; - } - - if (_Year < 0) { + _Custom_write(_Os, {._Type = 'm'}, _Time, _Val); _Os << _CharT{'-'}; - } - _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:02}"), - _STD abs(_CHRONO _Time_parse_fields::_Decompose_year(_Year).first) / 100); - return true; - case 'F': - _Custom_write(_Os, {._Type = 'Y'}, _Time, _Val); - _Os << _CharT{'-'}; - _Custom_write(_Os, {._Type = 'm'}, _Time, _Val); - _Os << _CharT{'-'}; - _Custom_write(_Os, {._Type = 'd'}, _Time, _Val); - return true; - case 'D': - _Custom_write(_Os, {._Type = 'm'}, _Time, _Val); - _Os << _CharT{'/'}; - _Custom_write(_Os, {._Type = 'd'}, _Time, _Val); - _Os << _CharT{'/'}; - _Custom_write(_Os, {._Type = 'y'}, _Time, _Val); - return true; - case 'H': - if constexpr (_Is_specialization_v<_Ty, _CHRONO hh_mm_ss>) { - if (_Val.hours() >= _CHRONO hours{24}) { - _THROW(format_error("Cannot localize hh_mm_ss with an absolute value of 24 hours or more.")); + _Custom_write(_Os, {._Type = 'd'}, _Time, _Val); + return true; + case 'D': + _Custom_write(_Os, {._Type = 'm'}, _Time, _Val); + _Os << _CharT{'/'}; + _Custom_write(_Os, {._Type = 'd'}, _Time, _Val); + _Os << _CharT{'/'}; + _Custom_write(_Os, {._Type = 'y'}, _Time, _Val); + return true; + case 'H': + if constexpr (_Is_specialization_v<_Ty, hh_mm_ss>) { + if (_Val.hours() >= hours{24}) { + _THROW(format_error("Cannot localize hh_mm_ss with an absolute value of 24 hours or more.")); + } } - } - return false; - case 'T': - // Alias for %H:%M:%S but we need to rewrite %S to display fractions of a second. - _Custom_write(_Os, {._Type = 'H'}, _Time, _Val); - _Os << _STD put_time(&_Time, _STATICALLY_WIDEN(_CharT, "%H:%M:")); - [[fallthrough]]; - case 'S': - if (_Has_modifier) { + return false; + case 'T': + // Alias for %H:%M:%S but we need to rewrite %S to display fractions of a second. + _Custom_write(_Os, {._Type = 'H'}, _Time, _Val); + _Os << _STD put_time(&_Time, _STATICALLY_WIDEN(_CharT, "%H:%M:")); + [[fallthrough]]; + case 'S': + if (_Has_modifier) { + return false; + } + _Write_seconds(_Os, _Val); + return true; + case 'Z': + _Os << _Time_zone_abbreviation; + return true; + case 'z': + _Os << _STATICALLY_WIDEN(_CharT, "+00"); + if (_Has_modifier) { + _Os << _CharT{':'}; + } + _Os << _STATICALLY_WIDEN(_CharT, "00"); + return true; + default: return false; } - _Write_seconds(_Os, _Val); - return true; - case 'Z': - _Os << _Time_zone_abbreviation; - return true; - case 'z': - _Os << _STATICALLY_WIDEN(_CharT, "+00"); - if (_Has_modifier) { - _Os << _CharT{':'}; - } - _Os << _STATICALLY_WIDEN(_CharT, "00"); - return true; - default: - return false; } - } - _Chrono_format_specs<_CharT> _Specs{}; - bool _No_chrono_specs = false; - basic_string_view<_CharT> _Time_zone_abbreviation{}; -}; + _Chrono_format_specs<_CharT> _Specs{}; + bool _No_chrono_specs = false; + basic_string_view<_CharT> _Time_zone_abbreviation{}; + }; +} // namespace chrono template struct _Fill_tm_formatter { @@ -5993,7 +5994,7 @@ struct _Fill_tm_formatter { } private: - _Chrono_formatter<_CharT> _Impl; + _CHRONO _Chrono_formatter<_CharT> _Impl; }; template @@ -6072,7 +6073,7 @@ struct formatter<_CHRONO sys_time<_Duration>, _CharT> { } private: - _Chrono_formatter<_CharT> _Impl{_STATICALLY_WIDEN(_CharT, "UTC")}; + _CHRONO _Chrono_formatter<_CharT> _Impl{_STATICALLY_WIDEN(_CharT, "UTC")}; }; template @@ -6088,7 +6089,7 @@ struct formatter<_CHRONO utc_time<_Duration>, _CharT> { } private: - _Chrono_formatter<_CharT> _Impl{_STATICALLY_WIDEN(_CharT, "UTC")}; + _CHRONO _Chrono_formatter<_CharT> _Impl{_STATICALLY_WIDEN(_CharT, "UTC")}; }; template @@ -6107,7 +6108,7 @@ struct formatter<_CHRONO tai_time<_Duration>, _CharT> { } private: - _Chrono_formatter<_CharT> _Impl{_STATICALLY_WIDEN(_CharT, "TAI")}; + _CHRONO _Chrono_formatter<_CharT> _Impl{_STATICALLY_WIDEN(_CharT, "TAI")}; }; template @@ -6126,7 +6127,7 @@ struct formatter<_CHRONO gps_time<_Duration>, _CharT> { } private: - _Chrono_formatter<_CharT> _Impl{_STATICALLY_WIDEN(_CharT, "GPS")}; + _CHRONO _Chrono_formatter<_CharT> _Impl{_STATICALLY_WIDEN(_CharT, "GPS")}; }; template @@ -6142,7 +6143,7 @@ struct formatter<_CHRONO file_time<_Duration>, _CharT> { } private: - _Chrono_formatter<_CharT> _Impl{_STATICALLY_WIDEN(_CharT, "UTC")}; + _CHRONO _Chrono_formatter<_CharT> _Impl{_STATICALLY_WIDEN(_CharT, "UTC")}; }; template diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index 30a4cc253e..524f46df54 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -47,7 +47,7 @@ struct testing_callbacks { int expected_precision = -1; size_t expected_dynamic_precision = static_cast(-1); bool expected_auto_dynamic_precision = false; - vector<_Chrono_specs>& expected_chrono_specs; + vector<_Chrono_spec>& expected_chrono_specs; size_t curr_index = 0; void _On_align(_Fmt_align aln) { @@ -108,7 +108,7 @@ template bool test_parse_conversion_spec() { auto parse_conv_spec_fn = _Parse_conversion_specs>; using view_typ = basic_string_view; - using chrono_spec = _Chrono_specs; + using chrono_spec = _Chrono_spec; view_typ s0(TYPED_LITERAL(CharT, "B")); view_typ s1(TYPED_LITERAL(CharT, "Ec")); @@ -140,7 +140,7 @@ template bool test_parse_chrono_format_specs() { auto parse_chrono_format_specs_fn = _Parse_chrono_format_specs>; using view_typ = basic_string_view; - using chrono_spec = _Chrono_specs; + using chrono_spec = _Chrono_spec; view_typ s0(TYPED_LITERAL(CharT, "%Oe")); view_typ s1(TYPED_LITERAL(CharT, "lit")); @@ -233,6 +233,27 @@ constexpr void print(Str str) { } } +template +void test_clock_formatter() { + stream_helper(STR("1970-01-01 00:00:00"), sys_seconds{}); + stream_helper(STR("1970-01-01"), sys_days{}); + stream_helper(STR("1970-01-01 00:00:00"), utc_seconds{}); + stream_helper(STR("1958-01-01 00:00:00"), tai_seconds{}); + stream_helper(STR("1980-01-06 00:00:00"), gps_seconds{}); + stream_helper(STR("1601-01-01 00:00:00"), file_time{}); + stream_helper(STR("1970-01-01 00:00:00"), local_seconds{}); + + assert(format(STR("{:%Z %z %Oz %Ez}"), sys_seconds{}) == STR("UTC +0000 +00:00 +00:00")); + assert(format(STR("{:%Z %z %Oz %Ez}"), sys_days{}) == STR("UTC +0000 +00:00 +00:00")); + assert(format(STR("{:%Z %z %Oz %Ez}"), utc_seconds{}) == STR("UTC +0000 +00:00 +00:00")); + assert(format(STR("{:%Z %z %Oz %Ez}"), tai_seconds{}) == STR("TAI +0000 +00:00 +00:00")); + assert(format(STR("{:%Z %z %Oz %Ez}"), gps_seconds{}) == STR("GPS +0000 +00:00 +00:00")); + assert(format(STR("{:%Z %z %Oz %Ez}"), file_time{}) == STR("UTC +0000 +00:00 +00:00")); + throw_helper(STR("{:%Z %z %Oz %Ez}"), local_seconds{}); + + assert(format(STR("{:%S}"), utc_clock::from_sys(get_tzdb().leap_seconds.front().date()) - 1s) == STR("60")); +} + template void test_day_formatter() { using view_typ = basic_string_view; @@ -401,6 +422,24 @@ void test_weekday_last_formatter() { assert(format(STR("{:%a %A %u %w}"), Sunday[last]) == STR("Sun Sunday 7 0")); } +template +void test_month_day_formatter() { + stream_helper(STR("Jan/16"), January / 16); + stream_helper(STR("13 is not a valid month/40 is not a valid day"), month{13} / day{40}); + + assert(format(STR("{:%B %d}"), June / 17) == STR("June 17")); + throw_helper(STR("{:%Y}"), June / 17); +} + +template +void test_month_day_last_formatter() { + stream_helper(STR("Feb/last"), February / last); + + assert(format(STR("{:%B}"), June / last) == STR("June")); + assert(format(STR("{:%d}"), June / last) == STR("30")); + throw_helper(STR("{:%d}"), February / last); +} + template void test_month_weekday_formatter() { constexpr month_weekday mwd1 = August / Tuesday[3]; @@ -443,6 +482,14 @@ void test_month_weekday_last_formatter() { assert(format(STR("{:%b %B %h %m %a %A %u %w}"), mwdl2) == STR("Dec December Dec 12 Sun Sunday 7 0")); } +template +void test_year_month_formatter() { + stream_helper(STR("1444/Oct"), 1444y / October); + + assert(format(STR("{:%Y %B}"), 2000y / July) == STR("2000 July")); + throw_helper(STR("{:%d}"), 2000y / July); +} + template void test_year_month_day_formatter() { year_month_day invalid{year{1234}, month{0}, day{31}}; @@ -535,53 +582,6 @@ void test_hh_mm_ss_formatter() { assert(format(STR("{:%M %S}"), hh_mm_ss{27h + 12min + 30s}) == STR("12 30")); } -template -void test_month_day_formatter() { - stream_helper(STR("Jan/16"), January / 16); - stream_helper(STR("13 is not a valid month/40 is not a valid day"), month{13} / day{40}); - - assert(format(STR("{:%B %d}"), June / 17) == STR("June 17")); - throw_helper(STR("{:%Y}"), June / 17); -} - -template -void test_month_day_last_formatter() { - stream_helper(STR("Feb/last"), February / last); - - assert(format(STR("{:%B}"), June / last) == STR("June")); - assert(format(STR("{:%d}"), June / last) == STR("30")); - throw_helper(STR("{:%d}"), February / last); -} - -template -void test_year_month_formatter() { - stream_helper(STR("1444/Oct"), 1444y / October); - - assert(format(STR("{:%Y %B}"), 2000y / July) == STR("2000 July")); - throw_helper(STR("{:%d}"), 2000y / July); -} - -template -void test_clock_formatter() { - stream_helper(STR("1970-01-01 00:00:00"), sys_seconds{}); - stream_helper(STR("1970-01-01"), sys_days{}); - stream_helper(STR("1970-01-01 00:00:00"), utc_seconds{}); - stream_helper(STR("1958-01-01 00:00:00"), tai_seconds{}); - stream_helper(STR("1980-01-06 00:00:00"), gps_seconds{}); - stream_helper(STR("1601-01-01 00:00:00"), file_time{}); - stream_helper(STR("1970-01-01 00:00:00"), local_seconds{}); - - assert(format(STR("{:%Z %z %Oz %Ez}"), sys_seconds{}) == STR("UTC +0000 +00:00 +00:00")); - assert(format(STR("{:%Z %z %Oz %Ez}"), sys_days{}) == STR("UTC +0000 +00:00 +00:00")); - assert(format(STR("{:%Z %z %Oz %Ez}"), utc_seconds{}) == STR("UTC +0000 +00:00 +00:00")); - assert(format(STR("{:%Z %z %Oz %Ez}"), tai_seconds{}) == STR("TAI +0000 +00:00 +00:00")); - assert(format(STR("{:%Z %z %Oz %Ez}"), gps_seconds{}) == STR("GPS +0000 +00:00 +00:00")); - assert(format(STR("{:%Z %z %Oz %Ez}"), file_time{}) == STR("UTC +0000 +00:00 +00:00")); - throw_helper(STR("{:%Z %z %Oz %Ez}"), local_seconds{}); - - assert(format(STR("{:%S}"), utc_clock::from_sys(get_tzdb().leap_seconds.front().date()) - 1s) == STR("60")); -} - void test_exception_classes() { { // N4885 [time.zone.exception.nonexist]/4 string s; @@ -622,6 +622,9 @@ int main() { test_parse_chrono_format_specs(); test_parse_chrono_format_specs(); + test_clock_formatter(); + test_clock_formatter(); + test_day_formatter(); test_day_formatter(); @@ -640,12 +643,21 @@ int main() { test_weekday_last_formatter(); test_weekday_last_formatter(); + test_month_day_formatter(); + test_month_day_formatter(); + + test_month_day_last_formatter(); + test_month_day_last_formatter(); + test_month_weekday_formatter(); test_month_weekday_formatter(); test_month_weekday_last_formatter(); test_month_weekday_last_formatter(); + test_year_month_formatter(); + test_year_month_formatter(); + test_year_month_day_formatter(); test_year_month_day_formatter(); @@ -661,17 +673,5 @@ int main() { test_hh_mm_ss_formatter(); test_hh_mm_ss_formatter(); - test_month_day_formatter(); - test_month_day_formatter(); - - test_month_day_last_formatter(); - test_month_day_last_formatter(); - - test_year_month_formatter(); - test_year_month_formatter(); - - test_clock_formatter(); - test_clock_formatter(); - test_exception_classes(); } From c06416c50efe95fedc054d4520a25f2ce5db4a14 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Sun, 18 Apr 2021 22:31:25 -0700 Subject: [PATCH 28/41] `` formatting: Simplify test (#1859) * Replace stream_helper with empty_braces_helper. * Replace assert(format(STR("{}"), A) == B) with empty_braces_helper(A, B). * Remove duplicate lines. * Simplify choose_literal, use STR consistently. --- .../test.cpp | 193 ++++++++---------- 1 file changed, 84 insertions(+), 109 deletions(-) diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index 524f46df54..9d6db36597 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -16,26 +16,16 @@ using namespace std; using namespace chrono; -// copied from the string_view tests template -struct choose_literal; // not defined - -template <> -struct choose_literal { - static constexpr const char* choose(const char* s, const wchar_t*) { - return s; - } -}; - -template <> -struct choose_literal { - static constexpr const wchar_t* choose(const char*, const wchar_t* s) { - return s; +[[nodiscard]] constexpr const CharT* choose_literal(const char* const str, const wchar_t* const wstr) noexcept { + if constexpr (is_same_v) { + return str; + } else { + return wstr; } -}; +} -#define TYPED_LITERAL(CharT, Literal) (choose_literal::choose(Literal, L##Literal)) -#define STR(Literal) TYPED_LITERAL(CharT, Literal) +#define STR(Literal) (choose_literal(Literal, L##Literal)) template struct testing_callbacks { @@ -110,13 +100,13 @@ bool test_parse_conversion_spec() { using view_typ = basic_string_view; using chrono_spec = _Chrono_spec; - view_typ s0(TYPED_LITERAL(CharT, "B")); - view_typ s1(TYPED_LITERAL(CharT, "Ec")); - view_typ s2(TYPED_LITERAL(CharT, "Od")); - view_typ s3(TYPED_LITERAL(CharT, "E")); - view_typ s4(TYPED_LITERAL(CharT, "")); - view_typ s5(TYPED_LITERAL(CharT, "}")); - view_typ s6(TYPED_LITERAL(CharT, "E}")); + view_typ s0(STR("B")); + view_typ s1(STR("Ec")); + view_typ s2(STR("Od")); + view_typ s3(STR("E")); + view_typ s4(STR("")); + view_typ s5(STR("}")); + view_typ s6(STR("E}")); vector v0{{._Type = 'B'}}; test_parse_helper(parse_conv_spec_fn, s0, false, view_typ::npos, {.expected_chrono_specs = v0}); @@ -142,15 +132,15 @@ bool test_parse_chrono_format_specs() { using view_typ = basic_string_view; using chrono_spec = _Chrono_spec; - view_typ s0(TYPED_LITERAL(CharT, "%Oe")); - view_typ s1(TYPED_LITERAL(CharT, "lit")); - view_typ s2(TYPED_LITERAL(CharT, "%H:%M}")); - view_typ s3(TYPED_LITERAL(CharT, "6%H}")); - view_typ s4(TYPED_LITERAL(CharT, "*<6hi")); - view_typ s5(TYPED_LITERAL(CharT, "*^4.4%ymm")); - view_typ s6(TYPED_LITERAL(CharT, "%H%")); - view_typ s7(TYPED_LITERAL(CharT, "%H%}")); - view_typ s8(TYPED_LITERAL(CharT, "%nB%tC%%D")); + view_typ s0(STR("%Oe")); + view_typ s1(STR("lit")); + view_typ s2(STR("%H:%M}")); + view_typ s3(STR("6%H}")); + view_typ s4(STR("*<6hi")); + view_typ s5(STR("*^4.4%ymm")); + view_typ s6(STR("%H%")); + view_typ s7(STR("%H%}")); + view_typ s8(STR("%nB%tC%%D")); vector v0{{._Modifier = 'O', ._Type = 'e'}}; test_parse_helper(parse_chrono_format_specs_fn, s0, false, s0.size(), {.expected_chrono_specs = v0}); @@ -172,14 +162,14 @@ bool test_parse_chrono_format_specs() { vector v4{{._Lit_char = 'h'}, {._Lit_char = 'i'}}; test_parse_helper(parse_chrono_format_specs_fn, s4, true, s4.size(), {.expected_alignment = _Fmt_align::_Left, - .expected_fill = view_typ(TYPED_LITERAL(CharT, "*")), + .expected_fill = view_typ(STR("*")), .expected_width = 6, .expected_chrono_specs = v4}); vector v5{{._Type = 'y'}, {._Lit_char = 'm'}, {._Lit_char = 'm'}}; test_parse_helper(parse_chrono_format_specs_fn, s5, false, s5.size(), {.expected_alignment = _Fmt_align::_Center, - .expected_fill = view_typ(TYPED_LITERAL(CharT, "*")), + .expected_fill = view_typ(STR("*")), .expected_width = 4, .expected_precision = 4, .expected_chrono_specs = v5}); @@ -205,14 +195,6 @@ void throw_helper(const CharT* fmt, const Args&... vals) { throw_helper(basic_string_view{fmt}, vals...); } -template -void stream_helper(const CharT* expect, const Args&... vals) { - basic_ostringstream stream; - (stream << ... << vals); - assert(stream.str() == expect); - assert(stream); -} - template void empty_braces_helper(const Arg& val, const CharT* const expected) { // N4885 [time.format]/6: "If the chrono-specs is omitted, the chrono object is formatted @@ -220,7 +202,10 @@ void empty_braces_helper(const Arg& val, const CharT* const expected) { // of the context with additional padding and adjustments as specified by the format specifiers." assert(format(STR("{}"), val) == expected); - stream_helper(expected, val); + basic_ostringstream stream; + stream << val; + assert(stream.str() == expected); + assert(stream); } // FIXME: TEMPORARY CODE FOR WRITING TESTS, REMOVE BEFORE MERGING @@ -235,13 +220,13 @@ constexpr void print(Str str) { template void test_clock_formatter() { - stream_helper(STR("1970-01-01 00:00:00"), sys_seconds{}); - stream_helper(STR("1970-01-01"), sys_days{}); - stream_helper(STR("1970-01-01 00:00:00"), utc_seconds{}); - stream_helper(STR("1958-01-01 00:00:00"), tai_seconds{}); - stream_helper(STR("1980-01-06 00:00:00"), gps_seconds{}); - stream_helper(STR("1601-01-01 00:00:00"), file_time{}); - stream_helper(STR("1970-01-01 00:00:00"), local_seconds{}); + empty_braces_helper(sys_seconds{}, STR("1970-01-01 00:00:00")); + empty_braces_helper(sys_days{}, STR("1970-01-01")); + empty_braces_helper(utc_seconds{}, STR("1970-01-01 00:00:00")); + empty_braces_helper(tai_seconds{}, STR("1958-01-01 00:00:00")); + empty_braces_helper(gps_seconds{}, STR("1980-01-06 00:00:00")); + empty_braces_helper(file_time{}, STR("1601-01-01 00:00:00")); + empty_braces_helper(local_seconds{}, STR("1970-01-01 00:00:00")); assert(format(STR("{:%Z %z %Oz %Ez}"), sys_seconds{}) == STR("UTC +0000 +00:00 +00:00")); assert(format(STR("{:%Z %z %Oz %Ez}"), sys_days{}) == STR("UTC +0000 +00:00 +00:00")); @@ -259,25 +244,25 @@ void test_day_formatter() { using view_typ = basic_string_view; using str_typ = basic_string; - view_typ s0(TYPED_LITERAL(CharT, "{:%d}")); - view_typ s1(TYPED_LITERAL(CharT, "{:%e}")); - view_typ s2(TYPED_LITERAL(CharT, "{:%Od}")); - view_typ s3(TYPED_LITERAL(CharT, "{:%Oe}")); - view_typ s4(TYPED_LITERAL(CharT, "{}")); - view_typ s5(TYPED_LITERAL(CharT, "{:=>8}")); - view_typ s6(TYPED_LITERAL(CharT, "{:lit}")); - view_typ s7(TYPED_LITERAL(CharT, "{:%d days}")); - view_typ s8(TYPED_LITERAL(CharT, "{:*^6%dmm}")); - - str_typ a0(TYPED_LITERAL(CharT, "27")); - str_typ a1(TYPED_LITERAL(CharT, "05")); - str_typ a2(TYPED_LITERAL(CharT, " 5")); - str_typ a3(TYPED_LITERAL(CharT, "50 is not a valid day")); - str_typ a4(TYPED_LITERAL(CharT, "======27")); - str_typ a5(TYPED_LITERAL(CharT, "======05")); - str_typ a6(TYPED_LITERAL(CharT, "lit27")); - str_typ a7(TYPED_LITERAL(CharT, "27 days")); - str_typ a8(TYPED_LITERAL(CharT, "*27mm*")); + view_typ s0(STR("{:%d}")); + view_typ s1(STR("{:%e}")); + view_typ s2(STR("{:%Od}")); + view_typ s3(STR("{:%Oe}")); + view_typ s4(STR("{}")); + view_typ s5(STR("{:=>8}")); + view_typ s6(STR("{:lit}")); + view_typ s7(STR("{:%d days}")); + view_typ s8(STR("{:*^6%dmm}")); + + str_typ a0(STR("27")); + str_typ a1(STR("05")); + str_typ a2(STR(" 5")); + str_typ a3(STR("50 is not a valid day")); + str_typ a4(STR("======27")); + str_typ a5(STR("======05")); + str_typ a6(STR("lit27")); + str_typ a7(STR("27 days")); + str_typ a8(STR("*27mm*")); // 2 digits day d0{27}; @@ -332,20 +317,19 @@ void test_day_formatter() { throw_helper(STR("{:%Ed}"), day{10}); throw_helper(STR("{:%Od}"), day{40}); throw_helper(STR("{:%Ed}"), day{40}); - assert(format(STR("{}"), day{0}) == STR("00 is not a valid day")); // Op << - stream_helper(STR("00 is not a valid day"), day{0}); - stream_helper(STR("27"), day{27}); - stream_helper(STR("200 is not a valid day"), day{200}); + empty_braces_helper(day{0}, STR("00 is not a valid day")); + empty_braces_helper(day{27}, STR("27")); + empty_braces_helper(day{200}, STR("200 is not a valid day")); } template void test_month_formatter() { - assert(format(STR("{}"), month{1}) == STR("Jan")); - assert(format(STR("{}"), month{12}) == STR("Dec")); - assert(format(STR("{}"), month{0}) == STR("0 is not a valid month")); - assert(format(STR("{}"), month{20}) == STR("20 is not a valid month")); + empty_braces_helper(month{1}, STR("Jan")); + empty_braces_helper(month{12}, STR("Dec")); + empty_braces_helper(month{0}, STR("0 is not a valid month")); + empty_braces_helper(month{20}, STR("20 is not a valid month")); // Specs assert(format(STR("{:%b %h %B}"), month{1}) == STR("Jan Jan January")); @@ -360,35 +344,28 @@ void test_month_formatter() { // Invalid specs throw_helper(STR("{:%A}"), month{1}); throw_helper(STR("{:%.4}"), month{1}); - - // Op << - stream_helper(STR("Jan"), month{1}); - stream_helper(STR("Dec"), month{12}); - stream_helper(STR("0 is not a valid month"), month{0}); - stream_helper(STR("20 is not a valid month"), month{20}); } template void test_year_formatter() { - assert(format(STR("{}"), year{0}) == STR("0000")); - assert(format(STR("{}"), year{-200}) == STR("-0200")); - assert(format(STR("{}"), year{121}) == STR("0121")); + empty_braces_helper(year{0}, STR("0000")); + empty_braces_helper(year{-200}, STR("-0200")); + empty_braces_helper(year{121}, STR("0121")); assert(format(STR("{:%Y %y%C}"), year{1912}) == STR("1912 1219")); assert(format(STR("{:%Y %y%C}"), year{-1912}) == STR("-1912 88-20")); // TRANSITION, add tests for EY Oy Ey EC - stream_helper(STR("1900"), year{1900}); - stream_helper(STR("2000"), year{2000}); - stream_helper(STR("-32768 is not a valid year"), year{-32768}); + empty_braces_helper(year{1900}, STR("1900")); + empty_braces_helper(year{2000}, STR("2000")); + empty_braces_helper(year{-32768}, STR("-32768 is not a valid year")); } template void test_weekday_formatter() { weekday invalid{10}; - assert(format(STR("{}"), weekday{3}) == STR("Wed")); - stream_helper(STR("Wed"), weekday{3}); - stream_helper(STR("10 is not a valid weekday"), invalid); + empty_braces_helper(weekday{3}, STR("Wed")); + empty_braces_helper(invalid, STR("10 is not a valid weekday")); assert(format(STR("{:%a %A}"), weekday{6}) == STR("Sat Saturday")); assert(format(STR("{:%u %w}"), weekday{6}) == STR("6 6")); @@ -400,11 +377,10 @@ void test_weekday_indexed_formatter() { weekday_indexed invalid1{Tuesday, 10}; weekday_indexed invalid2{weekday{10}, 3}; weekday_indexed invalid3{weekday{14}, 9}; - assert(format(STR("{}"), weekday_indexed{Monday, 1}) == STR("Mon[1]")); - stream_helper(STR("Mon[1]"), weekday_indexed{Monday, 1}); - stream_helper(STR("Tue[10 is not a valid index]"), invalid1); - stream_helper(STR("10 is not a valid weekday[3]"), invalid2); - stream_helper(STR("14 is not a valid weekday[9 is not a valid index]"), invalid3); + empty_braces_helper(weekday_indexed{Monday, 1}, STR("Mon[1]")); + empty_braces_helper(invalid1, STR("Tue[10 is not a valid index]")); + empty_braces_helper(invalid2, STR("10 is not a valid weekday[3]")); + empty_braces_helper(invalid3, STR("14 is not a valid weekday[9 is not a valid index]")); assert(format(STR("{:%a %A}"), weekday_indexed{Monday, 2}) == STR("Mon Monday")); assert(format(STR("{:%u %w}"), weekday_indexed{Tuesday, 3}) == STR("2 2")); @@ -424,8 +400,8 @@ void test_weekday_last_formatter() { template void test_month_day_formatter() { - stream_helper(STR("Jan/16"), January / 16); - stream_helper(STR("13 is not a valid month/40 is not a valid day"), month{13} / day{40}); + empty_braces_helper(January / 16, STR("Jan/16")); + empty_braces_helper(month{13} / day{40}, STR("13 is not a valid month/40 is not a valid day")); assert(format(STR("{:%B %d}"), June / 17) == STR("June 17")); throw_helper(STR("{:%Y}"), June / 17); @@ -433,7 +409,7 @@ void test_month_day_formatter() { template void test_month_day_last_formatter() { - stream_helper(STR("Feb/last"), February / last); + empty_braces_helper(February / last, STR("Feb/last")); assert(format(STR("{:%B}"), June / last) == STR("June")); assert(format(STR("{:%d}"), June / last) == STR("30")); @@ -484,7 +460,7 @@ void test_month_weekday_last_formatter() { template void test_year_month_formatter() { - stream_helper(STR("1444/Oct"), 1444y / October); + empty_braces_helper(1444y / October, STR("1444/Oct")); assert(format(STR("{:%Y %B}"), 2000y / July) == STR("2000 July")); throw_helper(STR("{:%d}"), 2000y / July); @@ -493,9 +469,8 @@ void test_year_month_formatter() { template void test_year_month_day_formatter() { year_month_day invalid{year{1234}, month{0}, day{31}}; - assert(format(STR("{}"), year_month_day{year{1900}, month{2}, day{1}}) == STR("1900-02-01")); - stream_helper(STR("1900-02-01"), year_month_day{year{1900}, month{2}, day{1}}); - stream_helper(STR("1234-00-31 is not a valid date"), invalid); + empty_braces_helper(year_month_day{year{1900}, month{2}, day{1}}, STR("1900-02-01")); + empty_braces_helper(invalid, STR("1234-00-31 is not a valid date")); assert(format(STR("{:%Y %b %d}"), year_month_day{year{1234}, month{5}, day{6}}) == STR("1234 May 06")); assert(format(STR("{:%F %D}"), invalid) == STR("1234-00-31 00/31/34")); @@ -566,10 +541,10 @@ void test_year_month_weekday_last_formatter() { template void test_hh_mm_ss_formatter() { - stream_helper(STR("-01:08:03.007"), hh_mm_ss{-4083007ms}); - stream_helper(STR("01:08:03.007"), hh_mm_ss{4083007ms}); - stream_helper(STR("18:15:45.123"), hh_mm_ss{65745123ms}); - stream_helper(STR("18:15:45"), hh_mm_ss{65745s}); + empty_braces_helper(hh_mm_ss{-4083007ms}, STR("-01:08:03.007")); + empty_braces_helper(hh_mm_ss{4083007ms}, STR("01:08:03.007")); + empty_braces_helper(hh_mm_ss{65745123ms}, STR("18:15:45.123")); + empty_braces_helper(hh_mm_ss{65745s}, STR("18:15:45")); assert(format(STR("{:%H %I %M %S %r %R %T %p}"), hh_mm_ss{13h + 14min + 15351ms}) == STR("13 01 14 15.351 13:14:15 13:14 13:14:15.351 PM")); From 229dfde84db7f3b72f080316555a424b4b58e0c2 Mon Sep 17 00:00:00 2001 From: Elnar Dakeshov <55715127+eldakesh-ms@users.noreply.github.com> Date: Mon, 19 Apr 2021 16:01:45 -0700 Subject: [PATCH 29/41] chronat: Add duration formatter (#1861) * chronat: Add duration formatter Special specifiers for duration are `j q Q`. Otherwise duration is very similar to `hh_mm_ss` except that times are interpreted as time from midnight. I thought this would mean that negative times are yesterday, but we just append a `-` instead, which means we should round instead of flooring to a day when computing hh/mm/ss. Other behavior is pretty simple. * Add typename. * Comments Co-authored-by: Stephan T. Lavavej --- stl/inc/chrono | 62 +++++++++++++++---- .../test.cpp | 24 +++++++ 2 files changed, 74 insertions(+), 12 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index a178fe500a..9ffe4b06af 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -774,6 +774,19 @@ namespace chrono { return _Rnext; } + template + void _Write_unit_suffix(basic_ostream<_CharT, _Traits>& _Os) { + constexpr auto _Suffix = _Get_literal_unit_suffix<_CharT, _Period>(); + if constexpr (_Suffix == nullptr) { + _CharT _Buffer[2 * (numeric_limits::digits10 + 1) + 5] = {}; // 2 numbers + "[/]s\0" + const _CharT* const _Begin = + _Get_general_unit_suffix<_CharT>(_STD end(_Buffer), _Period::num, _Period::den); + _Os << _Begin; + } else { + _Os << _Suffix; + } + } + template basic_ostream<_CharT, _Traits>& operator<<( basic_ostream<_CharT, _Traits>& _Os, const duration<_Rep, _Period>& _Dur) { @@ -782,16 +795,7 @@ namespace chrono { _Sstr.imbue(_Os.getloc()); _Sstr.precision(_Os.precision()); _Sstr << _Dur.count(); - - constexpr auto _Suffix = _Get_literal_unit_suffix<_CharT, _Period>(); - if constexpr (_Suffix == nullptr) { - _CharT _Buffer[2 * (numeric_limits::digits10 + 1) + 5] = {}; // 2 numbers + "[/]s\0" - const _CharT* const _Begin = - _Get_general_unit_suffix<_CharT>(_STD end(_Buffer), _Period::num, _Period::den); - _Sstr << _Begin; - } else { - _Sstr << _Suffix; - } + _Write_unit_suffix<_Period>(_Sstr); return _Os << _Sstr.str(); } @@ -5470,6 +5474,12 @@ namespace chrono { _Write_seconds(_Os, hh_mm_ss{_Val - _Dp}); } + template + void _Write_seconds(basic_ostream<_CharT, _Traits>& _Os, const duration<_Rep, _Period>& _Val) { + const auto _Dp = _CHRONO duration_cast(_Val); + _Write_seconds(_Os, hh_mm_ss{_Val - _Dp}); + } + template _NODISCARD tm _Fill_tm(const _Ty& _Val) { unsigned int _Day = 0; @@ -5480,7 +5490,10 @@ namespace chrono { int _Minutes = 0; int _Seconds = 0; - if constexpr (is_same_v<_Ty, day>) { + if constexpr (_Is_specialization_v<_Ty, duration>) { + const auto _Dp = _CHRONO duration_cast(_Val); + return _Fill_tm(hh_mm_ss{_Val - _Dp}); + } else if constexpr (is_same_v<_Ty, day>) { _Day = static_cast(_Val); } else if constexpr (is_same_v<_Ty, month>) { _Month = static_cast(_Val); @@ -5779,7 +5792,9 @@ namespace chrono { template _NODISCARD constexpr bool _Is_valid_type(const char _Type) noexcept { - if constexpr (is_same_v<_Ty, day>) { + if constexpr (_Is_specialization_v<_Ty, duration>) { + return _Type == 'j' || _Type == 'q' || _Type == 'Q' || _Is_valid_type>(_Type); + } else if constexpr (is_same_v<_Ty, day>) { return _Type == 'd' || _Type == 'e'; } else if constexpr (is_same_v<_Ty, month>) { return _Type == 'b' || _Type == 'B' || _Type == 'h' || _Type == 'm'; @@ -5825,6 +5840,10 @@ namespace chrono { if (_Val.is_negative()) { _Stream << _CharT{'-'}; } + } else if constexpr (_Is_specialization_v<_Ty, duration>) { + if (_Val < _Ty::zero()) { + _Stream << _CharT{'-'}; + } } for (const auto& _Spec : _Specs._Chrono_specs_list) { @@ -5892,6 +5911,21 @@ namespace chrono { } _Os << _Time.tm_mday; return true; + case 'j': + if constexpr (_Is_specialization_v<_Ty, duration>) { + _Os << _STD abs(_CHRONO duration_cast(_Val).count()); + } + return true; + case 'q': + if constexpr (_Is_specialization_v<_Ty, duration>) { + _Write_unit_suffix(_Os); + } + return true; + case 'Q': + if constexpr (_Is_specialization_v<_Ty, duration>) { + _Os << _STD abs(_Val.count()); + } + return true; case 'm': if (_Has_modifier) { return false; @@ -5997,6 +6031,10 @@ private: _CHRONO _Chrono_formatter<_CharT> _Impl; }; +template +struct formatter<_CHRONO duration<_Rep, _Period>, _CharT> + : _Fill_tm_formatter<_CHRONO duration<_Rep, _Period>, _CharT> {}; + template struct formatter<_CHRONO day, _CharT> // : _Fill_tm_formatter<_CHRONO day, _CharT> {}; diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index 9d6db36597..311cb96ab7 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -218,6 +218,27 @@ constexpr void print(Str str) { } } +template +void test_duration_formatter() { + empty_braces_helper(seconds{5}, STR("5s")); + empty_braces_helper(minutes{7}, STR("7min")); + empty_braces_helper(hours{9}, STR("9h")); + empty_braces_helper(days{2}, STR("2d")); + empty_braces_helper(-seconds{5}, STR("-5s")); + empty_braces_helper(duration>{40}, STR("40[3]s")); + empty_braces_helper(duration>{40}, STR("40[3/7]s")); + + assert(format(STR("{:%T}"), 4083007ms) == STR("01:08:03.007")); + assert(format(STR("{:%T}"), -4083007ms) == STR("-01:08:03.007")); + + assert(format(STR("{:%T %j %q %Q}"), days{4} + 30min) == STR("00:30:00 4 min 5790")); + assert(format(STR("{:%T %j %q %Q}"), -days{4} - 30min) == STR("-00:30:00 4 min 5790")); + assert(format(STR("{:%T %j}"), days{4} + 23h + 30min) == STR("23:30:00 4")); + assert(format(STR("{:%T %j}"), -days{4} - 23h - 30min) == STR("-23:30:00 4")); + assert(format(STR("{:%T %j}"), duration{1.55f}) == STR("13:11:59 1")); + assert(format(STR("{:%T %j}"), duration{-1.55f}) == STR("-13:11:59 1")); +} + template void test_clock_formatter() { empty_braces_helper(sys_seconds{}, STR("1970-01-01 00:00:00")); @@ -597,6 +618,9 @@ int main() { test_parse_chrono_format_specs(); test_parse_chrono_format_specs(); + test_duration_formatter(); + test_duration_formatter(); + test_clock_formatter(); test_clock_formatter(); From 39c722fe3e2183104296c89ed67732a7af7bccb5 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 19 Apr 2021 17:07:59 -0700 Subject: [PATCH 30/41] `` formatting: `sys_info`, `local_info`, feature-test macro (#1860) * Define and test the feature-test macro. * Implement and test sys_info and local_info. * Update libcxx skips. * Additionally test zero and half-hour offsets. * Print `seconds offset` and `minutes save`. * Add TRANSITION comment. --- stl/inc/chrono | 90 +++++++++++++- stl/inc/yvals_core.h | 15 +-- tests/libcxx/expected_results.txt | 3 + tests/libcxx/skipped_tests.txt | 3 + .../test.cpp | 110 +++++++++++++++++- .../test.compile.pass.cpp | 6 + 6 files changed, 212 insertions(+), 15 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 9ffe4b06af..ece4fc6268 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5537,6 +5537,8 @@ namespace chrono { _Hours = _Val.hours().count(); _Minutes = _Val.minutes().count(); _Seconds = static_cast(_Val.seconds().count()); + } else if constexpr (_Is_any_of_v<_Ty, sys_info, local_info>) { + return {}; // none of the valid conversion specifiers need tm fields } else if constexpr (_Is_specialization_v<_Ty, time_point>) { const auto _Dp = _CHRONO floor(_Val); const year_month_day _Ymd{_Dp}; @@ -5655,6 +5657,47 @@ namespace chrono { return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:%T}"), _Val); } +#pragma warning(push) +#pragma warning(disable : 4365) // 'argument': conversion from 'char' to 'const wchar_t', signed/unsigned mismatch + template + _NODISCARD decltype(auto) _Widen_string(const string& _Str) { + if constexpr (is_same_v<_CharT, char>) { + return _Str; + } else { + return wstring{_Str.begin(), _Str.end()}; // TRANSITION, should probably use ctype::widen + } + } +#pragma warning(pop) + + template + basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const sys_info& _Val) { + return _Os << _STD format(_Os.getloc(), + _STATICALLY_WIDEN(_CharT, "begin: {}, end: {}, offset: {}, save: {}, abbrev: {}"), // + _Val.begin, _Val.end, _Val.offset, _Val.save, _Widen_string<_CharT>(_Val.abbrev)); + } + + template + basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const local_info& _Val) { + switch (_Val.result) { + case local_info::unique: + return _Os << _STD format(_Os.getloc(), // + _STATICALLY_WIDEN(_CharT, "result: unique, first: ({})"), // + _Val.first); + case local_info::nonexistent: + return _Os << _STD format(_Os.getloc(), + _STATICALLY_WIDEN(_CharT, "result: nonexistent, first: ({}), second: ({})"), // + _Val.first, _Val.second); + case local_info::ambiguous: + return _Os << _STD format(_Os.getloc(), + _STATICALLY_WIDEN(_CharT, "result: ambiguous, first: ({}), second: ({})"), // + _Val.first, _Val.second); + default: + return _Os << _STD format(_Os.getloc(), // + _STATICALLY_WIDEN(_CharT, "result: {}, first: ({}), second: ({})"), // + _Val.result, _Val.first, _Val.second); + } + } + template // clang-format off requires (!treat_as_floating_point_v && _Duration{1} < days{1}) @@ -5815,6 +5858,8 @@ namespace chrono { } else if constexpr (_Is_specialization_v<_Ty, hh_mm_ss>) { return _Type == 'H' || _Type == 'I' || _Type == 'M' || _Type == 'S' || _Type == 'r' || _Type == 'R' || _Type == 'T' || _Type == 'p'; + } else if constexpr (_Is_any_of_v<_Ty, sys_info, local_info>) { + return _Type == 'z' || _Type == 'Z'; } else if constexpr (_Is_specialization_v<_Ty, time_point>) { if constexpr (!is_same_v) { if (_Type == 'z' || _Type == 'Z') { @@ -5889,6 +5934,12 @@ namespace chrono { template bool _Custom_write( basic_ostream<_CharT>& _Os, const _Chrono_spec<_CharT>& _Spec, const tm& _Time, const _Ty& _Val) { + if constexpr (is_same_v<_Ty, local_info>) { + if (_Val.result != local_info::unique) { + _THROW(format_error("Cannot print non-unique local_info")); + } + } + const auto _Year = _Time.tm_year + 1900; const auto _Month = _Time.tm_mon + 1; const bool _Has_modifier = _Spec._Modifier != '\0'; @@ -5996,15 +6047,34 @@ namespace chrono { _Write_seconds(_Os, _Val); return true; case 'Z': - _Os << _Time_zone_abbreviation; + if constexpr (is_same_v<_Ty, sys_info>) { + _Os << _Widen_string<_CharT>(_Val.abbrev); + } else if constexpr (is_same_v<_Ty, local_info>) { + _Os << _Widen_string<_CharT>(_Val.first.abbrev); + } else { + _Os << _Time_zone_abbreviation; + } return true; case 'z': - _Os << _STATICALLY_WIDEN(_CharT, "+00"); - if (_Has_modifier) { - _Os << _CharT{':'}; + { + hh_mm_ss _Offset; + + if constexpr (is_same_v<_Ty, sys_info>) { + _Offset = hh_mm_ss{_Val.offset}; + } else if constexpr (is_same_v<_Ty, local_info>) { + _Offset = hh_mm_ss{_Val.first.offset}; + } else { + _Offset = hh_mm_ss{}; + } + + const auto _Sign = _Offset.is_negative() ? _CharT{'-'} : _CharT{'+'}; + const auto _Separator = + _Has_modifier ? _STATICALLY_WIDEN(_CharT, ":") : _STATICALLY_WIDEN(_CharT, ""); + + _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{}{:02}{}{:02}"), _Sign, _Offset.hours().count(), + _Separator, _Offset.minutes().count()); + return true; } - _Os << _STATICALLY_WIDEN(_CharT, "00"); - return true; default: return false; } @@ -6099,6 +6169,14 @@ template struct formatter<_CHRONO hh_mm_ss<_CHRONO duration<_Rep, _Period>>, _CharT> : _Fill_tm_formatter<_CHRONO hh_mm_ss<_CHRONO duration<_Rep, _Period>>, _CharT> {}; +template +struct formatter<_CHRONO sys_info, _CharT> // + : _Fill_tm_formatter<_CHRONO sys_info, _CharT> {}; + +template +struct formatter<_CHRONO local_info, _CharT> // + : _Fill_tm_formatter<_CHRONO local_info, _CharT> {}; + template struct formatter<_CHRONO sys_time<_Duration>, _CharT> { auto parse(basic_format_parse_context<_CharT>& _Parse_ctx) { diff --git a/stl/inc/yvals_core.h b/stl/inc/yvals_core.h index 5a920d1640..74ea3106ef 100644 --- a/stl/inc/yvals_core.h +++ b/stl/inc/yvals_core.h @@ -140,7 +140,6 @@ // P0325R4 to_array() // P0339R6 polymorphic_allocator<> // P0355R7 Calendars And Time Zones -// (partially implemented) // P0356R5 bind_front() // P0357R3 Supporting Incomplete Types In reference_wrapper // P0408R7 Efficient Access To basic_stringbuf's Buffer @@ -1172,12 +1171,6 @@ #define __cpp_lib_variant 201606L #endif // _HAS_CXX17 -#if _HAS_CXX17 -#define __cpp_lib_chrono 201611L // P0505R0 constexpr For (Again) -#else // _HAS_CXX17 -#define __cpp_lib_chrono 201510L // P0092R1 floor(), ceil(), round(), abs() -#endif // _HAS_CXX17 - // C++20 #define __cpp_lib_atomic_value_initialization 201911L @@ -1303,6 +1296,14 @@ #define __cpp_lib_array_constexpr 201803L #endif // _HAS_CXX17 +#if _HAS_CXX20 && defined(__cpp_lib_concepts) // TRANSITION, GH-395 +#define __cpp_lib_chrono 201907L // P1466R3 Miscellaneous Minor Fixes For +#elif _HAS_CXX17 +#define __cpp_lib_chrono 201611L // P0505R0 constexpr For (Again) +#else // _HAS_CXX17 +#define __cpp_lib_chrono 201510L // P0092R1 floor(), ceil(), round(), abs() +#endif // _HAS_CXX17 + #if _HAS_CXX20 #define __cpp_lib_shared_ptr_arrays 201707L // P0674R1 make_shared() For Arrays #else // _HAS_CXX20 diff --git a/tests/libcxx/expected_results.txt b/tests/libcxx/expected_results.txt index 67bc234887..8cd4a768c6 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -640,6 +640,9 @@ std/language.support/support.limits/support.limits.general/algorithm.version.pas std/language.support/support.limits/support.limits.general/functional.version.pass.cpp FAIL std/language.support/support.limits/support.limits.general/iterator.version.pass.cpp FAIL +# Test expects __cpp_lib_chrono to have the old value 201611L for P0505R0; we define the C++20 value 201907L for P1466R3. +std/language.support/support.limits/support.limits.general/chrono.version.pass.cpp FAIL + # We unconditionally define __cpp_lib_addressof_constexpr; test error says it # "should not be defined when TEST_HAS_BUILTIN(__builtin_addressof) || TEST_GCC_VER >= 700 is not defined!" std/language.support/support.limits/support.limits.general/memory.version.pass.cpp FAIL diff --git a/tests/libcxx/skipped_tests.txt b/tests/libcxx/skipped_tests.txt index fedbf725d2..2eeaaaee2a 100644 --- a/tests/libcxx/skipped_tests.txt +++ b/tests/libcxx/skipped_tests.txt @@ -640,6 +640,9 @@ language.support\support.limits\support.limits.general\algorithm.version.pass.cp language.support\support.limits\support.limits.general\functional.version.pass.cpp language.support\support.limits\support.limits.general\iterator.version.pass.cpp +# Test expects __cpp_lib_chrono to have the old value 201611L for P0505R0; we define the C++20 value 201907L for P1466R3. +language.support\support.limits\support.limits.general\chrono.version.pass.cpp + # We unconditionally define __cpp_lib_addressof_constexpr; test error says it # "should not be defined when TEST_HAS_BUILTIN(__builtin_addressof) || TEST_GCC_VER >= 700 is not defined!" language.support\support.limits\support.limits.general\memory.version.pass.cpp diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index 311cb96ab7..333c9b71c9 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -13,6 +13,8 @@ #include #include +#include + using namespace std; using namespace chrono; @@ -212,9 +214,9 @@ void empty_braces_helper(const Arg& val, const CharT* const expected) { template constexpr void print(Str str) { if constexpr (is_same_v) { - cout << "res: " << str << "\n"; + cout << str << "\n"; } else { - wcout << "res: " << str << "\n"; + wcout << str << "\n"; } } @@ -611,6 +613,107 @@ void test_exception_classes() { } } +template +void test_information_classes() { + const tzdb& database = get_tzdb(); + + const time_zone* const sydney_tz = database.locate_zone(Sydney::Tz_name); + assert(sydney_tz != nullptr); + + const time_zone* const la_tz = database.locate_zone(LA::Tz_name); + assert(la_tz != nullptr); + + const sys_info sys1 = sydney_tz->get_info(Sydney::Std_1.begin() + days{1}); + const sys_info sys2 = sydney_tz->get_info(Sydney::Day_2.begin() + days{1}); + const sys_info sys3 = la_tz->get_info(LA::Std_1.begin() + days{1}); + const sys_info sys4 = la_tz->get_info(LA::Day_2.begin() + days{1}); + + const local_info loc1 = sydney_tz->get_info(Sydney::Std_1.local_begin() + days{1}); + const local_info loc2 = sydney_tz->get_info(Sydney::Day_2.local_begin() + days{1}); + const local_info loc3 = la_tz->get_info(LA::Std_1.local_begin() + days{1}); + const local_info loc4 = la_tz->get_info(LA::Day_2.local_begin() + days{1}); + + const local_info ambiguous1 = sydney_tz->get_info(Sydney::Std_1.local_begin()); + const local_info ambiguous2 = la_tz->get_info(LA::Std_1.local_begin()); + + const local_info nonexistent1 = sydney_tz->get_info(Sydney::Std_1.local_end()); + const local_info nonexistent2 = la_tz->get_info(LA::Std_1.local_end()); + + empty_braces_helper(sys1, STR("begin: 2020-04-04 16:00:00, end: 2020-10-03 16:00:00, " + "offset: 36000s, save: 0min, abbrev: GMT+10")); + empty_braces_helper(sys2, STR("begin: 2020-10-03 16:00:00, end: 2021-04-03 16:00:00, " + "offset: 39600s, save: 60min, abbrev: GMT+11")); + empty_braces_helper(sys3, STR("begin: 2020-11-01 09:00:00, end: 2021-03-14 10:00:00, " + "offset: -28800s, save: 0min, abbrev: PST")); + empty_braces_helper(sys4, STR("begin: 2021-03-14 10:00:00, end: 2021-11-07 09:00:00, " + "offset: -25200s, save: 60min, abbrev: PDT")); + empty_braces_helper(loc1, STR("result: unique, " + "first: (begin: 2020-04-04 16:00:00, end: 2020-10-03 16:00:00, " + "offset: 36000s, save: 0min, abbrev: GMT+10)")); + empty_braces_helper(loc2, STR("result: unique, " + "first: (begin: 2020-10-03 16:00:00, end: 2021-04-03 16:00:00, " + "offset: 39600s, save: 60min, abbrev: GMT+11)")); + empty_braces_helper(loc3, STR("result: unique, " + "first: (begin: 2020-11-01 09:00:00, end: 2021-03-14 10:00:00, " + "offset: -28800s, save: 0min, abbrev: PST)")); + empty_braces_helper(loc4, STR("result: unique, " + "first: (begin: 2021-03-14 10:00:00, end: 2021-11-07 09:00:00, " + "offset: -25200s, save: 60min, abbrev: PDT)")); + empty_braces_helper(ambiguous1, STR("result: ambiguous, " + "first: (begin: 2019-10-05 16:00:00, end: 2020-04-04 16:00:00, " + "offset: 39600s, save: 60min, abbrev: GMT+11), " + "second: (begin: 2020-04-04 16:00:00, end: 2020-10-03 16:00:00, " + "offset: 36000s, save: 0min, abbrev: GMT+10)")); + empty_braces_helper(ambiguous2, STR("result: ambiguous, " + "first: (begin: 2020-03-08 10:00:00, end: 2020-11-01 09:00:00, " + "offset: -25200s, save: 60min, abbrev: PDT), " + "second: (begin: 2020-11-01 09:00:00, end: 2021-03-14 10:00:00, " + "offset: -28800s, save: 0min, abbrev: PST)")); + empty_braces_helper(nonexistent1, STR("result: nonexistent, " + "first: (begin: 2020-04-04 16:00:00, end: 2020-10-03 16:00:00, " + "offset: 36000s, save: 0min, abbrev: GMT+10), " + "second: (begin: 2020-10-03 16:00:00, end: 2021-04-03 16:00:00, " + "offset: 39600s, save: 60min, abbrev: GMT+11)")); + empty_braces_helper(nonexistent2, STR("result: nonexistent, " + "first: (begin: 2020-11-01 09:00:00, end: 2021-03-14 10:00:00, " + "offset: -28800s, save: 0min, abbrev: PST), " + "second: (begin: 2021-03-14 10:00:00, end: 2021-11-07 09:00:00, " + "offset: -25200s, save: 60min, abbrev: PDT)")); + + assert(format(STR("{:%z %Ez %Oz %Z}"), sys1) == STR("+1000 +10:00 +10:00 GMT+10")); + assert(format(STR("{:%z %Ez %Oz %Z}"), sys2) == STR("+1100 +11:00 +11:00 GMT+11")); + assert(format(STR("{:%z %Ez %Oz %Z}"), sys3) == STR("-0800 -08:00 -08:00 PST")); + assert(format(STR("{:%z %Ez %Oz %Z}"), sys4) == STR("-0700 -07:00 -07:00 PDT")); + + assert(format(STR("{:%z %Ez %Oz %Z}"), loc1) == STR("+1000 +10:00 +10:00 GMT+10")); + assert(format(STR("{:%z %Ez %Oz %Z}"), loc2) == STR("+1100 +11:00 +11:00 GMT+11")); + assert(format(STR("{:%z %Ez %Oz %Z}"), loc3) == STR("-0800 -08:00 -08:00 PST")); + assert(format(STR("{:%z %Ez %Oz %Z}"), loc4) == STR("-0700 -07:00 -07:00 PDT")); + + throw_helper(STR("{:%z}"), ambiguous1); + throw_helper(STR("{:%z}"), ambiguous2); + throw_helper(STR("{:%z}"), nonexistent1); + throw_helper(STR("{:%z}"), nonexistent2); + + throw_helper(STR("{:%Z}"), ambiguous1); + throw_helper(STR("{:%Z}"), ambiguous2); + throw_helper(STR("{:%Z}"), nonexistent1); + throw_helper(STR("{:%Z}"), nonexistent2); + + // Additionally test zero and half-hour offsets. + const time_zone* const utc_tz = database.locate_zone("Etc/UTC"sv); + assert(utc_tz != nullptr); + + const time_zone* const kolkata_tz = database.locate_zone("Asia/Kolkata"sv); + assert(kolkata_tz != nullptr); + + const sys_info sys5 = utc_tz->get_info(sys_days{2021y / January / 1}); + const sys_info sys6 = kolkata_tz->get_info(sys_days{2021y / January / 1}); + + assert(format(STR("{:%z %Ez %Oz}"), sys5) == STR("+0000 +00:00 +00:00")); + assert(format(STR("{:%z %Ez %Oz}"), sys6) == STR("+0530 +05:30 +05:30")); +} + int main() { test_parse_conversion_spec(); test_parse_conversion_spec(); @@ -673,4 +776,7 @@ int main() { test_hh_mm_ss_formatter(); test_exception_classes(); + + test_information_classes(); + test_information_classes(); } diff --git a/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp b/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp index 73bf5dfcf8..3bf79bc606 100644 --- a/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp +++ b/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp @@ -325,6 +325,12 @@ STATIC_ASSERT(__cpp_lib_char8_t == 201907L); #ifndef __cpp_lib_chrono #error __cpp_lib_chrono is not defined +#elif _HAS_CXX20 && defined(__cpp_lib_concepts) // TRANSITION, GH-395 +#if __cpp_lib_chrono != 201907L +#error __cpp_lib_chrono is not 201907L +#else +STATIC_ASSERT(__cpp_lib_chrono == 201907L); +#endif #elif _HAS_CXX17 #if __cpp_lib_chrono != 201611L #error __cpp_lib_chrono is not 201611L From d69d7baa484fb74a8cf887bf4a361d1ee9596989 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 20 Apr 2021 04:50:07 -0700 Subject: [PATCH 31/41] Add local_time_format() and zoned_time. (#1863) --- stl/inc/chrono | 63 +++++++++++++++++++ .../test.cpp | 56 ++++++++++++++--- 2 files changed, 109 insertions(+), 10 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index ece4fc6268..1e2f684e42 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5446,6 +5446,19 @@ _NODISCARD constexpr const _CharT* _Parse_chrono_format_specs( } namespace chrono { + template + struct _Local_time_format_t { + local_time<_Duration> _Time; + const string* _Abbrev = nullptr; + const seconds* _Offset_sec = nullptr; + }; + + template + _NODISCARD _Local_time_format_t<_Duration> local_time_format(const local_time<_Duration> _Time, + const string* const _Abbrev = nullptr, const seconds* const _Offset_sec = nullptr) { + return {_Time, _Abbrev, _Offset_sec}; + } + // Replacement for %S, as put_time does not honor writing fractional seconds. template void _Write_seconds(basic_ostream<_CharT, _Traits>&, const _Ty&) { @@ -5474,6 +5487,11 @@ namespace chrono { _Write_seconds(_Os, hh_mm_ss{_Val - _Dp}); } + template + void _Write_seconds(basic_ostream<_CharT, _Traits>& _Os, const _Local_time_format_t<_Duration>& _Val) { + _Write_seconds(_Os, _Val._Time); + } + template void _Write_seconds(basic_ostream<_CharT, _Traits>& _Os, const duration<_Rep, _Period>& _Val) { const auto _Dp = _CHRONO duration_cast(_Val); @@ -5493,6 +5511,8 @@ namespace chrono { if constexpr (_Is_specialization_v<_Ty, duration>) { const auto _Dp = _CHRONO duration_cast(_Val); return _Fill_tm(hh_mm_ss{_Val - _Dp}); + } else if constexpr (_Is_specialization_v<_Ty, _Local_time_format_t>) { + return _Fill_tm(_Val._Time); } else if constexpr (is_same_v<_Ty, day>) { _Day = static_cast(_Val); } else if constexpr (is_same_v<_Ty, month>) { @@ -5738,6 +5758,21 @@ namespace chrono { return _Os << sys_time<_Duration>{_Val.time_since_epoch()}; } + template + basic_ostream<_CharT, _Traits>& operator<<( + basic_ostream<_CharT, _Traits>& _Os, const _Local_time_format_t<_Duration>& _Val) { + // Doesn't appear in the Standard, but allowed by N4885 [global.functions]/2. + // Implements N4885 [time.zone.zonedtime.nonmembers]/2 for zoned_time. + return _Os << _STD format(_Os.getloc(), _STATICALLY_WIDEN(_CharT, "{:%F %T %Z}"), _Val); + } + + template + basic_ostream<_CharT, _Traits>& operator<<( + basic_ostream<_CharT, _Traits>& _Os, const zoned_time<_Duration, _TimeZonePtr>& _Val) { + const auto _Info = _Val.get_info(); + return _Os << _Local_time_format_t<_Duration>{_Val.get_local_time(), &_Info.abbrev}; + } + template struct _Chrono_formatter { _Chrono_formatter() = default; @@ -5868,6 +5903,8 @@ namespace chrono { } return _Type == 'c' || _Type == 'x' || _Type == 'X' || _Is_valid_type(_Type) || _Is_valid_type>(_Type); + } else if constexpr (_Is_specialization_v<_Ty, _Local_time_format_t>) { + return _Type == 'z' || _Type == 'Z' || _Is_valid_type(_Type); } else { static_assert(_Always_false<_Ty>, "should be unreachable"); } @@ -6051,6 +6088,11 @@ namespace chrono { _Os << _Widen_string<_CharT>(_Val.abbrev); } else if constexpr (is_same_v<_Ty, local_info>) { _Os << _Widen_string<_CharT>(_Val.first.abbrev); + } else if constexpr (_Is_specialization_v<_Ty, _Local_time_format_t>) { + if (_Val._Abbrev == nullptr) { + _THROW(format_error("Cannot print local-time-format-t with null abbrev.")); + } + _Os << _Widen_string<_CharT>(*_Val._Abbrev); } else { _Os << _Time_zone_abbreviation; } @@ -6063,6 +6105,11 @@ namespace chrono { _Offset = hh_mm_ss{_Val.offset}; } else if constexpr (is_same_v<_Ty, local_info>) { _Offset = hh_mm_ss{_Val.first.offset}; + } else if constexpr (_Is_specialization_v<_Ty, _Local_time_format_t>) { + if (_Val._Offset_sec == nullptr) { + _THROW(format_error("Cannot print local-time-format-t with null offset_sec.")); + } + _Offset = hh_mm_ss{*_Val._Offset_sec}; } else { _Offset = hh_mm_ss{}; } @@ -6266,6 +6313,22 @@ template struct formatter<_CHRONO local_time<_Duration>, _CharT> // : _Fill_tm_formatter<_CHRONO local_time<_Duration>, _CharT> {}; +template +struct formatter<_CHRONO _Local_time_format_t<_Duration>, _CharT> + : _Fill_tm_formatter<_CHRONO _Local_time_format_t<_Duration>, _CharT> {}; + +template +struct formatter<_CHRONO zoned_time<_Duration, _TimeZonePtr>, _CharT> + : formatter<_CHRONO _Local_time_format_t<_Duration>, _CharT> { + + template + auto format(const _CHRONO zoned_time<_Duration, _TimeZonePtr>& _Val, _FormatContext& _FormatCtx) { + using _Mybase = formatter<_CHRONO _Local_time_format_t<_Duration>, _CharT>; + const auto _Info = _Val.get_info(); + return _Mybase::format({_Val.get_local_time(), &_Info.abbrev, &_Info.offset}, _FormatCtx); + } +}; + namespace chrono { template _NODISCARD string nonexistent_local_time::_Make_string(const local_time<_Duration>& _Tp, const local_info& _Info) { diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index 333c9b71c9..2b8f4af1b9 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -210,16 +210,6 @@ void empty_braces_helper(const Arg& val, const CharT* const expected) { assert(stream); } -// FIXME: TEMPORARY CODE FOR WRITING TESTS, REMOVE BEFORE MERGING -template -constexpr void print(Str str) { - if constexpr (is_same_v) { - cout << str << "\n"; - } else { - wcout << str << "\n"; - } -} - template void test_duration_formatter() { empty_braces_helper(seconds{5}, STR("5s")); @@ -639,6 +629,9 @@ void test_information_classes() { const local_info nonexistent1 = sydney_tz->get_info(Sydney::Std_1.local_end()); const local_info nonexistent2 = la_tz->get_info(LA::Std_1.local_end()); + // N4885 [time.zone.info.sys]/7: "Effects: Streams out the sys_info object r in an unspecified format." + // N4885 [time.zone.info.local]/3: "Effects: Streams out the local_info object r in an unspecified format." + empty_braces_helper(sys1, STR("begin: 2020-04-04 16:00:00, end: 2020-10-03 16:00:00, " "offset: 36000s, save: 0min, abbrev: GMT+10")); empty_braces_helper(sys2, STR("begin: 2020-10-03 16:00:00, end: 2021-04-03 16:00:00, " @@ -714,6 +707,43 @@ void test_information_classes() { assert(format(STR("{:%z %Ez %Oz}"), sys6) == STR("+0530 +05:30 +05:30")); } +template +void test_local_time_format_formatter() { + constexpr local_seconds t{local_days{2021y / April / 19} + 1h + 2min + 3s}; + const string abbrev{"Meow"}; + constexpr seconds offset{17h + 29min}; + + const auto ltf = local_time_format(t, &abbrev, &offset); + + throw_helper(STR("{:%Z}"), local_time_format(t, nullptr, &offset)); + throw_helper(STR("{:%z}"), local_time_format(t, &abbrev, nullptr)); + + assert(format(STR("{:%Z %z %Oz %Ez}"), ltf) == STR("Meow +1729 +17:29 +17:29")); + + // Doesn't appear in the Standard, but allowed by N4885 [global.functions]/2. + // Implements N4885 [time.zone.zonedtime.nonmembers]/2 for zoned_time. + empty_braces_helper(ltf, STR("2021-04-19 01:02:03 Meow")); + + assert(format(STR("{:%c, %x, %X}"), ltf) == STR("04/19/21 01:02:03, 04/19/21, 01:02:03")); + assert(format(STR("{:%D %F, %Y %C %y, %b %B %h %m, %d %e, %a %A %u %w}"), ltf) + == STR("04/19/21 2021-04-19, 2021 20 21, Apr April Apr 04, 19 19, Mon Monday 1 1")); + assert(format(STR("{:%H %I %M %S %r %R %T %p}"), ltf) == STR("01 01 02 03 01:02:03 01:02 01:02:03 AM")); +} + +template +void test_zoned_time_formatter() { + constexpr sys_seconds t{sys_days{2021y / April / 19} + 15h + 16min + 17s}; + + const zoned_time zt{LA::Tz_name, t}; + + empty_braces_helper(zt, STR("2021-04-19 08:16:17 PDT")); + + assert(format(STR("{:%c, %x, %X}"), zt) == STR("04/19/21 08:16:17, 04/19/21, 08:16:17")); + assert(format(STR("{:%D %F, %Y %C %y, %b %B %h %m, %d %e, %a %A %u %w}"), zt) + == STR("04/19/21 2021-04-19, 2021 20 21, Apr April Apr 04, 19 19, Mon Monday 1 1")); + assert(format(STR("{:%H %I %M %S %r %R %T %p}"), zt) == STR("08 08 16 17 08:16:17 08:16 08:16:17 AM")); +} + int main() { test_parse_conversion_spec(); test_parse_conversion_spec(); @@ -779,4 +809,10 @@ int main() { test_information_classes(); test_information_classes(); + + test_local_time_format_formatter(); + test_local_time_format_formatter(); + + test_zoned_time_formatter(); + test_zoned_time_formatter(); } From 65ad8a715149fcf30869f3dea81fc3d1455fa753 Mon Sep 17 00:00:00 2001 From: Elnar Dakeshov <55715127+eldakesh-ms@users.noreply.github.com> Date: Tue, 20 Apr 2021 13:42:48 -0700 Subject: [PATCH 32/41] : Add %j support and expand %aAuw (#1864) * : Add %j support and expand %aAuw Adds %j support to all the classes that can benefit from it. This means all year_month_day* but and month_last (but only for January). Changes %aAuw to throw on invalid weekdays, but still work if the weekday is explicitly ok (so a bad year_month_day will always throw but a bad year_month_weekday can still print the weekday if that portion is ok). * Add month_day support * Make put_time strings uniformly --- stl/inc/chrono | 97 +++++++++++++------ .../test.cpp | 35 +++++++ 2 files changed, 104 insertions(+), 28 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 1e2f684e42..5f3d3b9aaa 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5503,6 +5503,7 @@ namespace chrono { unsigned int _Day = 0; unsigned int _Month = 0; int _Year = 0; + int _Yearday = 0; int _Weekday = 0; int _Hours = 0; int _Minutes = 0; @@ -5526,9 +5527,17 @@ namespace chrono { } else if constexpr (is_same_v<_Ty, month_day>) { _Day = static_cast(_Val.day()); _Month = static_cast(_Val.month()); + if (_Val.month() == January) { + _Yearday = static_cast(_Day) - 1; + } else if (_Val.month() == February) { + _Yearday = 31 + static_cast(_Day) - 1; + } } else if constexpr (is_same_v<_Ty, month_day_last>) { _Month = static_cast(_Val.month()); _Day = static_cast(_Last_day_table[(_Month - 1) & 0xF]); + if (_Val.month() == January) { + _Yearday = 30; + } } else if constexpr (is_same_v<_Ty, month_weekday>) { _Month = static_cast(_Val.month()); _Weekday = static_cast(_Val.weekday_indexed().weekday().c_encoding()); @@ -5538,21 +5547,19 @@ namespace chrono { } else if constexpr (is_same_v<_Ty, year_month>) { _Month = static_cast(_Val.month()); _Year = static_cast(_Val.year()); - } else if constexpr (is_same_v<_Ty, year_month_day>) { - _Day = static_cast(_Val.day()); - _Month = static_cast(_Val.month()); - _Year = static_cast(_Val.year()); - _Weekday = _Val._Calculate_weekday(); - } else if constexpr (is_same_v<_Ty, year_month_day_last>) { - _Day = static_cast(_Val.day()); - _Month = static_cast(_Val.month()); - _Year = static_cast(_Val.year()); - _Weekday = year_month_day{_Val}._Calculate_weekday(); + } else if constexpr (_Is_any_of_v<_Ty, year_month_day, year_month_day_last>) { + _Day = static_cast(_Val.day()); + _Month = static_cast(_Val.month()); + _Year = static_cast(_Val.year()); + if (_Val.ok()) { + const year_month_day& _Ymd = _Val; + _Weekday = _Ymd._Calculate_weekday(); + _Yearday = (static_cast(_Val) - static_cast(_Val.year() / January / 1)).count(); + } } else if constexpr (_Is_any_of_v<_Ty, year_month_weekday, year_month_weekday_last>) { - _Day = static_cast(year_month_day{_Val}.day()); - _Month = static_cast(_Val.month()); - _Year = static_cast(_Val.year()); - _Weekday = static_cast(_Val.weekday().c_encoding()); + auto _Tm = _Fill_tm(year_month_day{_Val}); + _Tm.tm_wday = static_cast(_Val.weekday().c_encoding()); + return _Tm; } else if constexpr (_Is_specialization_v<_Ty, hh_mm_ss>) { _Hours = _Val.hours().count(); _Minutes = _Val.minutes().count(); @@ -5578,6 +5585,7 @@ namespace chrono { _Time.tm_mday = static_cast(_Day); _Time.tm_mon = static_cast(_Month) - 1; _Time.tm_year = _Year - 1900; + _Time.tm_yday = _Yearday; _Time.tm_wday = _Weekday; return _Time; } @@ -5881,15 +5889,15 @@ namespace chrono { } else if constexpr (_Is_any_of_v<_Ty, weekday, weekday_indexed, weekday_last>) { return _Type == 'a' || _Type == 'A' || _Type == 'u' || _Type == 'w'; } else if constexpr (_Is_any_of_v<_Ty, month_day, month_day_last>) { - return _Is_valid_type(_Type) || _Is_valid_type(_Type); + return _Type == 'j' || _Is_valid_type(_Type) || _Is_valid_type(_Type); } else if constexpr (_Is_any_of_v<_Ty, month_weekday, month_weekday_last>) { return _Is_valid_type(_Type) || _Is_valid_type(_Type); } else if constexpr (is_same_v<_Ty, year_month>) { return _Is_valid_type(_Type) || _Is_valid_type(_Type); } else if constexpr (_Is_any_of_v<_Ty, year_month_day, year_month_day_last, year_month_weekday, year_month_weekday_last>) { - return _Type == 'D' || _Type == 'F' || _Is_valid_type(_Type) || _Is_valid_type(_Type) - || _Is_valid_type(_Type) || _Is_valid_type(_Type); + return _Type == 'D' || _Type == 'F' || _Type == 'j' || _Is_valid_type(_Type) + || _Is_valid_type(_Type) || _Is_valid_type(_Type) || _Is_valid_type(_Type); } else if constexpr (_Is_specialization_v<_Ty, hh_mm_ss>) { return _Type == 'H' || _Type == 'I' || _Type == 'M' || _Type == 'S' || _Type == 'r' || _Type == 'R' || _Type == 'T' || _Type == 'p'; @@ -5947,16 +5955,7 @@ namespace chrono { } } - _CharT _Fmt_str[4]; - size_t _Next_idx = 0; - _Fmt_str[_Next_idx++] = _CharT{'%'}; - if (_Spec._Modifier != '\0') { - _Fmt_str[_Next_idx++] = static_cast<_CharT>(_Spec._Modifier); - } - _Fmt_str[_Next_idx++] = static_cast<_CharT>(_Spec._Type); - _Fmt_str[_Next_idx] = _CharT{'\0'}; - - _Stream << _STD put_time<_CharT>(&_Time, _Fmt_str); + _Stream << _STD put_time<_CharT>(&_Time, _Fmt_string(_Spec).data()); } } @@ -5981,6 +5980,22 @@ namespace chrono { const auto _Month = _Time.tm_mon + 1; const bool _Has_modifier = _Spec._Modifier != '\0'; switch (_Spec._Type) { + case 'a': + case 'A': + case 'u': + case 'w': + if constexpr (_Is_any_of_v<_Ty, year_month_weekday, year_month_weekday_last>) { + if (!_Val.weekday().ok()) { + _THROW(format_error("Cannot print invalid weekday")); + } + _Os << _STD put_time(&_Time, _Fmt_string(_Spec).data()); + return true; + } else if constexpr (_Has_ok<_Ty>) { + if (!_Val.ok()) { + _THROW(format_error("Cannot print invalid weekday")); + } + } + return false; case 'd': case 'e': // Most months have a proper last day, but February depends on the year. @@ -6002,8 +6017,22 @@ namespace chrono { case 'j': if constexpr (_Is_specialization_v<_Ty, duration>) { _Os << _STD abs(_CHRONO duration_cast(_Val).count()); + return true; + } else if constexpr (is_same_v<_Ty, month_day>) { + if (_Val.month() > February) { + _THROW(format_error("The day of year for a month_day past February is ambiguous.")); + } + } else if constexpr (is_same_v<_Ty, month_day_last>) { + if (_Val.month() >= February) { + _THROW(format_error("The day of year for a month_day_last other than January is ambiguous")); + } } - return true; + if constexpr (_Has_ok<_Ty>) { + if (!_Val.ok()) { + _THROW(format_error("Cannot print invalid day of year")); + } + } + return false; case 'q': if constexpr (_Is_specialization_v<_Ty, duration>) { _Write_unit_suffix(_Os); @@ -6127,6 +6156,18 @@ namespace chrono { } } + _NODISCARD array<_CharT, 4> _Fmt_string(const _Chrono_spec<_CharT>& _Spec) { + array<_CharT, 4> _Fmt_str; + size_t _Next_idx = 0; + _Fmt_str[_Next_idx++] = _CharT{'%'}; + if (_Spec._Modifier != '\0') { + _Fmt_str[_Next_idx++] = static_cast<_CharT>(_Spec._Modifier); + } + _Fmt_str[_Next_idx++] = static_cast<_CharT>(_Spec._Type); + _Fmt_str[_Next_idx] = _CharT{'\0'}; + return _Fmt_str; + } + _Chrono_format_specs<_CharT> _Specs{}; bool _No_chrono_specs = false; basic_string_view<_CharT> _Time_zone_abbreviation{}; diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index 2b8f4af1b9..520fe496af 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -418,6 +418,12 @@ void test_month_day_formatter() { assert(format(STR("{:%B %d}"), June / 17) == STR("June 17")); throw_helper(STR("{:%Y}"), June / 17); + + assert(format(STR("{:%j}"), January / 5) == STR("005")); + assert(format(STR("{:%j}"), February / 5) == STR("036")); + assert(format(STR("{:%j}"), February / 28) == STR("059")); + assert(format(STR("{:%j}"), February / 29) == STR("060")); + throw_helper(STR("{:%j}"), March / 1); } template @@ -427,6 +433,10 @@ void test_month_day_last_formatter() { assert(format(STR("{:%B}"), June / last) == STR("June")); assert(format(STR("{:%d}"), June / last) == STR("30")); throw_helper(STR("{:%d}"), February / last); + + assert(format(STR("{:%j}"), January / last) == STR("031")); + throw_helper(STR("{:%j}"), February / last); + throw_helper(STR("{:%j}"), April / last); } template @@ -489,6 +499,12 @@ void test_year_month_day_formatter() { assert(format(STR("{:%F %D}"), invalid) == STR("1234-00-31 00/31/34")); assert(format(STR("{:%a %A}"), year_month_day{year{1900}, month{1}, day{4}}) == STR("Thu Thursday")); assert(format(STR("{:%u %w}"), year_month_day{year{1900}, month{1}, day{4}}) == STR("4 4")); + throw_helper(STR("{:%u}"), invalid); + + assert(format(STR("{:%j}"), 1900y / January / 4) == STR("004")); + assert(format(STR("{:%j}"), 1900y / May / 7) == STR("127")); + assert(format(STR("{:%j}"), 2000y / May / 7) == STR("128")); + throw_helper(STR("{:%j}"), invalid); } template @@ -506,6 +522,13 @@ void test_year_month_day_last_formatter() { constexpr auto fmt = STR("{:%D %F, %Y %C %y, %b %B %h %m, %d %e, %a %A %u %w}"); assert(format(fmt, ymdl1) == STR("04/30/21 2021-04-30, 2021 20 21, Apr April Apr 04, 30 30, Fri Friday 5 5")); assert(format(fmt, ymdl2) == STR("02/29/04 2004-02-29, 2004 20 04, Feb February Feb 02, 29 29, Sun Sunday 7 0")); + + throw_helper(STR("{:%u}"), invalid); + + assert(format(STR("{:%j}"), 1900y / January / last) == STR("031")); + assert(format(STR("{:%j}"), 1900y / February / last) == STR("059")); + assert(format(STR("{:%j}"), 2000y / February / last) == STR("060")); + throw_helper(STR("{:%j}"), year{1900} / month{13} / last); } template @@ -529,6 +552,12 @@ void test_year_month_weekday_formatter() { constexpr auto fmt = STR("{:%D %F, %Y %C %y, %b %B %h %m, %d %e, %a %A %u %w}"); assert(format(fmt, ymwd1) == STR("04/30/21 2021-04-30, 2021 20 21, Apr April Apr 04, 30 30, Fri Friday 5 5")); assert(format(fmt, ymwd2) == STR("02/29/04 2004-02-29, 2004 20 04, Feb February Feb 02, 29 29, Sun Sunday 7 0")); + + assert(format(STR("{:%u}"), invalid1) == STR("5")); + throw_helper(STR("{:%u}"), invalid2); + + assert(format(STR("{:%j}"), 1900y / January / Tuesday[2]) == STR("009")); + throw_helper(STR("{:%j}"), invalid1); } template @@ -550,6 +579,12 @@ void test_year_month_weekday_last_formatter() { constexpr auto fmt = STR("{:%D %F, %Y %C %y, %b %B %h %m, %d %e, %a %A %u %w}"); assert(format(fmt, ymwdl1) == STR("04/30/21 2021-04-30, 2021 20 21, Apr April Apr 04, 30 30, Fri Friday 5 5")); assert(format(fmt, ymwdl2) == STR("02/29/04 2004-02-29, 2004 20 04, Feb February Feb 02, 29 29, Sun Sunday 7 0")); + + assert(format(STR("{:%u}"), invalid2) == STR("5")); + throw_helper(STR("{:%u}"), invalid1); + + assert(format(STR("{:%j}"), 1900y / January / Tuesday[last]) == STR("030")); + throw_helper(STR("{:%j}"), invalid1); } template From d4882008e64321ad6d1d910e2c52aa6201b358b7 Mon Sep 17 00:00:00 2001 From: Elnar Dakeshov <55715127+eldakesh-ms@users.noreply.github.com> Date: Tue, 20 Apr 2021 14:58:27 -0700 Subject: [PATCH 33/41] ) { + // put_time uses _Strftime in order to bypass reference-counting that locale uses. This function + // takes the locale information by pointer, but the pointer (from _Gettnames) returns a copy. + // _Strftime delegates to other functions but eventually (for the C locale) has the %r specifier + // rewritten. It checks for the locale by comparing pointers, which do not compare equal as we have + // a copy of the pointer instead of the original. Therefore, we replace %r for the C locale + // ourselves. + if (_Os.getloc() == locale::classic()) { + _Os << _STD put_time(&_Time, _STATICALLY_WIDEN(_CharT, "%I:%M:%S %p")); + return true; + } + } + return false; case 'j': if constexpr (_Is_specialization_v<_Ty, duration>) { _Os << _STD abs(_CHRONO duration_cast(_Val).count()); diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index 520fe496af..0305df6786 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -595,10 +595,10 @@ void test_hh_mm_ss_formatter() { empty_braces_helper(hh_mm_ss{65745s}, STR("18:15:45")); assert(format(STR("{:%H %I %M %S %r %R %T %p}"), hh_mm_ss{13h + 14min + 15351ms}) - == STR("13 01 14 15.351 13:14:15 13:14 13:14:15.351 PM")); + == STR("13 01 14 15.351 01:14:15 PM 13:14 13:14:15.351 PM")); assert(format(STR("{:%H %I %M %S %r %R %T %p}"), hh_mm_ss{-13h - 14min - 15351ms}) - == STR("-13 01 14 15.351 13:14:15 13:14 13:14:15.351 PM")); + == STR("-13 01 14 15.351 01:14:15 PM 13:14 13:14:15.351 PM")); throw_helper(STR("{}"), hh_mm_ss{24h}); throw_helper(STR("{}"), hh_mm_ss{-24h}); From cc2651ddacc6187339bad7f7f8c90806a0a657ff Mon Sep 17 00:00:00 2001 From: Elnar Dakeshov <55715127+eldakesh-ms@users.noreply.github.com> Date: Tue, 20 Apr 2021 16:28:22 -0700 Subject: [PATCH 34/41] : Fix hh_mm_ss subsecond formatting for floats (#1866) * : Fix hh_mm_ss subsecond formatting for floats Before, the same formatting string was used for floats and integrals. This meant that large floats were formatted using exponent notaion and small floats were not, and it also meant there was an extra period in a time, as the subseconds could be fractions of a subsecond (say .4 nanoseconds). Now if the subseconds are floats, we force fixed formatting to get the right number of leading zeroes and a precision of 0 to round off fractions of subseconds. * Floor subseconds --- stl/inc/chrono | 11 ++++++++--- .../test.cpp | 8 ++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index ef8d383e0a..0035ee7102 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5469,9 +5469,14 @@ namespace chrono { void _Write_seconds(basic_ostream<_CharT, _Traits>& _Os, const hh_mm_ss<_Duration>& _Val) { _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:02}"), _Val.seconds().count()); if constexpr (hh_mm_ss<_Duration>::fractional_width > 0) { - _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{0}{1:0{2}}"), - _STD use_facet>(_Os.getloc()).decimal_point(), _Val.subseconds().count(), - _Val.fractional_width); + _Os << _STD use_facet>(_Os.getloc()).decimal_point(); + if constexpr (treat_as_floating_point_v::precision::rep>) { + _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:0{}.0f}"), _STD floor(_Val.subseconds().count()), + _Val.fractional_width); + } else { + _Os << _STD format( + _STATICALLY_WIDEN(_CharT, "{:0{}}"), _Val.subseconds().count(), _Val.fractional_width); + } } } diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index 0305df6786..4cdaac2844 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -593,6 +593,14 @@ void test_hh_mm_ss_formatter() { empty_braces_helper(hh_mm_ss{4083007ms}, STR("01:08:03.007")); empty_braces_helper(hh_mm_ss{65745123ms}, STR("18:15:45.123")); empty_braces_helper(hh_mm_ss{65745s}, STR("18:15:45")); + empty_braces_helper(hh_mm_ss{0.1ns}, STR("00:00:00.000000000")); + empty_braces_helper(hh_mm_ss{1.45ns}, STR("00:00:00.000000001")); + empty_braces_helper(hh_mm_ss{1.56ns}, STR("00:00:00.000000001")); + empty_braces_helper(hh_mm_ss{1e+8ns}, STR("00:00:00.100000000")); + empty_braces_helper(hh_mm_ss{999'999.9us}, STR("00:00:00.999999")); + empty_braces_helper(hh_mm_ss{59'999'999.9us}, STR("00:00:59.999999")); + empty_braces_helper(hh_mm_ss{3'599'999'999.9us}, STR("00:59:59.999999")); + empty_braces_helper(hh_mm_ss{86'399'999'999.9us}, STR("23:59:59.999999")); assert(format(STR("{:%H %I %M %S %r %R %T %p}"), hh_mm_ss{13h + 14min + 15351ms}) == STR("13 01 14 15.351 01:14:15 PM 13:14 13:14:15.351 PM")); From cec735cc98b20befa11b59b1ac5e55f928302481 Mon Sep 17 00:00:00 2001 From: Elnar Dakeshov <55715127+eldakesh-ms@users.noreply.github.com> Date: Tue, 20 Apr 2021 20:48:15 -0700 Subject: [PATCH 35/41] : Fix utc_clock seconds formatting (#1868) Co-authored-by: MattStephanson <68978048+MattStephanson@users.noreply.github.com> Co-authored-by: Stephan T. Lavavej --- stl/inc/chrono | 36 ++++++++++++------- .../test.cpp | 28 +++++++++++++++ 2 files changed, 52 insertions(+), 12 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 0035ee7102..7e49b82e14 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5465,28 +5465,40 @@ namespace chrono { _STL_INTERNAL_CHECK(false); } - template - void _Write_seconds(basic_ostream<_CharT, _Traits>& _Os, const hh_mm_ss<_Duration>& _Val) { - _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:02}"), _Val.seconds().count()); - if constexpr (hh_mm_ss<_Duration>::fractional_width > 0) { + template + void _Write_fractional_seconds( + basic_ostream<_CharT, _Traits>& _Os, const seconds& _Seconds, const _Precision& _Subseconds) { + _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:02}"), _Seconds.count()); + if constexpr (_Fractional_width > 0) { _Os << _STD use_facet>(_Os.getloc()).decimal_point(); - if constexpr (treat_as_floating_point_v::precision::rep>) { - _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:0{}.0f}"), _STD floor(_Val.subseconds().count()), - _Val.fractional_width); - } else { + if constexpr (treat_as_floating_point_v) { _Os << _STD format( - _STATICALLY_WIDEN(_CharT, "{:0{}}"), _Val.subseconds().count(), _Val.fractional_width); + _STATICALLY_WIDEN(_CharT, "{:0{}.0f}"), _STD floor(_Subseconds.count()), _Fractional_width); + } else { + _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:0{}}"), _Subseconds.count(), _Fractional_width); } } } + template + void _Write_seconds(basic_ostream<_CharT, _Traits>& _Os, const hh_mm_ss<_Duration>& _Val) { + _Write_fractional_seconds::fractional_width>(_Os, _Val.seconds(), _Val.subseconds()); + } + template void _Write_seconds(basic_ostream<_CharT, _Traits>& _Os, const time_point<_Clock, _Duration>& _Val) { if constexpr (is_same_v<_Clock, utc_clock>) { - if (_CHRONO get_leap_second_info(_Val).is_leap_second) { - _Os << _STATICALLY_WIDEN(_CharT, "60"); - return; + const auto _Lsi = _CHRONO get_leap_second_info(_Val); + const auto _Dp = + _CHRONO floor(_Val - _Lsi.elapsed) + _Lsi.elapsed - seconds{_Lsi.is_leap_second ? 1 : 0}; + const hh_mm_ss _Hms{_Val - _Dp}; + constexpr auto _Fractional_width = decltype(_Hms)::fractional_width; + if (_Lsi.is_leap_second) { + _Write_fractional_seconds<_Fractional_width>(_Os, _Hms.seconds() + seconds{60}, _Hms.subseconds()); + } else { + _Write_fractional_seconds<_Fractional_width>(_Os, _Hms.seconds(), _Hms.subseconds()); } + return; } const auto _Dp = _CHRONO floor(_Val); _Write_seconds(_Os, hh_mm_ss{_Val - _Dp}); diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index 4cdaac2844..3c14b7c28f 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -250,6 +250,34 @@ void test_clock_formatter() { throw_helper(STR("{:%Z %z %Oz %Ez}"), local_seconds{}); assert(format(STR("{:%S}"), utc_clock::from_sys(get_tzdb().leap_seconds.front().date()) - 1s) == STR("60")); + assert(format(STR("{:%F %T}"), utc_clock::from_sys(get_tzdb().leap_seconds.front().date())) + == STR("1972-07-01 00:00:00")); + assert(format(STR("{:%F %T}"), utc_clock::from_sys(sys_days{January / 9 / 2014} + 12h + 35min + 34s)) + == STR("2014-01-09 12:35:34")); + assert(format(STR("{:%F %T}"), utc_clock::from_sys(get_tzdb().leap_seconds.front().date()) - 500ms) + == STR("1972-06-30 23:59:60.500")); + + // Test an ordinary day. + const auto utc_2021_05_04 = utc_clock::from_sys(sys_days{2021y / May / 4}); + + // This is both the last day of a leap year (366th day) and the day of a leap second insertion. + const auto utc_2016_12_31 = utc_clock::from_sys(sys_days{2016y / December / 31}); + + for (const auto& h : {0h, 1h, 7h, 22h, 23h}) { // Accelerate testing; 24 * 60 * 61 iterations would be a lot. + for (const auto& m : {0min, 1min, 7min, 58min, 59min}) { + for (const auto& s : {0s, 1s, 7s, 58s, 59s, 60s}) { + if (s != 60s) { + assert(format(STR("{:%F %T}"), utc_2021_05_04 + h + m + s) + == format(STR("2021-05-04 {:02}:{:02}:{:02}"), h.count(), m.count(), s.count())); + } + + if ((h == 23h && m == 59min) || s != 60s) { + assert(format(STR("{:%F %T}"), utc_2016_12_31 + h + m + s) + == format(STR("2016-12-31 {:02}:{:02}:{:02}"), h.count(), m.count(), s.count())); + } + } + } + } } template From e015c351b8c590b12ca8f3a90a43d29b9a7da3c1 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 21 Apr 2021 03:27:51 -0700 Subject: [PATCH 36/41] `` formatting: extend `%r`, add `%g %G %U %V %W` (#1869) * Various cleanups. * Use `else` with `if constexpr` to avoid dead code. * Some helpers can be `static`. * Add a newline between non-chained `if` statements. * Extend %r to all types (local_time_format, zoned_time). * Implement %g %G %U %V %W. --- stl/inc/chrono | 55 ++++++++++++------- .../test.cpp | 55 ++++++++++++++++++- 2 files changed, 88 insertions(+), 22 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 7e49b82e14..e8af360e74 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5498,10 +5498,10 @@ namespace chrono { } else { _Write_fractional_seconds<_Fractional_width>(_Os, _Hms.seconds(), _Hms.subseconds()); } - return; + } else { + const auto _Dp = _CHRONO floor(_Val); + _Write_seconds(_Os, hh_mm_ss{_Val - _Dp}); } - const auto _Dp = _CHRONO floor(_Val); - _Write_seconds(_Os, hh_mm_ss{_Val - _Dp}); } template @@ -5848,7 +5848,7 @@ namespace chrono { return _Res_iter; } - void _Check_modifier(const char _Type, const char _Modifier) { + static void _Check_modifier(const char _Type, const char _Modifier) { if (_Modifier == '\0') { return; } @@ -5894,7 +5894,7 @@ namespace chrono { } template - _NODISCARD constexpr bool _Is_valid_type(const char _Type) noexcept { + _NODISCARD static constexpr bool _Is_valid_type(const char _Type) noexcept { if constexpr (_Is_specialization_v<_Ty, duration>) { return _Type == 'j' || _Type == 'q' || _Type == 'Q' || _Is_valid_type>(_Type); } else if constexpr (is_same_v<_Ty, day>) { @@ -5910,11 +5910,12 @@ namespace chrono { } else if constexpr (_Is_any_of_v<_Ty, month_weekday, month_weekday_last>) { return _Is_valid_type(_Type) || _Is_valid_type(_Type); } else if constexpr (is_same_v<_Ty, year_month>) { - return _Is_valid_type(_Type) || _Is_valid_type(_Type); + return _Type == 'g' || _Type == 'G' || _Is_valid_type(_Type) || _Is_valid_type(_Type); } else if constexpr (_Is_any_of_v<_Ty, year_month_day, year_month_day_last, year_month_weekday, year_month_weekday_last>) { - return _Type == 'D' || _Type == 'F' || _Type == 'j' || _Is_valid_type(_Type) - || _Is_valid_type(_Type) || _Is_valid_type(_Type) || _Is_valid_type(_Type); + return _Type == 'D' || _Type == 'F' || _Type == 'g' || _Type == 'G' || _Type == 'j' || _Type == 'U' + || _Type == 'V' || _Type == 'W' || _Is_valid_type(_Type) || _Is_valid_type(_Type) + || _Is_valid_type(_Type) || _Is_valid_type(_Type); } else if constexpr (_Is_specialization_v<_Ty, hh_mm_ss>) { return _Type == 'H' || _Type == 'I' || _Type == 'M' || _Type == 'S' || _Type == 'r' || _Type == 'R' || _Type == 'T' || _Type == 'p'; @@ -6031,19 +6032,32 @@ namespace chrono { } _Os << _Time.tm_mday; return true; - case 'r': - if constexpr (_Is_specialization_v<_Ty, hh_mm_ss>) { - // put_time uses _Strftime in order to bypass reference-counting that locale uses. This function - // takes the locale information by pointer, but the pointer (from _Gettnames) returns a copy. - // _Strftime delegates to other functions but eventually (for the C locale) has the %r specifier - // rewritten. It checks for the locale by comparing pointers, which do not compare equal as we have - // a copy of the pointer instead of the original. Therefore, we replace %r for the C locale - // ourselves. - if (_Os.getloc() == locale::classic()) { - _Os << _STD put_time(&_Time, _STATICALLY_WIDEN(_CharT, "%I:%M:%S %p")); - return true; + case 'g': + case 'G': + if constexpr (is_same_v<_Ty, year_month>) { + if (_Val.month() == January || _Val.month() == December) { + _THROW(format_error( + "The ISO week-based year for a year_month of January or December is ambiguous.")); } + + const char _Gregorian_type = _Spec._Type == 'g' ? 'y' : 'Y'; + _Os << _STD put_time(&_Time, _Fmt_string({._Type = _Gregorian_type}).data()); + return true; } + + return false; + case 'r': + // put_time uses _Strftime in order to bypass reference-counting that locale uses. This function + // takes the locale information by pointer, but the pointer (from _Gettnames) returns a copy. + // _Strftime delegates to other functions but eventually (for the C locale) has the %r specifier + // rewritten. It checks for the locale by comparing pointers, which do not compare equal as we have + // a copy of the pointer instead of the original. Therefore, we replace %r for the C locale + // ourselves. + if (_Os.getloc() == locale::classic()) { + _Os << _STD put_time(&_Time, _STATICALLY_WIDEN(_CharT, "%I:%M:%S %p")); + return true; + } + return false; case 'j': if constexpr (_Is_specialization_v<_Ty, duration>) { @@ -6058,6 +6072,7 @@ namespace chrono { _THROW(format_error("The day of year for a month_day_last other than January is ambiguous")); } } + if constexpr (_Has_ok<_Ty>) { if (!_Val.ok()) { _THROW(format_error("Cannot print invalid day of year")); @@ -6187,7 +6202,7 @@ namespace chrono { } } - _NODISCARD array<_CharT, 4> _Fmt_string(const _Chrono_spec<_CharT>& _Spec) { + _NODISCARD static array<_CharT, 4> _Fmt_string(const _Chrono_spec<_CharT>& _Spec) { array<_CharT, 4> _Fmt_str; size_t _Next_idx = 0; _Fmt_str[_Next_idx++] = _CharT{'%'}; diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index 3c14b7c28f..6fd4b2117b 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -249,6 +249,24 @@ void test_clock_formatter() { assert(format(STR("{:%Z %z %Oz %Ez}"), file_time{}) == STR("UTC +0000 +00:00 +00:00")); throw_helper(STR("{:%Z %z %Oz %Ez}"), local_seconds{}); + assert(format(STR("{:%g %G %U %V %W}"), sys_days{2010y / January / 1}) == STR("09 2009 00 53 00")); + assert(format(STR("{:%g %G %U %V %W}"), sys_days{2010y / January / 2}) == STR("09 2009 00 53 00")); + assert(format(STR("{:%g %G %U %V %W}"), sys_days{2010y / January / 3}) == STR("09 2009 01 53 00")); + assert(format(STR("{:%g %G %U %V %W}"), sys_days{2010y / January / 4}) == STR("10 2010 01 01 01")); + assert(format(STR("{:%g %G %U %V %W}"), sys_days{2010y / January / 5}) == STR("10 2010 01 01 01")); + assert(format(STR("{:%g %G %U %V %W}"), sys_days{2010y / January / 6}) == STR("10 2010 01 01 01")); + assert(format(STR("{:%g %G %U %V %W}"), sys_days{2010y / January / 7}) == STR("10 2010 01 01 01")); + assert(format(STR("{:%g %G %U %V %W}"), sys_days{2010y / May / 1}) == STR("10 2010 17 17 17")); + assert(format(STR("{:%g %G %U %V %W}"), sys_days{2010y / May / 2}) == STR("10 2010 18 17 17")); + assert(format(STR("{:%g %G %U %V %W}"), sys_days{2010y / May / 3}) == STR("10 2010 18 18 18")); + assert(format(STR("{:%g %G %U %V %W}"), sys_days{2010y / December / 25}) == STR("10 2010 51 51 51")); + assert(format(STR("{:%g %G %U %V %W}"), sys_days{2010y / December / 26}) == STR("10 2010 52 51 51")); + assert(format(STR("{:%g %G %U %V %W}"), sys_days{2010y / December / 27}) == STR("10 2010 52 52 52")); + assert(format(STR("{:%g %G %U %V %W}"), sys_days{2010y / December / 28}) == STR("10 2010 52 52 52")); + assert(format(STR("{:%g %G %U %V %W}"), sys_days{2010y / December / 29}) == STR("10 2010 52 52 52")); + assert(format(STR("{:%g %G %U %V %W}"), sys_days{2010y / December / 30}) == STR("10 2010 52 52 52")); + assert(format(STR("{:%g %G %U %V %W}"), sys_days{2010y / December / 31}) == STR("10 2010 52 52 52")); + assert(format(STR("{:%S}"), utc_clock::from_sys(get_tzdb().leap_seconds.front().date()) - 1s) == STR("60")); assert(format(STR("{:%F %T}"), utc_clock::from_sys(get_tzdb().leap_seconds.front().date())) == STR("1972-07-01 00:00:00")); @@ -515,6 +533,13 @@ void test_year_month_formatter() { assert(format(STR("{:%Y %B}"), 2000y / July) == STR("2000 July")); throw_helper(STR("{:%d}"), 2000y / July); + + throw_helper(STR("{:%g}"), 2005y / January); + throw_helper(STR("{:%G}"), 2005y / January); + assert(format(STR("{:%g %G}"), 2005y / February) == STR("05 2005")); + assert(format(STR("{:%g %G}"), 2005y / November) == STR("05 2005")); + throw_helper(STR("{:%g}"), 2005y / December); + throw_helper(STR("{:%G}"), 2005y / December); } template @@ -533,6 +558,24 @@ void test_year_month_day_formatter() { assert(format(STR("{:%j}"), 1900y / May / 7) == STR("127")); assert(format(STR("{:%j}"), 2000y / May / 7) == STR("128")); throw_helper(STR("{:%j}"), invalid); + + assert(format(STR("{:%g %G %U %V %W}"), 2010y / January / 1) == STR("09 2009 00 53 00")); + assert(format(STR("{:%g %G %U %V %W}"), 2010y / January / 2) == STR("09 2009 00 53 00")); + assert(format(STR("{:%g %G %U %V %W}"), 2010y / January / 3) == STR("09 2009 01 53 00")); + assert(format(STR("{:%g %G %U %V %W}"), 2010y / January / 4) == STR("10 2010 01 01 01")); + assert(format(STR("{:%g %G %U %V %W}"), 2010y / January / 5) == STR("10 2010 01 01 01")); + assert(format(STR("{:%g %G %U %V %W}"), 2010y / January / 6) == STR("10 2010 01 01 01")); + assert(format(STR("{:%g %G %U %V %W}"), 2010y / January / 7) == STR("10 2010 01 01 01")); + assert(format(STR("{:%g %G %U %V %W}"), 2010y / May / 1) == STR("10 2010 17 17 17")); + assert(format(STR("{:%g %G %U %V %W}"), 2010y / May / 2) == STR("10 2010 18 17 17")); + assert(format(STR("{:%g %G %U %V %W}"), 2010y / May / 3) == STR("10 2010 18 18 18")); + assert(format(STR("{:%g %G %U %V %W}"), 2010y / December / 25) == STR("10 2010 51 51 51")); + assert(format(STR("{:%g %G %U %V %W}"), 2010y / December / 26) == STR("10 2010 52 51 51")); + assert(format(STR("{:%g %G %U %V %W}"), 2010y / December / 27) == STR("10 2010 52 52 52")); + assert(format(STR("{:%g %G %U %V %W}"), 2010y / December / 28) == STR("10 2010 52 52 52")); + assert(format(STR("{:%g %G %U %V %W}"), 2010y / December / 29) == STR("10 2010 52 52 52")); + assert(format(STR("{:%g %G %U %V %W}"), 2010y / December / 30) == STR("10 2010 52 52 52")); + assert(format(STR("{:%g %G %U %V %W}"), 2010y / December / 31) == STR("10 2010 52 52 52")); } template @@ -557,6 +600,8 @@ void test_year_month_day_last_formatter() { assert(format(STR("{:%j}"), 1900y / February / last) == STR("059")); assert(format(STR("{:%j}"), 2000y / February / last) == STR("060")); throw_helper(STR("{:%j}"), year{1900} / month{13} / last); + + assert(format(STR("{:%g %G %U %V %W}"), 2010y / May / last) == STR("10 2010 22 22 22")); } template @@ -586,6 +631,8 @@ void test_year_month_weekday_formatter() { assert(format(STR("{:%j}"), 1900y / January / Tuesday[2]) == STR("009")); throw_helper(STR("{:%j}"), invalid1); + + assert(format(STR("{:%g %G %U %V %W}"), 2010y / May / Monday[5]) == STR("10 2010 22 22 22")); } template @@ -613,6 +660,8 @@ void test_year_month_weekday_last_formatter() { assert(format(STR("{:%j}"), 1900y / January / Tuesday[last]) == STR("030")); throw_helper(STR("{:%j}"), invalid1); + + assert(format(STR("{:%g %G %U %V %W}"), 2010y / May / Monday[last]) == STR("10 2010 22 22 22")); } template @@ -798,7 +847,8 @@ void test_local_time_format_formatter() { assert(format(STR("{:%c, %x, %X}"), ltf) == STR("04/19/21 01:02:03, 04/19/21, 01:02:03")); assert(format(STR("{:%D %F, %Y %C %y, %b %B %h %m, %d %e, %a %A %u %w}"), ltf) == STR("04/19/21 2021-04-19, 2021 20 21, Apr April Apr 04, 19 19, Mon Monday 1 1")); - assert(format(STR("{:%H %I %M %S %r %R %T %p}"), ltf) == STR("01 01 02 03 01:02:03 01:02 01:02:03 AM")); + assert(format(STR("{:%H %I %M %S, %r, %R %T %p}"), ltf) == STR("01 01 02 03, 01:02:03 AM, 01:02 01:02:03 AM")); + assert(format(STR("{:%g %G %U %V %W}"), ltf) == STR("21 2021 16 16 16")); } template @@ -812,7 +862,8 @@ void test_zoned_time_formatter() { assert(format(STR("{:%c, %x, %X}"), zt) == STR("04/19/21 08:16:17, 04/19/21, 08:16:17")); assert(format(STR("{:%D %F, %Y %C %y, %b %B %h %m, %d %e, %a %A %u %w}"), zt) == STR("04/19/21 2021-04-19, 2021 20 21, Apr April Apr 04, 19 19, Mon Monday 1 1")); - assert(format(STR("{:%H %I %M %S %r %R %T %p}"), zt) == STR("08 08 16 17 08:16:17 08:16 08:16:17 AM")); + assert(format(STR("{:%H %I %M %S, %r, %R %T %p}"), zt) == STR("08 08 16 17, 08:16:17 AM, 08:16 08:16:17 AM")); + assert(format(STR("{:%g %G %U %V %W}"), zt) == STR("21 2021 16 16 16")); } int main() { From d652eabb2dea3cf56ce5a2aa117376433596456d Mon Sep 17 00:00:00 2001 From: Elnar Dakeshov <55715127+eldakesh-ms@users.noreply.github.com> Date: Wed, 21 Apr 2021 16:42:15 -0700 Subject: [PATCH 37/41] : Fine grained bounds checking (#1871) Co-authored-by: Stephan T. Lavavej Co-authored-by: mnatsuhara <46756417+mnatsuhara@users.noreply.github.com> --- stl/inc/chrono | 187 +++++++++++++----- .../test.cpp | 10 + 2 files changed, 145 insertions(+), 52 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index e8af360e74..02100ddbd2 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5965,13 +5965,8 @@ namespace chrono { if (_Custom_write(_Stream, _Spec, _Time, _Val)) { continue; } - // Otherwise, we should throw on out-of-bounds to avoid triggering asserts within put_time - // machinery. - if constexpr (_Has_ok<_Ty>) { - if (!_Val.ok()) { - _THROW(format_error("Cannot localize out-of-bounds time point.")); - } - } + + _Validate_specifiers(_Spec, _Val); _Stream << _STD put_time<_CharT>(&_Time, _Fmt_string(_Spec).data()); } @@ -5998,29 +5993,17 @@ namespace chrono { const auto _Month = _Time.tm_mon + 1; const bool _Has_modifier = _Spec._Modifier != '\0'; switch (_Spec._Type) { - case 'a': - case 'A': - case 'u': - case 'w': - if constexpr (_Is_any_of_v<_Ty, year_month_weekday, year_month_weekday_last>) { - if (!_Val.weekday().ok()) { - _THROW(format_error("Cannot print invalid weekday")); - } - _Os << _STD put_time(&_Time, _Fmt_string(_Spec).data()); - return true; - } else if constexpr (_Has_ok<_Ty>) { - if (!_Val.ok()) { - _THROW(format_error("Cannot print invalid weekday")); - } - } - return false; - case 'd': + case 'd': // Print days as a decimal, even if invalid. case 'e': // Most months have a proper last day, but February depends on the year. if constexpr (is_same_v<_Ty, month_day_last>) { if (_Val.month() == February) { _THROW(format_error("Cannot print the last day of February without a year")); } + + if (!_Val.ok()) { + return false; + } } if (_Has_modifier) { @@ -6063,20 +6046,6 @@ namespace chrono { if constexpr (_Is_specialization_v<_Ty, duration>) { _Os << _STD abs(_CHRONO duration_cast(_Val).count()); return true; - } else if constexpr (is_same_v<_Ty, month_day>) { - if (_Val.month() > February) { - _THROW(format_error("The day of year for a month_day past February is ambiguous.")); - } - } else if constexpr (is_same_v<_Ty, month_day_last>) { - if (_Val.month() >= February) { - _THROW(format_error("The day of year for a month_day_last other than January is ambiguous")); - } - } - - if constexpr (_Has_ok<_Ty>) { - if (!_Val.ok()) { - _THROW(format_error("Cannot print invalid day of year")); - } } return false; case 'q': @@ -6089,7 +6058,7 @@ namespace chrono { _Os << _STD abs(_Val.count()); } return true; - case 'm': + case 'm': // Print months as a decimal, even if invalid. if (_Has_modifier) { return false; } @@ -6099,7 +6068,7 @@ namespace chrono { } _Os << _Month; return true; - case 'Y': + case 'Y': // Print years as a decimal, even if invalid. if (_Has_modifier) { return false; } @@ -6109,13 +6078,13 @@ namespace chrono { } _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:04}"), _STD abs(_Year)); return true; - case 'y': + case 'y': // Print the two-digit year as a decimal, even if invalid. if (_Has_modifier) { return false; } _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:02}"), _Year < 0 ? 100 + (_Year % 100) : _Year % 100); return true; - case 'C': + case 'C': // Print the century as a decimal, even if invalid. if (_Has_modifier) { return false; } @@ -6126,30 +6095,23 @@ namespace chrono { _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:02}"), _STD abs(_Time_parse_fields::_Decompose_year(_Year).first) / 100); return true; - case 'F': + case 'F': // Print YMD even if invalid. _Custom_write(_Os, {._Type = 'Y'}, _Time, _Val); _Os << _CharT{'-'}; _Custom_write(_Os, {._Type = 'm'}, _Time, _Val); _Os << _CharT{'-'}; _Custom_write(_Os, {._Type = 'd'}, _Time, _Val); return true; - case 'D': + case 'D': // Print YMD even if invalid. _Custom_write(_Os, {._Type = 'm'}, _Time, _Val); _Os << _CharT{'/'}; _Custom_write(_Os, {._Type = 'd'}, _Time, _Val); _Os << _CharT{'/'}; _Custom_write(_Os, {._Type = 'y'}, _Time, _Val); return true; - case 'H': - if constexpr (_Is_specialization_v<_Ty, hh_mm_ss>) { - if (_Val.hours() >= hours{24}) { - _THROW(format_error("Cannot localize hh_mm_ss with an absolute value of 24 hours or more.")); - } - } - return false; case 'T': // Alias for %H:%M:%S but we need to rewrite %S to display fractions of a second. - _Custom_write(_Os, {._Type = 'H'}, _Time, _Val); + _Validate_specifiers({._Type = 'H'}, _Val); _Os << _STD put_time(&_Time, _STATICALLY_WIDEN(_CharT, "%H:%M:")); [[fallthrough]]; case 'S': @@ -6202,6 +6164,127 @@ namespace chrono { } } + template + static void _Validate_specifiers(const _Chrono_spec<_CharT>& _Spec, const _Ty& _Val) { + // clang-format off + if constexpr (_Is_specialization_v<_Ty, duration> || is_same_v<_Ty, sys_info> + || _Is_specialization_v<_Ty, time_point> || _Is_specialization_v<_Ty, _Local_time_format_t>) { + return; + } + // clang-format on + + if constexpr (_Is_specialization_v<_Ty, hh_mm_ss>) { + if (_Spec._Type == 'H' && _Val.hours() >= hours{24}) { + _THROW(format_error("Cannot localize hh_mm_ss with an absolute value of 24 hours or more.")); + } + return; + } + + constexpr bool _Is_ymd = + _Is_any_of_v<_Ty, year_month_day, year_month_day_last, year_month_weekday, year_month_weekday_last>; + + const auto _Validate = [&] { + switch (_Spec._Type) { + case 'a': + case 'A': + case 'u': + case 'w': + if constexpr (_Is_any_of_v<_Ty, weekday, weekday_last>) { + return _Val.ok(); + } else if constexpr (_Is_any_of_v<_Ty, weekday_indexed, year_month_weekday, + year_month_weekday_last>) { + return _Val.weekday().ok(); + } else if constexpr (is_same_v<_Ty, month_weekday>) { + return _Val.weekday_indexed().weekday().ok(); + } else if constexpr (is_same_v<_Ty, month_weekday_last>) { + return _Val.weekday_last().ok(); + } else if constexpr (_Is_any_of_v<_Ty, year_month_day, year_month_day_last>) { + return _Val.ok(); + } + break; + + case 'b': + case 'B': + case 'h': + case 'm': + if constexpr (is_same_v<_Ty, month>) { + return _Val.ok(); + } else if constexpr (_Is_any_of_v<_Ty, month_day, month_day_last, month_weekday, month_weekday_last, + year_month> || _Is_ymd) { + return _Val.month().ok(); + } + break; + + case 'C': + case 'y': + case 'Y': + if constexpr (is_same_v<_Ty, year>) { + return _Val.ok(); + } else if constexpr (_Is_any_of_v<_Ty, year_month> || _Is_ymd) { + return _Val.year().ok(); + } + break; + + case 'd': + case 'e': + if constexpr (_Is_any_of_v<_Ty, day, month_day_last>) { + return _Val.ok(); + } else if constexpr (is_same_v<_Ty, month_day>) { + return _Val.day().ok(); + } else if constexpr (_Is_ymd) { + const year_month_day& _Ymd{_Val}; + return _Ymd.day().ok(); + } + break; + + case 'D': + case 'F': + if constexpr (_Has_ok<_Ty>) { + return _Val.ok(); + } + break; + + case 'j': + if constexpr (is_same_v<_Ty, month_day>) { + if (_Val.month() > February) { + _THROW(format_error("The day of year for a month_day past February is ambiguous.")); + } + return true; + } else if constexpr (is_same_v<_Ty, month_day_last>) { + if (_Val.month() >= February) { + _THROW( + format_error("The day of year for a month_day_last other than January is ambiguous")); + } + return true; + } else if constexpr (_Is_ymd) { + return _Val.ok(); + } + break; + + case 'g': + case 'G': + case 'U': + case 'V': + case 'W': + if constexpr (_Is_ymd) { + return _Val.ok(); + } + break; + + default: + if constexpr (_Has_ok<_Ty>) { + return _Val.ok(); + } + return true; + } + _STL_INTERNAL_CHECK(false); + return false; + }; + if (!_Validate()) { + _THROW(format_error("Cannot localize out-of-bounds time point.")); + } + } + _NODISCARD static array<_CharT, 4> _Fmt_string(const _Chrono_spec<_CharT>& _Spec) { array<_CharT, 4> _Fmt_str; size_t _Next_idx = 0; diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index 6fd4b2117b..6e97f6bb7c 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -444,6 +444,7 @@ void test_weekday_indexed_formatter() { assert(format(STR("{:%a %A}"), weekday_indexed{Monday, 2}) == STR("Mon Monday")); assert(format(STR("{:%u %w}"), weekday_indexed{Tuesday, 3}) == STR("2 2")); assert(format(STR("{:%u %w}"), weekday_indexed{Sunday, 4}) == STR("7 0")); + assert(format(STR("{:%u %w}"), weekday_indexed{Sunday, 10}) == STR("7 0")); } template @@ -464,6 +465,8 @@ void test_month_day_formatter() { assert(format(STR("{:%B %d}"), June / 17) == STR("June 17")); throw_helper(STR("{:%Y}"), June / 17); + assert(format(STR("{:%B}"), June / day{40}) == STR("June")); + throw_helper(STR("{:%B}"), month{13} / 17); assert(format(STR("{:%j}"), January / 5) == STR("005")); assert(format(STR("{:%j}"), February / 5) == STR("036")); @@ -479,6 +482,7 @@ void test_month_day_last_formatter() { assert(format(STR("{:%B}"), June / last) == STR("June")); assert(format(STR("{:%d}"), June / last) == STR("30")); throw_helper(STR("{:%d}"), February / last); + throw_helper(STR("{:%B}"), month{13} / last); assert(format(STR("{:%j}"), January / last) == STR("031")); throw_helper(STR("{:%j}"), February / last); @@ -505,6 +509,9 @@ void test_month_weekday_formatter() { assert(format(STR("{:%b %B %h %m %a %A %u %w}"), mwd1) == STR("Aug August Aug 08 Tue Tuesday 2 2")); assert(format(STR("{:%b %B %h %m %a %A %u %w}"), mwd2) == STR("Dec December Dec 12 Sun Sunday 7 0")); + + assert(format(STR("{:%u}"), invalid1) == STR("5")); + throw_helper(STR("{:%u}"), invalid2); } template @@ -525,6 +532,9 @@ void test_month_weekday_last_formatter() { assert(format(STR("{:%b %B %h %m %a %A %u %w}"), mwdl1) == STR("Aug August Aug 08 Tue Tuesday 2 2")); assert(format(STR("{:%b %B %h %m %a %A %u %w}"), mwdl2) == STR("Dec December Dec 12 Sun Sunday 7 0")); + + assert(format(STR("{:%u}"), invalid2) == STR("5")); + throw_helper(STR("{:%u}"), invalid1); } template From 360e631b5a7229d20f8821f4c6619860f19876cf Mon Sep 17 00:00:00 2001 From: MattStephanson <68978048+MattStephanson@users.noreply.github.com> Date: Wed, 21 Apr 2021 16:43:07 -0700 Subject: [PATCH 38/41] Fixes two-digit year for negative centuries. (#1872) --- stl/inc/chrono | 3 ++- .../tests/P0355R7_calendars_and_time_zones_formatting/test.cpp | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 02100ddbd2..53b8a8ee35 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -6082,7 +6082,8 @@ namespace chrono { if (_Has_modifier) { return false; } - _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:02}"), _Year < 0 ? 100 + (_Year % 100) : _Year % 100); + _Os << _STD format( + _STATICALLY_WIDEN(_CharT, "{:02}"), _Time_parse_fields::_Decompose_year(_Year).second); return true; case 'C': // Print the century as a decimal, even if invalid. if (_Has_modifier) { diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index 6e97f6bb7c..d1a0ed8f8b 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -413,6 +413,8 @@ void test_year_formatter() { assert(format(STR("{:%Y %y%C}"), year{1912}) == STR("1912 1219")); assert(format(STR("{:%Y %y%C}"), year{-1912}) == STR("-1912 88-20")); + assert(format(STR("{:%Y %y%C}"), year{-200}) == STR("-0200 00-02")); + assert(format(STR("{:%Y %y%C}"), year{200}) == STR("0200 0002")); // TRANSITION, add tests for EY Oy Ey EC empty_braces_helper(year{1900}, STR("1900")); From bd5b397fe177fb9f98f38ed8ed5dcb2e25846a61 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 21 Apr 2021 16:47:21 -0700 Subject: [PATCH 39/41] Use run_tz_test to catch exceptions and handle internal machines. --- .../P0355R7_calendars_and_time_zones_formatting/test.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index d1a0ed8f8b..c6ec0f9c71 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -878,7 +878,7 @@ void test_zoned_time_formatter() { assert(format(STR("{:%g %G %U %V %W}"), zt) == STR("21 2021 16 16 16")); } -int main() { +void test() { test_parse_conversion_spec(); test_parse_conversion_spec(); @@ -950,3 +950,7 @@ int main() { test_zoned_time_formatter(); test_zoned_time_formatter(); } + +int main() { + run_tz_test([] { test(); }); +} From 7c177d0347c06474b21b23868f8dfabe45142d02 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 21 Apr 2021 17:46:24 -0700 Subject: [PATCH 40/41] Validate year_month::ok() for %g %G. --- stl/inc/chrono | 4 ++++ .../P0355R7_calendars_and_time_zones_formatting/test.cpp | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/stl/inc/chrono b/stl/inc/chrono index 53b8a8ee35..964843eded 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -6023,6 +6023,10 @@ namespace chrono { "The ISO week-based year for a year_month of January or December is ambiguous.")); } + if (!_Val.ok()) { + _THROW(format_error("The ISO week-based year for an out-of-bounds year_month is ambiguous.")); + } + const char _Gregorian_type = _Spec._Type == 'g' ? 'y' : 'Y'; _Os << _STD put_time(&_Time, _Fmt_string({._Type = _Gregorian_type}).data()); return true; diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp index c6ec0f9c71..b16ef15005 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_formatting/test.cpp @@ -546,12 +546,18 @@ void test_year_month_formatter() { assert(format(STR("{:%Y %B}"), 2000y / July) == STR("2000 July")); throw_helper(STR("{:%d}"), 2000y / July); + throw_helper(STR("{:%g}"), 2005y / month{0}); + throw_helper(STR("{:%G}"), 2005y / month{0}); throw_helper(STR("{:%g}"), 2005y / January); throw_helper(STR("{:%G}"), 2005y / January); assert(format(STR("{:%g %G}"), 2005y / February) == STR("05 2005")); assert(format(STR("{:%g %G}"), 2005y / November) == STR("05 2005")); throw_helper(STR("{:%g}"), 2005y / December); throw_helper(STR("{:%G}"), 2005y / December); + throw_helper(STR("{:%g}"), 2005y / month{13}); + throw_helper(STR("{:%G}"), 2005y / month{13}); + throw_helper(STR("{:%g}"), year{-32768} / November); + throw_helper(STR("{:%G}"), year{-32768} / November); } template From ab1001cd1e4841fe0feee6bf80b1c32bf7458864 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 21 Apr 2021 20:00:37 -0700 Subject: [PATCH 41/41] We don't need _No_chrono_specs. --- stl/inc/chrono | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/stl/inc/chrono b/stl/inc/chrono index 964843eded..e4850fd835 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -5830,15 +5830,7 @@ namespace chrono { } } - const auto& _List = _Specs._Chrono_specs_list; - - // [time.format]/6 - if (_List.empty()) { - _No_chrono_specs = true; - return _Res_iter; - } - - for (const auto& _Spec : _List) { + for (const auto& _Spec : _Specs._Chrono_specs_list) { if (_Spec._Type != '\0' && !_Is_valid_type<_Ty>(_Spec._Type)) { _THROW(format_error("Invalid type.")); } @@ -5940,8 +5932,8 @@ namespace chrono { _NODISCARD auto _Write(_FormatContext& _FormatCtx, const _Ty& _Val, const tm& _Time) { basic_ostringstream<_CharT> _Stream; - if (_No_chrono_specs) { - _Stream << _Val; + if (_Specs._Chrono_specs_list.empty()) { + _Stream << _Val; // N4885 [time.format]/6 } else { _Stream.imbue(_FormatCtx.locale()); if constexpr (_Is_specialization_v<_Ty, hh_mm_ss>) { @@ -6303,7 +6295,6 @@ namespace chrono { } _Chrono_format_specs<_CharT> _Specs{}; - bool _No_chrono_specs = false; basic_string_view<_CharT> _Time_zone_abbreviation{}; }; } // namespace chrono