diff --git a/stl/inc/chrono b/stl/inc/chrono index 94027aadf5..e4850fd835 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -11,7 +11,6 @@ #include #include #include -#include #include #include @@ -25,6 +24,8 @@ #include #include #include +#include +#include #include #include #include @@ -32,6 +33,7 @@ #ifdef __cpp_lib_concepts #include #include +#include #endif // defined(__cpp_lib_concepts) #endif // _HAS_CXX20 @@ -772,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) { @@ -780,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(); } @@ -1429,6 +1435,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; @@ -2252,16 +2270,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] @@ -3166,7 +3206,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 @@ -5177,184 +5217,1309 @@ 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 +_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; + } +} - 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>(_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(char{}, _CharT{}) } -> same_as; + { _At._On_lit_char(_CharT{}) } -> same_as; +}; +// clang-format on - // same as _Specs_setter - constexpr void _On_align(_Fmt_align _Aln) { - _Specs._Alignment = _Aln; - } +// 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_spec { + _CharT _Lit_char = _CharT{'\0'}; // any character other than '{', '}', or '%' + char _Modifier = '\0'; // either 'E' or 'O' + char _Type = '\0'; +}; - // 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).")); - } +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 + vector<_Chrono_spec<_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_, _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(_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(_Auto_id_tag) { + _Specs._Dynamic_precision_index = _Verify_dynamic_arg_index_in_range(_Parse_ctx.next_arg_id()); + } + + 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.")); } - 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_spec<_CharT> _Conv_spec{._Modifier = _Modifier, ._Type = static_cast(_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_spec<_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); - } +private: + _Chrono_format_specs<_CharT>& _Specs; + _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); + } +}; - template _Callbacks_type> - _NODISCARD constexpr const _CharT* _Parse_conversion_specs( - const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) { - _STL_INTERNAL_CHECK(*_Begin == '%'); +// 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.")); + } + + char _Mod = '\0'; + _CharT _Ch = *_Begin; + + if (_Ch == 'E' || _Ch == 'O') { // includes modifier + _Mod = static_cast(_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; + 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 != '}' && *_Begin != '%') { + _THROW(format_error("Invalid format string - chrono-specs must begin with conversion-spec")); + } + + // chrono-spec + while (_Begin != _End && *_Begin != '}') { + if (*_Begin == '%') { // conversion-spec + if (++_Begin == _End) { + _THROW(format_error("Invalid format string - missing type after %")); + } + + switch (*_Begin) { + 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; - if (_Begin == _End || *_Begin == '}') { - _THROW(format_error("Invalid format string - missing type after modifier.")); + } + } + + return _Begin; +} + +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&) { + _STL_INTERNAL_CHECK(false); + } + + 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) { + _Os << _STD format( + _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>) { + 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()); } + } else { + const auto _Dp = _CHRONO floor(_Val); + _Write_seconds(_Os, hh_mm_ss{_Val - _Dp}); } + } - _CharT _Type = *_Begin; - _Callbacks._On_conversion_spec(_Mod, _Type); + template + void _Write_seconds(basic_ostream<_CharT, _Traits>& _Os, const _Local_time_format_t<_Duration>& _Val) { + _Write_seconds(_Os, _Val._Time); + } - return ++_Begin; + 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 _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 + _NODISCARD tm _Fill_tm(const _Ty& _Val) { + unsigned int _Day = 0; + unsigned int _Month = 0; + int _Year = 0; + int _Yearday = 0; + int _Weekday = 0; + int _Hours = 0; + int _Minutes = 0; + int _Seconds = 0; + + 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>) { + _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_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()); + _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()); + } 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()); + } 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>) { + 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(); + _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}; + 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; } - _Begin = _Parse_align(_Begin, _End, _Callbacks); - if (_Begin == _End) { - return _Begin; + 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_yday = _Yearday; + _Time.tm_wday = _Weekday; + return _Time; + } + + template + basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const day& _Val) { + 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) { + 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) { + 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) { + 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) { + 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) - _Begin = _Parse_width(_Begin, _End, _Callbacks); - if (_Begin == _End) { - return _Begin; + 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}) + 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()}; + } + + 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; + + 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}; + 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, 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 non-chrono::duration type, " + "see N4885 [time.format]/1.")); + } + } + + for (const auto& _Spec : _Specs._Chrono_specs_list) { + if (_Spec._Type != '\0' && !_Is_valid_type<_Ty>(_Spec._Type)) { + _THROW(format_error("Invalid type.")); + } + _Check_modifier(_Spec._Type, _Spec._Modifier); + } + + return _Res_iter; } - if (*_Begin == '.') { - _Begin = _Parse_precision(_Begin, _End, _Callbacks); - if (_Begin == _End) { - return _Begin; + static void _Check_modifier(const char _Type, const char _Modifier) { + if (_Modifier == '\0') { + return; } + + 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; + } + } + + _THROW(format_error("Incompatible modifier for type")); + } + + template + _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>) { + 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 _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 _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 == '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'; + } 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') { + return true; + } + } + 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"); + } + } + + template + _NODISCARD auto _Write(_FormatContext& _FormatCtx, const _Ty& _Val, const tm& _Time) { + basic_ostringstream<_CharT> _Stream; + + 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>) { + 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) { + if (_Spec._Lit_char != _CharT{'\0'}) { + _Stream << _Spec._Lit_char; + continue; + } + + // 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; + } + + _Validate_specifiers(_Spec, _Val); + + _Stream << _STD put_time<_CharT>(&_Time, _Fmt_string(_Spec).data()); + } + } + + 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-spec - while (_Begin != _End && *_Begin != '}') { - if (*_Begin == '%') { // conversion-spec - if (_Begin + 1 == _End) { - _THROW(format_error("Invalid format string - missing type after %")); + // 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) { + 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'; + switch (_Spec._Type) { + 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) { + return false; + } + + if (_Time.tm_mday < 10) { + _Os << (_Spec._Type == 'd' ? _CharT{'0'} : _CharT{' '}); + } + _Os << _Time.tm_mday; + 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.")); + } + + 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; } - const _CharT _Next_ch = *(_Begin + 1); - switch (_Next_ch) { - case 'n': - _Callbacks._On_lit_char('\n'); - _Begin += 2; + 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>) { + _Os << _STD abs(_CHRONO duration_cast(_Val).count()); + return true; + } + return false; + 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': // Print months as a decimal, even if invalid. + if (_Has_modifier) { + return false; + } + + if (_Month < 10) { + _Os << _CharT{'0'}; + } + _Os << _Month; + return true; + case 'Y': // Print years as a decimal, even if invalid. + if (_Has_modifier) { + return false; + } + + if (_Year < 0) { + _Os << _CharT{'-'}; + } + _Os << _STD format(_STATICALLY_WIDEN(_CharT, "{:04}"), _STD abs(_Year)); + return true; + 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}"), _Time_parse_fields::_Decompose_year(_Year).second); + return true; + case 'C': // Print the century as a decimal, even if invalid. + 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': // 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': // 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 'T': + // Alias for %H:%M:%S but we need to rewrite %S to display fractions of a second. + _Validate_specifiers({._Type = 'H'}, _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': + 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 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; + } + return true; + case 'z': + { + 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 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{}; + } + + 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; + } + default: + return false; + } + } + + 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 't': - _Callbacks._On_lit_char('\t'); - _Begin += 2; + + 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 '%': - _Callbacks._On_lit_char('%'); - _Begin += 2; + + 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; - default: // some other type - _Begin = _Parse_conversion_specs(_Begin, _End, _Callbacks); + + 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; } - } else { // literal-char - _Callbacks._On_lit_char(*_Begin); - ++_Begin; + _STL_INTERNAL_CHECK(false); + return false; + }; + if (!_Validate()) { + _THROW(format_error("Cannot localize out-of-bounds time point.")); } } - return _Begin; + _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{'%'}; + 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{}; + basic_string_view<_CharT> _Time_zone_abbreviation{}; + }; +} // namespace chrono + +template +struct _Fill_tm_formatter { + auto parse(basic_format_parse_context<_CharT>& _Parse_ctx) { + return _Impl.template _Parse<_Ty>(_Parse_ctx); + } + + template + auto format(const _Ty& _Val, _FormatContext& _FormatCtx) { + return _Impl._Write(_FormatCtx, _Val, _Fill_tm(_Val)); + } + +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> {}; + +template +struct formatter<_CHRONO month, _CharT> // + : _Fill_tm_formatter<_CHRONO month, _CharT> {}; + +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 weekday_last, _CharT> // + : _Fill_tm_formatter<_CHRONO weekday_last, _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 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> {}; + +template +struct formatter<_CHRONO year_month_day, _CharT> // + : _Fill_tm_formatter<_CHRONO year_month_day, _CharT> {}; + +template +struct formatter<_CHRONO year_month_day_last, _CharT> // + : _Fill_tm_formatter<_CHRONO year_month_day_last, _CharT> {}; + +template +struct formatter<_CHRONO year_month_weekday, _CharT> // + : _Fill_tm_formatter<_CHRONO year_month_weekday, _CharT> {}; + +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> + : _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) { + 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 _Chrono_formatter<_CharT> _Impl{_STATICALLY_WIDEN(_CharT, "UTC")}; +}; + +template +struct formatter<_CHRONO utc_time<_Duration>, _CharT> { + 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 _Chrono_formatter<_CharT> _Impl{_STATICALLY_WIDEN(_CharT, "UTC")}; +}; + +template +struct formatter<_CHRONO tai_time<_Duration>, _CharT> { + 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) { + 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 _Chrono_formatter<_CharT> _Impl{_STATICALLY_WIDEN(_CharT, "TAI")}; +}; + +template +struct formatter<_CHRONO gps_time<_Duration>, _CharT> { + 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) { + 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 _Chrono_formatter<_CharT> _Impl{_STATICALLY_WIDEN(_CharT, "GPS")}; +}; + +template +struct formatter<_CHRONO file_time<_Duration>, _CharT> { + 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 _Chrono_formatter<_CharT> _Impl{_STATICALLY_WIDEN(_CharT, "UTC")}; +}; + +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) { + 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 -} // namespace chrono // HELPERS template @@ -5451,6 +6616,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/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/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 e667375ace..8cd4a768c6 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -275,43 +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.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 -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 - -# 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 @@ -662,6 +625,28 @@ 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 + +# 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 + # *** LIKELY STL BUGS *** # Not yet analyzed, likely STL bugs. Assertions and other runtime failures. @@ -868,9 +853,22 @@ 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 +# 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,18 @@ 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.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.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 3ac3830315..2eeaaaee2a 100644 --- a/tests/libcxx/skipped_tests.txt +++ b/tests/libcxx/skipped_tests.txt @@ -275,43 +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.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 -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 - -# 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 @@ -662,6 +625,28 @@ 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 + +# 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 + # *** LIKELY STL BUGS *** # Not yet analyzed, likely STL bugs. Assertions and other runtime failures. @@ -868,6 +853,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 2ae3a52bb3..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 @@ -6,33 +6,28 @@ #include #include #include +#include #include +#include #include #include #include +#include + 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; +[[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; } -}; - -template <> -struct choose_literal { - static constexpr const wchar_t* choose(const char*, const wchar_t* s) { - return s; - } -}; +} -#define TYPED_LITERAL(CharT, Literal) (choose_literal::choose(Literal, L##Literal)) +#define STR(Literal) (choose_literal(Literal, L##Literal)) template struct testing_callbacks { @@ -44,7 +39,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) { @@ -71,8 +66,8 @@ struct testing_callbacks { void _On_dynamic_precision(_Auto_id_tag) { assert(expected_auto_dynamic_precision); } - void _On_conversion_spec(CharT mod, CharT type) { - assert(static_cast(mod) == expected_chrono_specs[curr_index]._Modifier); + void _On_conversion_spec(char mod, CharT type) { + assert(mod == expected_chrono_specs[curr_index]._Modifier); assert(static_cast(type) == expected_chrono_specs[curr_index]._Type); assert(expected_chrono_specs[curr_index]._Lit_char == CharT{0}); // not set ++curr_index; @@ -105,15 +100,15 @@ 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")); - 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}); @@ -137,23 +132,23 @@ 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; - - 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, "A%nB%tC%%D")); + using chrono_spec = _Chrono_spec; + + 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}); 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,21 +157,21 @@ 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_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}); @@ -188,10 +183,780 @@ bool test_parse_chrono_format_specs() { return true; } -int main() { +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 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); + + basic_ostringstream stream; + stream << val; + assert(stream.str() == expected); + assert(stream); +} + +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")); + 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")); + 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("{:%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")); + 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 +void test_day_formatter() { + using view_typ = basic_string_view; + using str_typ = basic_string; + + 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}; + 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")); + 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}); + + // Op << + 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() { + 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")); + assert(format(STR("{:%m %Om}"), month{1}) == STR("01 01")); + + // Out of bounds month + 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}); + + // Invalid specs + throw_helper(STR("{:%A}"), month{1}); + throw_helper(STR("{:%.4}"), month{1}); +} + +template +void test_year_formatter() { + 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")); + 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")); + 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}; + 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")); + 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}; + 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")); + 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 +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_day_formatter() { + 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); + 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")); + assert(format(STR("{:%j}"), February / 28) == STR("059")); + assert(format(STR("{:%j}"), February / 29) == STR("060")); + throw_helper(STR("{:%j}"), March / 1); +} + +template +void test_month_day_last_formatter() { + empty_braces_helper(February / last, STR("Feb/last")); + + 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); + throw_helper(STR("{:%j}"), April / last); +} + +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")); + + assert(format(STR("{:%u}"), invalid1) == STR("5")); + throw_helper(STR("{:%u}"), invalid2); +} + +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")); + + assert(format(STR("{:%u}"), invalid2) == STR("5")); + throw_helper(STR("{:%u}"), invalid1); +} + +template +void test_year_month_formatter() { + empty_braces_helper(1444y / October, STR("1444/Oct")); + + 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 +void test_year_month_day_formatter() { + year_month_day invalid{year{1234}, month{0}, day{31}}; + 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")); + 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); + + 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 +void test_year_month_day_last_formatter() { + 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")); + + 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); + + assert(format(STR("{:%g %G %U %V %W}"), 2010y / May / last) == STR("10 2010 22 22 22")); +} + +template +void test_year_month_weekday_formatter() { + 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")); + + 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); + + assert(format(STR("{:%g %G %U %V %W}"), 2010y / May / Monday[5]) == STR("10 2010 22 22 22")); +} + +template +void test_year_month_weekday_last_formatter() { + 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")); + + 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); + + assert(format(STR("{:%g %G %U %V %W}"), 2010y / May / Monday[last]) == STR("10 2010 22 22 22")); +} + +template +void test_hh_mm_ss_formatter() { + 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")); + 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")); + + 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")); + + 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")); +} + +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"); + } +} + +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()); + + // 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, " + "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")); +} + +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 AM, 01:02 01:02:03 AM")); + assert(format(STR("{:%g %G %U %V %W}"), ltf) == STR("21 2021 16 16 16")); +} + +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 AM, 08:16 08:16:17 AM")); + assert(format(STR("{:%g %G %U %V %W}"), zt) == STR("21 2021 16 16 16")); +} + +void test() { test_parse_conversion_spec(); test_parse_conversion_spec(); test_parse_chrono_format_specs(); test_parse_chrono_format_specs(); + + test_duration_formatter(); + test_duration_formatter(); + + test_clock_formatter(); + test_clock_formatter(); + + test_day_formatter(); + test_day_formatter(); + + test_month_formatter(); + test_month_formatter(); + + test_year_formatter(); + test_year_formatter(); + + test_weekday_formatter(); + test_weekday_formatter(); + + test_weekday_indexed_formatter(); + test_weekday_indexed_formatter(); + + 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(); + + 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(); + + test_exception_classes(); + + test_information_classes(); + test_information_classes(); + + test_local_time_format_formatter(); + test_local_time_format_formatter(); + + test_zoned_time_formatter(); + test_zoned_time_formatter(); +} + +int main() { + run_tz_test([] { test(); }); } 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}); } } 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