diff --git a/stl/inc/chrono b/stl/inc/chrono index 02af136b01e..351cfa5cbfb 100644 --- a/stl/inc/chrono +++ b/stl/inc/chrono @@ -21,8 +21,11 @@ #include #include #include +#include #include +#include #include +#include #include #include #endif // _HAS_CXX20 @@ -3441,13 +3444,15 @@ namespace chrono { template concept _Convertible_to_utc_time = - is_same_v<_Clock, utc_clock> || requires(const time_point<_Clock, _Duration>& _Time) { + is_same_v<_Clock, + utc_clock> || is_same_v<_Clock, system_clock> || requires(const time_point<_Clock, _Duration>& _Time) { { _Clock::to_utc(_Time) } ->_Is_time_point; }; template - concept _Convertible_from_utc_time = is_same_v<_Clock, utc_clock> || requires(const utc_time<_Duration>& _Time) { + concept _Convertible_from_utc_time = + is_same_v<_Clock, utc_clock> || is_same_v<_Clock, system_clock> || requires(const utc_time<_Duration>& _Time) { { _Clock::from_utc(_Time) } ->_Is_time_point<_Clock>; }; @@ -3521,6 +3526,1589 @@ namespace chrono { } // clang-format on } + + // [time.parse] + struct _Time_parse_fields { + using _SubsecondType = duration; + + // These are the primary fields, used to set the chrono type being parsed. + optional _Subsecond; + optional _Second; + optional _Minute; + optional _Hour_24; + optional _Weekday; // 0-based, starts Sunday + optional _Day; // 1-based + optional _Month; // 1-based + optional _Day_of_year; // 1-based + optional _Two_dig_year; + optional _Century; + optional _Utc_offset; // in minutes + optional _Tz_name; + + // These are the secondary fields, used to store parsed data. They must be converted to primary fields and + // checked for consistency. + optional _Hour_12; + optional _Ampm; + optional _Iso_year; + optional _Two_dig_iso_year; + optional _Iso_week; // 1-based + optional _Week_u; // week number, W01 begins on first Sunday + optional _Week_w; // week number, W01 begins on first Monday + + enum _FieldFlags : unsigned int { + _F_sec = 0x01, + _F_min = 0x02, + _F_hr = 0x04, + _F_day = 0x08, + _F_wkday = 0x10, + _F_mon = 0x20, + _F_doy = 0x40, + _F_year = 0x80, + }; + + _NODISCARD unsigned int _Used_fields() const { + unsigned int _Ret{0}; + if (_Second || _Subsecond) { + _Ret |= _F_sec; + } + if (_Minute) { + _Ret |= _F_min; + } + if (_Hour_24) { + _Ret |= _F_hr; + } + if (_Day) { + _Ret |= _F_day; + } + if (_Weekday) { + _Ret |= _F_wkday; + } + if (_Month) { + _Ret |= _F_mon; + } + if (_Day_of_year) { + _Ret |= _F_doy; + } + if (_Two_dig_year && _Century) { + _Ret |= _F_year; + } + return _Ret; + } + + _NODISCARD static bool _Test_bits( + const unsigned int _Bits, const unsigned int _Must_set, const unsigned int _Optional = 0) { + return (_Bits & ~_Optional) == _Must_set; + } + + + template + static constexpr _Ty _Invalid_time_field{numeric_limits<_Ty>::lowest()}; + static constexpr const char* _Invalid_time_string = "!"; + + static void _Initialize_time_point(tm& _Tp) { + _Tp.tm_sec = _Invalid_time_field; + _Tp.tm_min = _Invalid_time_field; + _Tp.tm_hour = _Invalid_time_field; + _Tp.tm_wday = _Invalid_time_field; + _Tp.tm_mday = _Invalid_time_field; + _Tp.tm_mon = _Invalid_time_field; + _Tp.tm_yday = _Invalid_time_field; + _Tp.tm_year = _Invalid_time_field; + } + + template + _NODISCARD static bool _Update(optional<_Ty>& _Val, const _Ty& _New) { + // Update a field. Ignores invalid values. If _Val already has a value, returns true or false according to + // whether the new value matches the current one or not, so that inconsistencies can be detected. + + if constexpr (is_same_v<_Ty, string>) { + if (_New == _Invalid_time_string) { + return true; + } + } else { + if (_New == _Invalid_time_field<_Ty>) { + return true; + } + } + + if (!_Val.has_value()) { + _Val = _New; + return true; + } else { + return _STD exchange(_Val, _New) == _New; + } + } + + _NODISCARD static pair _Decompose_year(const int _Year) { + int _Two_d_year = _Year % 100; + if (_Two_d_year < 0) { + _Two_d_year += 100; + } + return {_Year - _Two_d_year, _Two_d_year}; + } + + _NODISCARD bool _Update_if_valid(const tm& _Tp, const bool _Full_year) { + bool _No_err{true}; + if (_Tp.tm_hour != _Invalid_time_field) { + _No_err = _No_err && _Update(_Hour_24, _Tp.tm_hour); + _No_err = _No_err && _Update(_Ampm, _Hour_24 >= 12 ? 1 : 0); + _No_err = _No_err && _Update(_Hour_12, _CHRONO make12(hours{*_Hour_24}).count()); + } + + _No_err = _No_err && _Update(_Minute, _Tp.tm_min); + _No_err = _No_err && _Update(_Second, _Tp.tm_sec); + _No_err = _No_err && _Update(_Day, _Tp.tm_mday); + _No_err = _No_err && _Update(_Weekday, _Tp.tm_wday); + + if (_Tp.tm_mon != _Invalid_time_field) { + _No_err = _No_err && _Update(_Month, _Tp.tm_mon + 1); + } + + if (_Tp.tm_yday != _Invalid_time_field) { + _No_err = _No_err && _Update(_Day_of_year, _Tp.tm_yday + 1); + } + + if (_Tp.tm_year != _Invalid_time_field) { + // Sometimes we expect only the last two digits. + const auto _Year_parts = _Decompose_year(_Tp.tm_year + 1900); + _No_err = _No_err && _Update(_Two_dig_year, _Year_parts.second); + if (_Full_year) { + _No_err = _No_err && _Update(_Century, _Year_parts.first); + } + } + + return _No_err; + } + + _NODISCARD bool _Yday_to_month_day(const int _Yday, const int _Year) { + // A day-of-year that's February 28 or earlier, by itself, is a valid month_day. Any later day is + // ambiguous without a year. + if (_Out_of_range(_Day_of_year, 1, _Two_dig_year || _Iso_year ? _Days_in_year(_Year) : 59)) { + return false; + } + + if (_Day_of_year <= 31) { + return _Update(_Day, _Yday) && _Update(_Month, 1); + } + + const int _Feb_end{_Is_leap(_Year) ? 60 : 59}; + if (_Day_of_year <= _Feb_end) { + return _Update(_Day, _Yday - 31) && _Update(_Month, 2); + } + + // Shift day-of-year so that 1 == March 1. This is the same as year_month_day::_Civil_from_days, except + // _Day_of_year --> _Shifted_yday-1 and _Mp --> _Month - 3. + const int _Shifted_yday{*_Day_of_year - _Feb_end}; + return _Update(_Month, (535 * _Shifted_yday + 48950) >> 14) + && _Update(_Day, _Shifted_yday - ((979 * *_Month - 2918) >> 5)); + } + + static constexpr int _Era_begin_wday{3}; // Wednesday + + _NODISCARD static constexpr int _Jan1_weekday(int _Year) { + --_Year; + const int _Era = (_Year >= 0 ? _Year : _Year - 399) / 400; + const int _Yoe = _Year - _Era * 400; + // Jan. 1 is always day 306 of the shifted [Mar, ..., Dec, Jan, Feb] year. + const int _Doe = ((1461 * _Yoe) >> 2) - _Yoe / 100 + 306; + return (_Doe + _Era_begin_wday) % 7; + } + + _NODISCARD static constexpr int _Iso8601_weeks(int _Year) { + const int _P_y = (_Year + _Year / 4 - _Year / 100 + _Year / 400) % 7; + --_Year; + const int _P_ym1 = (_Year + _Year / 4 - _Year / 100 + _Year / 400) % 7; + return 52 + (_P_y == 4 || _P_ym1 == 3); + } + + _NODISCARD static constexpr int _Iso8601_week(const int _Day_of_year, const int _Weekday, const int _Year) { + // Jan. 4 is always week 1; rollover to next week always happens on Monday. + const auto _Week{(_Day_of_year + 9 - _Prev_weekday(_Weekday, 1)) / 7}; + if (_Week < 1) { + return _Iso8601_weeks(_Year - 1); + } else if (_Week > _Iso8601_weeks(_Year)) { + return 1; + } else { + return _Week; + } + } + + _NODISCARD static constexpr bool _Is_leap(const int _Year) { + return _Year % 4 == 0 && (_Year % 100 != 0 || _Year % 400 == 0); + } + + _NODISCARD static constexpr int _Days_in_year(const int _Year) { + return _Is_leap(_Year) ? 366 : 365; + } + + _NODISCARD static constexpr int _Next_weekday(const int _Wday, const int _Shift) { + // 0 <= _Shift <= 6 + int _Result = _Wday + _Shift; + if (_Result >= 7) { + _Result -= 7; + } + return _Result; + } + + _NODISCARD static constexpr int _Prev_weekday(const int _Wday, const int _Shift) { + // 0 <= _Shift <= 6 + return (_Wday >= _Shift ? 0 : 7) + (_Wday - _Shift); + } + + _NODISCARD bool _Calculate_ymd_from_week_date(const int _Starting_wday, const int _Week, const int _Year) { + // (a) Calculate day-of-year of first _Starting_wday in January. + // (b) Shift *_Weekday so that it's relative to _Starting_wday. + // (c) Offset to desired week. + const int _Jan1_wday = _Jan1_weekday(_Year); + const int _Yday = 1 + _Prev_weekday(_Starting_wday, _Jan1_wday) // (a) + + _Prev_weekday(*_Weekday, _Starting_wday) // (b) + + 7 * (_Week - 1); // (c) + return _Update(_Day_of_year, _Yday) && !_Out_of_range(_Day_of_year, 1, _Days_in_year(_Year)) + && _Yday_to_month_day(*_Day_of_year, _Year); + } + + _NODISCARD bool _Calculate_ymd() { + bool _No_err = true; + // Flags to indicate if a field should be checked for consistency with other data. Set to false when the + // field is used to calculate the date, as it's necessarily self-consistent in that case (barring a bug). + bool _Check_u = true; + bool _Check_w = true; + bool _Check_iso = true; + bool _Check_wday = true; + + if (_Day_of_year && _Out_of_range(_Day_of_year, 1, 366)) { + return false; + } + + bool _Have_year = false; + int _Year{0}; + if (_Two_dig_year) { + _Year = *_Century + *_Two_dig_year; + _Have_year = true; + if (_Day_of_year) { + _No_err = _No_err && _Yday_to_month_day(*_Day_of_year, _Year); + } else if (_Week_u || _Week_w) { + _Check_wday = false; + if (_Week_u) { + _Check_u = false; + _No_err = _No_err && _Calculate_ymd_from_week_date(0 /*Sunday*/, *_Week_u, _Year); + } + + if (_Week_w) { + _Check_w = false; + _No_err = _No_err && _Calculate_ymd_from_week_date(1 /*Monday*/, *_Week_w, _Year); + } + } + } + + if (_Iso_year) { + // ISO weeks begin on Monday. W01 always contains January 4. There is no W00, so the beginning of + // January can be in W52 or W53 of the previous year. Likewise, the end of December can occur at the + // beginning of W01 of the following year, depending on where in the week Jan. 4 falls. + _Check_wday = false; + _Check_iso = false; + _Year = *_Iso_year; + _Have_year = true; + + // Shift weekdays to Monday-based. Jan. 4 is the anchor of week 1, so calculate where the parsed weekday + // is relative to that point. + const int _Jan4_wday = _Next_weekday(_Jan1_weekday(_Year), 3 - 1); + const int _Offset_from_jan4 = _Prev_weekday(*_Weekday, 1) - _Jan4_wday; + int _Trial_yday = 4 + 7 * (*_Iso_week - 1) + _Offset_from_jan4; + const int _Ref_num_days = _Trial_yday < 1 ? _Days_in_year(_Year - 1) : _Days_in_year(_Year); + if (_Trial_yday < 1) { + _Trial_yday += _Ref_num_days; + --_Year; + } else if (_Trial_yday > _Ref_num_days) { + _Trial_yday -= _Ref_num_days; + ++_Year; + } + + const auto _Year_parts = _Decompose_year(_Year); + _No_err = _No_err && _Update(_Day_of_year, _Trial_yday) && _Yday_to_month_day(*_Day_of_year, _Year) + && _Update(_Century, _Year_parts.first) && _Update(_Two_dig_year, _Year_parts.second); + } + + // Must have YMD by this point, either parsed directly or calculated above. + if (!_Have_year || !_Month || !_Day || !_No_err) { + return false; + } + + // consistency checks + if (_Check_wday && _Weekday) { + const auto _Era_year = _Year - (*_Month <= 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 + (*_Month > 2 ? -3 : 9)) + 19) >> 5) + *_Day - 1; + const int _Doe = ((1461 * _Yoe) >> 2) - _Yoe / 100 + _Yday_era; + _No_err = _No_err && _Update(_Weekday, (_Doe + _Era_begin_wday) % 7); + } + + if (_Check_u && _Week_u) { + _No_err = _No_err && _Update(_Week_u, (*_Day_of_year + 6 - *_Weekday) / 7); + } + + if (_Check_w && _Week_w) { + _No_err = _No_err && _Update(_Week_w, (*_Day_of_year + 6 - _Prev_weekday(*_Weekday, 1)) / 7); + } + + if (_Check_iso && _Iso_week) { + _No_err = _No_err && _Update(_Iso_week, _Iso8601_week(*_Day_of_year, *_Weekday, _Year)); + } + + return _No_err; + } + + _NODISCARD bool _Calculate_hour24() { + if (_Hour_12) { + return _Update(_Hour_24, _CHRONO make24(hours{*_Hour_12}, _Ampm.value_or(0)).count()); + } else { + return true; + } + } + + _NODISCARD bool _Calculate_year_fields() { + bool _No_err = true; + // The order of these updates is significant. Updating the ISO date second allows + // formats with %g and %y, but not %C, to get the century implicitly from %y. + if (_Two_dig_year && !_Century) { + _No_err = _No_err && _Update(_Century, _Two_dig_year >= 69 ? 1900 : 2000); + } + + // %C is only combined with %g if %G is missing, to avoid an unnecessary parse failure when the ISO and + // Gregorian years are in different centuries. + if (_Two_dig_iso_year && _Century && !_Iso_year) { + _No_err = _No_err && _Update(_Iso_year, *_Century + *_Two_dig_iso_year); + } + + return _No_err; + } + + _NODISCARD static bool _Out_of_range(const optional& _Field, const int _Min, const int _Max) { + return _Field && (_Field < _Min || _Max < _Field); + } + + _NODISCARD bool _Is_complete() const { + // Check for data that is incomplete, ambiguous, or obviously out-of-range. The exception is 12-hour time + // without am/pm. Most strptime implementations will assume am in this case, so we'll do that too. Don't + // check for consistency yet, because the data might not even be representable by the type being parsed, + // and calendar computations are relatively expensive. + + // Most time-of-day fields are deferred until we know if we're parsing a time_point. + if (_Out_of_range(_Hour_12, 1, 12) // + || _Out_of_range(_Weekday, 0, 6) // + || _Out_of_range(_Day, 1, 31) // + || _Out_of_range(_Month, 1, 12)) { + return false; + } + + if (_Iso_year || _Two_dig_iso_year || _Iso_week) { + // Need to have %G or %C+%g. The century can be parsed explicitly, or derived implicitly from %y. + const bool _Has_complete_year{_Iso_year || ((_Century || _Two_dig_year) && _Two_dig_iso_year)}; + if (!_Has_complete_year || !_Iso_week || !_Weekday || _Out_of_range(_Iso_week, 1, 53)) { + return false; + } + } + + if (_Week_u || _Week_w) { + // Need a weekday and year to be complete. + if (!_Weekday || !_Two_dig_year) { + return false; + } + + if (_Out_of_range(_Week_u, 0, 53) || _Out_of_range(_Week_w, 0, 53)) { + return false; + } + } + + return true; + } + + template + _NODISCARD static constexpr bool _Can_represent() { + using _Rep1 = typename _Duration1::rep; + using _Period1 = typename _Duration1::period; + using _Period2 = typename _Duration2::period; + + // Returns whether _Duration1 can represent _Duration2{1}. Assumes 1 <= _Period2 <= + // 86,400, i.e., we're interested in time periods between seconds and days. + if constexpr (is_integral_v<_Rep1>) { + // Must have _Period1 <= _Period2 and numeric_limits<_Rep1>::max() >= _Period2 / _Period1. For example, + // std::days can't represent std::seconds, and duration::max() is ~9.2 seconds. + constexpr auto _Max_tick = static_cast((numeric_limits>::max)()); + + // clang-format off + return ratio_less_equal_v<_Period1, _Period2> + && ratio_greater_equal_v, ratio_divide, _Period1>>; + // clang-format on + } else if (is_floating_point_v<_Rep1>) { + // With the smallest possible _Period1, ratio<1,INTMAX_MAX>, one day has a tick count of + // 86,400*INTMAX_MAX ~= 7.97e23. This is representable by float and double, so they can always represent + // at least one day. On the other hand, one second with the largest possible _Period1 needs a tick count + // of 1/(INTMAX_MAX) ~= 1.08e-19, which is also representable in both float and double. So, both + // floating point types can represent durations between one second and one day, regardless of _Period1. + return true; + } else { + // TRANSITION: user-defined arithmetic-like types + return true; + } + } + + template + _NODISCARD bool _Apply_duration_fields(_DurationType& _Result, const bool _For_time_point) { + constexpr bool _Can_rep_sec = _Can_represent<_DurationType, seconds>(); + constexpr bool _Can_rep_min = _Can_represent<_DurationType, minutes>(); + constexpr bool _Can_rep_hr = _Can_represent<_DurationType, hours>(); + constexpr bool _Can_rep_day = _Can_represent<_DurationType, days>(); + + const auto _Required{(_For_time_point ? _F_day | _F_mon | _F_year : 0)}; + const auto _Optional{(_For_time_point ? _F_wkday : 0) | (_Can_rep_sec ? _F_sec : 0) + | (_Can_rep_min ? _F_min : 0) | (_Can_rep_hr ? _F_hr : 0) + | (_Can_rep_day ? _F_doy : 0)}; + + const auto _Used{_Used_fields()}; + const auto _Time_out_of_range{ + _For_time_point + && (_Out_of_range(_Second, 0, 60) || _Out_of_range(_Minute, 0, 59) || _Out_of_range(_Hour_24, 0, 23))}; + + if (_Time_out_of_range || !_Test_bits(_Used, _Required, _Optional)) { + return false; + } + + _Result = _DurationType::zero(); + if constexpr (_Can_rep_sec) { + if (_Used & _F_sec) { + if (_Subsecond) { + using _CastedType = duration; + const _SubsecondType _Sub{*_Subsecond}; + if (treat_as_floating_point_v<_DurationType> // + || _CHRONO duration_cast<_CastedType>(_Sub) == _Sub) { + _Result += _CHRONO duration_cast<_DurationType>(_Sub); + } else { + return false; + } + } + + if (_Second) { + _Result += _CHRONO duration_cast<_DurationType>(seconds{*_Second}); + } + } + } + + if constexpr (_Can_rep_min) { + if (_Used & _F_min) { + _Result += _CHRONO duration_cast<_DurationType>(minutes{*_Minute}); + } + + if (_Utc_offset) { + _Result -= _CHRONO duration_cast<_DurationType>(minutes{*_Utc_offset}); + } + } + + if constexpr (_Can_rep_hr) { + if (_Used & _F_hr) { + _Result += _CHRONO duration_cast<_DurationType>(hours{*_Hour_24}); + } + } + + if constexpr (_Can_rep_day) { + if (_For_time_point) { + year_month_day _Ymd; + if (_Make_year_month_day(_Ymd, true)) { + _Result += _CHRONO duration_cast<_DurationType>(static_cast(_Ymd).time_since_epoch()); + } else { + return false; + } + } else if (_Used & _F_doy) { + _Result += _CHRONO duration_cast<_DurationType>(days{*_Day_of_year}); + } + } + + return true; + } + + template + _NODISCARD bool _Make_duration(duration<_Rep, _Period>& _Duration_result) { + const bool _Consistent = _Calculate_hour24(); + if (_Consistent) { + _Duration_result = duration<_Rep, _Period>::zero(); // TRANSITION: LWG-3536 & GH-1740 + } + + return _Consistent && _Apply_duration_fields(_Duration_result, false); + } + + enum class _LeapSecondRep : unsigned int { + _None, // tai_clock, gps_clock; oblivious to leap seconds + _Negative, // system_clock, observes negative, but not positive, leap seconds + _All, // utc_clock + _File_time // _Negative before 1 January 2017, _All afterwards. + }; + + template + _NODISCARD bool _Make_time_point(_DurationType& _Dur, _LeapSecondRep _Leap) { + + const bool _Consistent{_Calculate_hour24() && _Calculate_year_fields()}; + if (!_Consistent || !_Apply_duration_fields(_Dur, true)) { + return false; + } + + const _LeapSecondRep _Original_leap{_Leap}; + if (_Leap == _LeapSecondRep::_File_time) { + const int _Year{*_Century + *_Two_dig_year}; + _Leap = _Year <= 2016 ? _LeapSecondRep::_Negative : _LeapSecondRep::_All; + } + + if (_Second > (_Leap == _LeapSecondRep::_All ? 60 : 59)) { + return false; + } + + // A distinction has to be made here between clocks that tick monotonically, even around a positive leap + // second (everything except system_clock) and ones that have a special representation for leap seconds + // (utc_clock and, for negative leap seconds, system_clock). gps_clock and tai_clock, in particular, always + // monotonically count 86,400 seconds/day. The correct tick count, therefore, can be determined without + // knowing whether any leap seconds have occurred, and there aren't any invalid times due to negative leap + // second deletions. + // + // system_clock also has 86,400 seconds/day, but observes negative leap seconds by skipping them in its tick + // count. So leap second data is needed to check for a valid time, but not to calculate the tick count. + + if (_Leap != _LeapSecondRep::_None) { + if (_Hour_24 == 23 && _Minute == 59 && _Second >= 59) { + // It's possible that the parsed time doesn't exist because (a) _Seconds == 60 and there *isn't* a + // leap second insertion or (b) _Seconds == 59 and there *is* a leap second subtraction. + + // Check for quick exit. + const auto& _Tzdb{_CHRONO get_tzdb()}; + if (_Leap == _LeapSecondRep::_Negative && _Tzdb._All_ls_positive) { + return true; + } + + const bool _Possible_insertion{_Second == 60}; + const sys_seconds _Target_sys_time{ + _CHRONO floor(_Dur) + (_Possible_insertion ? seconds{0} : seconds{1})}; + const auto& _Ls_vector{_Tzdb.leap_seconds}; + const auto _It{_STD lower_bound(_Ls_vector.begin(), _Ls_vector.end(), _Target_sys_time)}; + const bool _Match_leap{_It != _Ls_vector.end() && *_It == _Target_sys_time}; + + // Condition for a good parse: (_Seconds == 59 && !_Match_leap) || (_Match_leap && + // _It->_Is_positive()). Below is the inverse of this. + if (!(_Match_leap ? _It->_Positive() : !_Possible_insertion)) { + return false; + } + } + + if (_Leap == _LeapSecondRep::_All) { + constexpr bool _Can_rep_sec = _Can_represent<_DurationType, seconds>(); + if constexpr (_Can_rep_sec) { + _Dur = utc_clock::from_sys(sys_time<_DurationType>{_Dur}).time_since_epoch(); + // If we parsed a positive leap second, then _Dur, interpreted as a system time, refers to the + // second *after* insertion. Need to back up one second in this case. + if (_Second == 60) { + _Dur -= _CHRONO duration_cast<_DurationType>(seconds{1}); + } + + if (_Original_leap == _LeapSecondRep::_File_time) { + _Dur -= _CHRONO duration_cast<_DurationType>( + filesystem::_File_time_clock::_Skipped_filetime_leap_seconds); + } + } else { + // Error if _Dur can't represent the above adjustment. + return false; + } + } + } + + return true; + } + + _NODISCARD bool _Make_day(day& _Day_result) { + if (_Used_fields() == _F_day) { + _Day_result = day{static_cast(*_Day)}; + return true; + } else { + return false; + } + } + + _NODISCARD bool _Make_weekday(weekday& _Weekday_result) { + if (_Used_fields() == _F_wkday) { + _Weekday_result = weekday{static_cast(*_Weekday)}; + return true; + } else { + return false; + } + } + + _NODISCARD bool _Make_month(month& _Month_result) { + if (_Used_fields() == _F_mon) { + _Month_result = month{static_cast(*_Month)}; + return true; + } else { + return false; + } + } + + _NODISCARD bool _Make_month_day(month_day& _Month_day_result) { + if (_Day_of_year && !_Yday_to_month_day(*_Day_of_year, 0)) { + return false; + } + + constexpr auto _Required = _F_mon | _F_day; + constexpr auto _Optional = _F_doy; + if (_Test_bits(_Used_fields(), _Required, _Optional)) { + _Month_day_result = + month_day{month{static_cast(*_Month)}, day{static_cast(*_Day)}}; + return true; + } else { + return false; + } + } + + _NODISCARD bool _Make_year(year& _Year_result) { + if (_Calculate_year_fields() && _Used_fields() == _F_year) { + _Year_result = year{*_Century + *_Two_dig_year}; + return true; + } else { + return false; + } + } + + _NODISCARD bool _Make_year_month(year_month& _Year_month_result) { + if (_Calculate_year_fields() && _Used_fields() == (_F_mon | _F_year)) { + _Year_month_result = + year_month{year{*_Century + *_Two_dig_year}, month{static_cast(*_Month)}}; + return true; + } else { + return false; + } + } + + _NODISCARD bool _Make_year_month_day( + year_month_day& _Year_month_day_result, const bool _For_time_point = false) { + const bool _Consistent = _Calculate_year_fields() && _Calculate_ymd(); + constexpr auto _Required = _F_day | _F_mon | _F_year; + auto _Optional = _F_wkday | _F_doy; + if (_For_time_point) { + // These fields aren't used here, but they might be used later if converting to a time_point. + _Optional |= _F_sec | _F_min | _F_hr; + } + + if (_Consistent && _Test_bits(_Used_fields(), _Required, _Optional)) { + _Year_month_day_result = year_month_day{year{*_Century + *_Two_dig_year}, + month{static_cast(*_Month)}, day{static_cast(*_Day)}}; + return true; + } else { + return false; + } + } + + template + _NODISCARD _InIt _Parse_time_field(_InIt _First, ios_base& _Iosbase, ios_base::iostate& _State, + const char _Flag, const char _Modifier, const unsigned int _Width, + const unsigned int _Subsecond_precision) { + using _CharT = typename _InIt::value_type; + + const auto& _Ctype_fac = _STD use_facet>(_Iosbase.getloc()); + const auto& _Time_fac = _STD use_facet>(_Iosbase.getloc()); + constexpr _InIt _Last{}; + + int _Val{0}; + switch (_Flag) { + case 'a': + case 'A': + { + tm _Tp; + _Tp.tm_wday = _Invalid_time_field; + _First = _Time_fac.get(_First, _Last, _Iosbase, _State, &_Tp, 'a'); + if (!_Update(_Weekday, _Tp.tm_wday)) { + _State |= ios_base::failbit; + } + break; + } + + case 'b': + case 'B': + case 'h': + { + tm _Tp; + _Tp.tm_mon = _Invalid_time_field; + _First = _Time_fac.get(_First, _Last, _Iosbase, _State, &_Tp, 'b'); + if (_Tp.tm_mon == _Invalid_time_field || !_Update(_Month, ++_Tp.tm_mon)) { + _State |= ios_base::failbit; + } + break; + } + + case 'C': + if (_Modifier != 'E') { + _State |= _Get_int(_First, _Width == 0 ? 2u : _Width, _Val, _Ctype_fac); + _Val *= 100; + } else { + tm _Tp; + _Tp.tm_year = _Invalid_time_field; + _First = _Time_fac.get(_First, _Last, _Iosbase, _State, &_Tp, 'C', 'E'); + _Val = _Tp.tm_year; + if (_Tp.tm_year != _Invalid_time_field) { + _Val += 1900; + } + } + + if (!_Update(_Century, _Val)) { + _State |= ios_base::failbit; + } + break; + + case 'd': + case 'e': + if (_Modifier != 'O') { + _State |= _Get_int(_First, _Width == 0 ? 2u : _Width, _Val, _Ctype_fac); + } else { + tm _Tp; + _Tp.tm_mday = _Invalid_time_field; + _First = _Time_fac.get(_First, _Last, _Iosbase, _State, &_Tp, 'd', 'O'); + _Val = _Tp.tm_mday; + } + + if (!_Update(_Day, _Val)) { + _State |= ios_base::failbit; + } + break; + + case 'D': + _First = _Parse_time_field_restricted(_First, _Iosbase, _State, "%m/%d/%y"); + break; + + case 'F': + { + // If modified with a width N, the width is applied to only %Y. + _State |= _Get_int(_First, _Width == 0 ? 4u : _Width, _Val, _Ctype_fac); + const auto _Year_parts = _Decompose_year(_Val); + if (_Update(_Century, _Year_parts.first) && _Update(_Two_dig_year, _Year_parts.second)) { + _First = _Parse_time_field_restricted(_First, _Iosbase, _State, "-%m-%d"); + } else { + _State |= ios_base::failbit; + } + break; + } + + case 'g': + _State |= _Get_int(_First, _Width == 0 ? 2u : _Width, _Val, _Ctype_fac); + if (!_Update(_Two_dig_iso_year, _Val)) { + _State |= ios_base::failbit; + } + break; + + case 'G': + { + _State |= _Get_int(_First, _Width == 0 ? 4u : _Width, _Val, _Ctype_fac); + const auto _Year_parts = _Decompose_year(_Val); + if (!_Update(_Iso_year, _Val) || !_Update(_Two_dig_iso_year, _Year_parts.second)) { + _State |= ios_base::failbit; + } + break; + } + + case 'H': + if (_Modifier != 'O') { + _State |= _Get_int(_First, _Width == 0 ? 2u : _Width, _Val, _Ctype_fac); + } else { + tm _Tp; + _Tp.tm_hour = _Invalid_time_field; + _First = _Time_fac.get(_First, _Last, _Iosbase, _State, &_Tp, 'H', 'O'); + _Val = _Tp.tm_hour; + } + + if (!_Update(_Hour_24, _Val) + || (_Val < 24 + && (!_Update(_Ampm, _Val >= 12 ? 1 : 0) + || !_Update(_Hour_12, _CHRONO make12(hours{_Val}).count())))) { + _State |= ios_base::failbit; + } + break; + + case 'I': + if (_Modifier != 'O') { + _State |= _Get_int(_First, _Width == 0 ? 2u : _Width, _Val, _Ctype_fac); + } else { + tm _Tp; + _Tp.tm_hour = _Invalid_time_field; + _First = _Time_fac.get(_First, _Last, _Iosbase, _State, &_Tp, 'I', 'O'); + _Val = (_Tp.tm_hour == 0) ? 12 : _Tp.tm_hour; + } + + if (!_Update(_Hour_12, _Val)) { + _State |= ios_base::failbit; + } + break; + + case 'j': + _State |= _Get_int(_First, _Width == 0 ? 3u : _Width, _Val, _Ctype_fac); + if (!_Update(_Day_of_year, _Val)) { + _State |= ios_base::failbit; + } + break; + + case 'M': + if (_Modifier != 'O') { + _State |= _Get_int(_First, _Width == 0 ? 2u : _Width, _Val, _Ctype_fac); + } else { + tm _Tp; + _Tp.tm_min = _Invalid_time_field; + _First = _Time_fac.get(_First, _Last, _Iosbase, _State, &_Tp, 'M', 'O'); + _Val = _Tp.tm_min; + } + + if (!_Update(_Minute, _Val)) { + _State |= ios_base::failbit; + } + break; + + case 'm': + if (_Modifier != 'O') { + _State |= _Get_int(_First, _Width == 0 ? 2u : _Width, _Val, _Ctype_fac); + } else { + tm _Tp; + _Initialize_time_point(_Tp); + _First = _Time_fac.get(_First, _Last, _Iosbase, _State, &_Tp, 'm', 'O'); + _Val = _Tp.tm_mon; + if (_Tp.tm_mon != _Invalid_time_field) { + ++_Val; + } + } + + if (!_Update(_Month, _Val)) { + _State |= ios_base::failbit; + } + break; + + case 'p': + { + tm _Tp; + _Tp.tm_hour = 0; + _First = _Time_fac.get(_First, _Last, _Iosbase, _State, &_Tp, 'p'); + if (!_Update(_Ampm, _Tp.tm_hour == 0 ? 0 : 1)) { + _State |= ios_base::failbit; + } + break; + } + + case 'c': + case 'r': + case 'x': + case 'X': + { + tm _Tp; + _Initialize_time_point(_Tp); + const bool _Full_year = (_Flag == 'c'); // 'x' reads two-digit year, 'r' and 'X' read times + _First = _Time_fac.get(_First, _Last, _Iosbase, _State, &_Tp, _Flag, _Modifier); + if (!_Update_if_valid(_Tp, _Full_year)) { + _State |= ios_base::failbit; + } + break; + } + + case 'R': + _First = _Parse_time_field_restricted(_First, _Iosbase, _State, "%H:%M"); + break; + + case 'T': + _First = _Parse_time_field_restricted(_First, _Iosbase, _State, "%H:%M:%S", _Subsecond_precision); + break; + + case 'S': + if (_Subsecond_precision == 0) { + _State |= _Get_int(_First, _Width == 0 ? 2u : _Width, _Val, _Ctype_fac); + if (!_Update(_Second, _Val)) { + _State |= ios_base::failbit; + } + } else { + const auto& _Numpunct_fac = _STD use_facet>(_Iosbase.getloc()); + _State |= + _Get_fixed(_First, _Width == 0 ? 3 + _Subsecond_precision : _Width, _Ctype_fac, _Numpunct_fac); + } + break; + + case 'u': + case 'w': + if (_Flag == 'w' && _Modifier == 'O') { + tm _Tp; + _Tp.tm_wday = _Invalid_time_field; + _First = _Time_fac.get(_First, _Last, _Iosbase, _State, &_Tp, 'w', 'O'); + _Val = _Tp.tm_wday; + } else { + _State |= _Get_int(_First, _Width == 0 ? 1u : _Width, _Val, _Ctype_fac); + if (_Flag == 'u') { + // ISO weekday: [1,7], 7 == Sunday + if (_Val == 7) { + _Val = 0; + } else if (_Val == 0) { + _Val = 7; // out of range + } + } + } + + if (!_Update(_Weekday, _Val)) { + _State |= ios_base::failbit; + } + break; + + case 'U': + case 'V': + case 'W': + { + _State |= _Get_int(_First, _Width == 0 ? 2u : _Width, _Val, _Ctype_fac); + auto& _Field{(_Flag == 'U') ? _Week_u : (_Flag == 'W' ? _Week_w : _Iso_week)}; + if (!_Update(_Field, _Val)) { + _State |= ios_base::failbit; + } + break; + } + + case 'y': + if (_Modifier == '\0') { + _State |= _Get_int(_First, _Width == 0 ? 2u : _Width, _Val, _Ctype_fac); + } else { + tm _Tp; + _Tp.tm_year = _Invalid_time_field; + _First = _Time_fac.get(_First, _Last, _Iosbase, _State, &_Tp, 'y', _Modifier); + if (_Modifier == 'E') { + _Val = _Tp.tm_year + 1900; // offset from %EC base year + } else { + const auto _Year_parts = _Decompose_year(_Tp.tm_year); + _Val = _Year_parts.second; + } + } + + if (!_Update(_Two_dig_year, _Val)) { + _State |= ios_base::failbit; + } + break; + + case 'Y': + { + if (_Modifier == 'E') { + tm _Tp; + _Tp.tm_year = _Invalid_time_field; + _First = _Time_fac.get(_First, _Last, _Iosbase, _State, &_Tp, 'Y', 'E'); + _Val = _Tp.tm_year + 1900; + } else { + _State |= _Get_int(_First, _Width == 0 ? 4u : _Width, _Val, _Ctype_fac); + } + + const auto _Year_parts = _Decompose_year(_Val); + if (!_Update(_Century, _Year_parts.first) || !_Update(_Two_dig_year, _Year_parts.second)) { + _State |= ios_base::failbit; + } + break; + } + + case 'z': + _State |= _Get_tz_offset(_First, _Ctype_fac, _Modifier == 'E' || _Modifier == 'O', _Val); + if (!_Update(_Utc_offset, _Val)) { + _State |= ios_base::failbit; + } + break; + + case 'Z': + { + string _Name; + _State |= _Get_tz_name(_First, _Ctype_fac, _Name); + if (!_Update(_Tz_name, _Name)) { + _State |= ios_base::failbit; + } + break; + } + + default: + // Invalid flag + _State |= ios_base::failbit; + break; + } + + return _First; + } + + template + _NODISCARD _InIt _Parse_time_field_restricted(_InIt _First, ios_base& _Iosbase, ios_base::iostate& _State, + const char* _Fmt, const unsigned int _Subsecond_precision = 0) { + using _Ctype = ctype; + // Parses a restricted format string. It generally doesn't handle anything parsed outside of + // _Parse_time_field: + // (a) any whitespace (' ', %n, %t) + // (b) %% literal (other literals are parsed, however) + // (c) E or O modifiers + // (d) width parameter + // It also assumes a valid format string, specifically that '%' is always followed by a flag. + const _Ctype& _Ctype_fac{_STD use_facet<_Ctype>(_Iosbase.getloc())}; + constexpr _InIt _Last{}; + + while (*_Fmt != '\0' && _First != _Last && _State == ios_base::goodbit) { + if (*_Fmt == '%') { + _First = _Parse_time_field(_First, _Iosbase, _State, *++_Fmt, '\0', 0, _Subsecond_precision); + } else if (_Ctype_fac.narrow(*_First++) != *_Fmt) { + _State |= ios_base::failbit; + } + ++_Fmt; + } + return _First; + } + + template + _NODISCARD ios_base::iostate _Get_fixed(_InIt& _First, unsigned int _Width, + const ctype& _Ctype_fac, + const numpunct& _Numpunct_fac) { + constexpr _InIt _Last{}; + + while (_First != _Last && _Ctype_fac.is(ctype_base::space, *_First) && _Width > 0) { + ++_First; + --_Width; + } + + int _Second_ = 0; + int64_t _Subsecond_ = 0; + int64_t _Multiplier = _SubsecondType::period::den; + bool _Found_point = false; + bool _Found_digit = false; + + while (_First != _Last && _Width > 0 && _Multiplier >= 10) { + const auto _Ch = *_First; + if (_Ch == _Numpunct_fac.decimal_point() && !_Found_point) { + _Found_point = true; + } else if (_Ctype_fac.is(ctype_base::digit, _Ch)) { + _Found_digit = true; + const auto _Digit = _Ctype_fac.narrow(_Ch) - '0'; + if (_Found_point) { + _Multiplier /= 10; + _Subsecond_ += _Digit * _Multiplier; + } else { + if (_Second_ > ((numeric_limits::max)() - _Digit) / 10) { + return ios_base::failbit; + } + + _Second_ = _Second_ * 10 + _Digit; + } + } else { + break; + } + ++_First; + --_Width; + } + + ios_base::iostate _State = ios_base::goodbit; + if (_First == _Last) { + _State |= ios_base::eofbit; + } + + if (!(_Found_digit && _Update(_Second, _Second_) && _Update(_Subsecond, _Subsecond_))) { + _State |= ios_base::failbit; + } + + return _State; + } + + template + _NODISCARD ios_base::iostate _Get_int( + _InIt& _First, unsigned int _Width, int& _Val, const ctype& _Ctype_fac) { + constexpr _InIt _Last{}; + + while (_First != _Last && _Ctype_fac.is(ctype_base::space, *_First) && _Width > 0) { + ++_First; + --_Width; + } + + char _Ac[_MAX_INT_DIG]; + char* _Ptr = _Ac; + if (_First != _Last && _Width > 0) { + const char _Ch = _Ctype_fac.narrow(*_First); + if (_Ch == '+' || _Ch == '-') { // copy sign + *_Ptr++ = _Ch; + ++_First; + --_Width; + } + } + + bool _Has_leading_zero = false; + while (_First != _Last && _Width > 0 && _Ctype_fac.narrow(*_First) == '0') { // strip leading zeros + ++_First; + --_Width; + _Has_leading_zero = true; + } + + if (_Has_leading_zero) { + *_Ptr++ = '0'; + } + + char _Ch; + while (_First != _Last && _Width > 0 && '0' <= (_Ch = _Ctype_fac.narrow(*_First)) + && _Ch <= '9') { // copy digits + *_Ptr = _Ch; + if (_Ptr < _STD cend(_Ac)) { + ++_Ptr; // drop trailing digits if already too large + } + ++_First; + --_Width; + } + + *_Ptr = '\0'; + int _Errno = 0; + char* _Ep; + const long _Ans = _CSTD _Stolx(_Ac, &_Ep, 10, &_Errno); + ios_base::iostate _State = ios_base::goodbit; + + if (_First == _Last) { + _State |= ios_base::eofbit; + } + + if (_Ep == _Ac || _Errno != 0) { + _State |= ios_base::failbit; // bad conversion + } else { + _Val = _Ans; // store valid result + } + + return _State; + } + + template + _NODISCARD ios_base::iostate _Get_tz_offset( + _InIt& _First, const ctype& _Ctype_fac, const bool _Is_modified, int& _Offset) { + constexpr _InIt _Last{}; + if (_First == _Last) { + return ios_base::eofbit; + } + + bool _Negative = false; + switch (_Ctype_fac.narrow(*_First)) { + case '-': + _Negative = true; + [[fallthrough]]; + case '+': + ++_First; + break; + } + + // For a regular offset hh[mm], simply read four digits, with the option of an EOF or non-digit after + // reading two. The modified form h[h][:mm] is similar, except for the following points: + // (a) an EOF or non-digit is allowable after reading *one* digit, not two. + // (b) after reading one digit, another optional digit keeps us in the same state, except for decrementing + // the number of optional digits allowed. In this state, reading a ':' allows parsing to continue. + + int _Tz_hours = 0; + int _Tz_minutes = 0; + int _Optional_digits = 1; + for (int _Count = 0; _Count < 4; ++_Count) { + const bool _Allow_match_fail{_Count == (_Is_modified ? 1 : 2)}; + + if (_First == _Last) { + if (_Allow_match_fail) { + break; + } else { + return ios_base::eofbit | ios_base::failbit; + } + } + + const char _Ch = _Ctype_fac.narrow(*_First++); + const bool _Is_digit = ('0' <= _Ch && _Ch <= '9'); + if (_Is_modified && _Count == 1) { + if (_Ch == ':') { + continue; + } else if (_Is_digit && _Optional_digits > 0) { + _Tz_hours = 10 * _Tz_hours + (_Ch - '0'); + --_Optional_digits; + --_Count; + } else { + if (_Allow_match_fail) { + break; + } else { + return ios_base::failbit; + } + } + } else if (_Is_digit) { + auto& _Part = _Count < 2 ? _Tz_hours : _Tz_minutes; + _Part = 10 * _Part + (_Ch - '0'); + } else { + if (_Allow_match_fail) { + break; + } else { + return ios_base::failbit; + } + } + } + + _Offset = 60 * _Tz_hours + _Tz_minutes; + if (_Negative) { + _Offset = -_Offset; + } + return ios_base::goodbit; + } + + template + _NODISCARD ios_base::iostate _Get_tz_name( + _InIt& _First, const ctype& _Ctype_fac, string& _Tz_name) { + constexpr _InIt _Last{}; + _Tz_name.clear(); + while (_First != _Last) { + const char _Ch{_Ctype_fac.narrow(*_First)}; + if (_STD isalnum(static_cast(_Ch)) || _Ch == '_' || _Ch == '/' || _Ch == '-' + || _Ch == '+') { + _Tz_name.push_back(_Ch); + ++_First; + } else { + break; + } + } + return _First == _Last ? ios_base::eofbit : ios_base::goodbit; + } + + template > + _Time_parse_fields(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _FmtFirst, + basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, minutes* _Offset = nullptr, + unsigned int _Subsecond_precision = 0) { + + using _Myis = basic_istream<_CharT, _Traits>; + + const auto& _Ctype_fac = _STD use_facet>(_Istr.getloc()); + ios_base::iostate _State = ios_base::goodbit; + const _CharT* const _FmtLast = _FmtFirst + _Traits::length(_FmtFirst); + const typename _Myis::sentry _Ok{_Istr, true}; + + istreambuf_iterator _First{_Istr}; + constexpr decltype(_First) _Last{}; + + if (_Ok) { + _TRY_IO_BEGIN + for (; _FmtFirst != _FmtLast && _State == ios_base::goodbit; ++_FmtFirst) { + if (_First == _Last) { + // EOF is not an error if the remaining flags can match zero characters. + for (; _FmtFirst != _FmtLast; ++_FmtFirst) { + char _Flag{}; + if (_Ctype_fac.is(ctype_base::space, *_FmtFirst)) { + _Flag = ' '; + } else { + if (_Ctype_fac.narrow(*_FmtFirst) == '%' && ++_FmtFirst != _FmtLast) { + _Flag = _Ctype_fac.narrow(*_FmtFirst); + } + } + + if (_Flag != ' ' && _Flag != 't') { + _State |= ios_base::failbit | ios_base::eofbit; + break; + } + } + break; + } else if (_Ctype_fac.narrow(*_FmtFirst) != '%') { // match literal element + if (_Ctype_fac.is(ctype_base::space, *_FmtFirst)) { + while (_First != _Last && _Ctype_fac.is(ctype_base::space, *_First)) { + ++_First; + } + } else if (*_First == *_FmtFirst) { + ++_First; + } else { + _State |= ios_base::failbit; // bad literal match + } + } else if (++_FmtFirst == _FmtLast) { // not enough for a valid flag + _State |= ios_base::failbit; + } else { // get flag after % + char _Flag = _Ctype_fac.narrow(*_FmtFirst); + char _Modifier = '\0'; + unsigned int _Width = 0; + + if (_Flag == 'E' || _Flag == 'O') { + if (++_FmtFirst == _FmtLast) { + _State |= ios_base::failbit; + break; + } + _Modifier = _STD exchange(_Flag, _Ctype_fac.narrow(*_FmtFirst)); + } else if ('0' <= _Flag && _Flag <= '9') { + _Width = static_cast(_Flag - '0'); + while (++_FmtFirst != _FmtLast && _Ctype_fac.is(ctype_base::digit, *_FmtFirst)) { + const auto _Digit = static_cast(_Ctype_fac.narrow(*_FmtFirst) - '0'); + if (_Width > ((numeric_limits::max)() - _Digit) / 10) { + _State |= ios_base::failbit; + break; + } + _Width = 10 * _Width + _Digit; + } + if (_FmtFirst == _FmtLast) { + _State |= ios_base::failbit; + break; + } + _Flag = _Ctype_fac.narrow(*_FmtFirst); + } + + switch (_Flag) { + case 'n': // exactly one space + if (!_Ctype_fac.is(ctype_base::space, *_First++)) { + _State |= ios_base::failbit; + } + break; + + case 't': // zero or one space + if (_Ctype_fac.is(ctype_base::space, *_First)) { + ++_First; + } + break; + + case '%': + if (_Ctype_fac.narrow(*_First++) != '%') { + _State |= ios_base::failbit; + } + break; + + default: + _First = _Parse_time_field(_First, _Istr, _State, _Flag, _Modifier, _Width, + _Subsecond_precision); // convert a single field + break; + } + } + } + + _CATCH_IO_(_Myis, _Istr) + } + + if (!_Is_complete()) { + _State |= ios_base::failbit; + } + + if (!(_State & ios_base::failbit)) { + if (_Offset != nullptr && _Utc_offset) { + *_Offset = minutes{*_Utc_offset}; + } + + if (_Abbrev != nullptr && _Tz_name) { + if constexpr (is_same_v) { + *_Abbrev = _STD move(*_Tz_name); + } else { + _Abbrev->clear(); + for (const char& _Ch : *_Tz_name) { + _Abbrev->push_back(_Ctype_fac.widen(_Ch)); + } + } + } + } + + _Istr.setstate(_State); + } + }; + + // FUNCTION TEMPLATE from_stream + template > + basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _Fmt, + duration<_Rep, _Period>& _Duration, basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, + minutes* _Offset = nullptr) { + _Time_parse_fields _Time{_Istr, _Fmt, _Abbrev, _Offset, hh_mm_ss>::fractional_width}; + if (_Istr && !_Time._Make_duration(_Duration)) { + _Istr.setstate(ios_base::failbit); + } + return _Istr; + } + + template > + basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _Fmt, weekday& _Wd, + basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, minutes* _Offset = nullptr) { + _Time_parse_fields _Time{_Istr, _Fmt, _Abbrev, _Offset}; + if (_Istr && !_Time._Make_weekday(_Wd)) { + _Istr.setstate(ios_base::failbit); + } + return _Istr; + } + + template > + basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _Fmt, day& _Day, + basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, minutes* _Offset = nullptr) { + _Time_parse_fields _Time{_Istr, _Fmt, _Abbrev, _Offset}; + if (_Istr && !_Time._Make_day(_Day)) { + _Istr.setstate(ios_base::failbit); + } + return _Istr; + } + + template > + basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _Fmt, + month& _Month, basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, minutes* _Offset = nullptr) { + _Time_parse_fields _Time{_Istr, _Fmt, _Abbrev, _Offset}; + if (_Istr && !_Time._Make_month(_Month)) { + _Istr.setstate(ios_base::failbit); + } + return _Istr; + } + + template > + basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _Fmt, + month_day& _Md, basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, minutes* _Offset = nullptr) { + _Time_parse_fields _Time{_Istr, _Fmt, _Abbrev, _Offset}; + if (_Istr && !_Time._Make_month_day(_Md)) { + _Istr.setstate(ios_base::failbit); + } + return _Istr; + } + + template > + basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _Fmt, year& _Year, + basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, minutes* _Offset = nullptr) { + _Time_parse_fields _Time{_Istr, _Fmt, _Abbrev, _Offset}; + if (_Istr && !_Time._Make_year(_Year)) { + _Istr.setstate(ios_base::failbit); + } + return _Istr; + } + + template > + basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _Fmt, + year_month& _Ym, basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, minutes* _Offset = nullptr) { + _Time_parse_fields _Time{_Istr, _Fmt, _Abbrev, _Offset}; + if (_Istr && !_Time._Make_year_month(_Ym)) { + _Istr.setstate(ios_base::failbit); + } + return _Istr; + } + + template > + basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _Fmt, + year_month_day& _Ymd, basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, minutes* _Offset = nullptr) { + _Time_parse_fields _Time{_Istr, _Fmt, _Abbrev, _Offset}; + if (_Istr && !_Time._Make_year_month_day(_Ymd)) { + _Istr.setstate(ios_base::failbit); + } + return _Istr; + } + + template > + basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _Fmt, + utc_time<_Duration>& _Tp, basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, + minutes* _Offset = nullptr) { + _Time_parse_fields _Time{_Istr, _Fmt, _Abbrev, _Offset}; + _Duration _Dur; + if (_Istr && _Time._Make_time_point(_Dur, _Time_parse_fields::_LeapSecondRep::_All)) { + _Tp = utc_time<_Duration>{_Dur}; + } else { + _Istr.setstate(ios_base::failbit); + } + return _Istr; + } + + template > + basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _Fmt, + sys_time<_Duration>& _Tp, basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, + minutes* _Offset = nullptr) { + _Time_parse_fields _Time{_Istr, _Fmt, _Abbrev, _Offset}; + _Duration _Dur; + if (_Istr && _Time._Make_time_point(_Dur, _Time_parse_fields::_LeapSecondRep::_Negative)) { + _Tp = sys_time<_Duration>{_Dur}; + } else { + _Istr.setstate(ios_base::failbit); + } + return _Istr; + } + + template > + basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _Fmt, + tai_time<_Duration>& _Tp, basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, + minutes* _Offset = nullptr) { + _Time_parse_fields _Time{_Istr, _Fmt, _Abbrev, _Offset}; + _Duration _Dur; + if (_Istr && _Time._Make_time_point(_Dur, _Time_parse_fields::_LeapSecondRep::_None)) { + _Tp = tai_time<_Duration>{_Dur + _CHRONO duration_cast<_Duration>(days{4383})}; + } else { + _Istr.setstate(ios_base::failbit); + } + return _Istr; + } + + template > + basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _Fmt, + gps_time<_Duration>& _Tp, basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, + chrono::minutes* _Offset = nullptr) { + _Time_parse_fields _Time{_Istr, _Fmt, _Abbrev, _Offset}; + _Duration _Dur; + if (_Istr && _Time._Make_time_point(_Dur, _Time_parse_fields::_LeapSecondRep::_None)) { + _Tp = gps_time<_Duration>{_Dur - _CHRONO duration_cast<_Duration>(days{3657})}; + } else { + _Istr.setstate(ios_base::failbit); + } + return _Istr; + } + + template > + basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _Fmt, + file_time<_Duration>& _Tp, basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, + chrono::minutes* _Offset = nullptr) { + _Time_parse_fields _Time{_Istr, _Fmt, _Abbrev, _Offset}; + _Duration _Dur; + if (_Istr && _Time._Make_time_point(_Dur, _Time_parse_fields::_LeapSecondRep::_File_time)) { + constexpr auto _File_clock_adj{_CHRONO duration_cast<_Duration>( + filesystem::_File_time_clock::duration{filesystem::__std_fs_file_time_epoch_adjustment})}; + _Tp = file_time<_Duration>{_Dur} + _File_clock_adj; + } else { + _Istr.setstate(ios_base::failbit); + } + return _Istr; + } + + template > + basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& _Istr, const _CharT* _Fmt, + local_time<_Duration>& _Tp, basic_string<_CharT, _Traits, _Alloc>* _Abbrev = nullptr, + minutes* _Offset = nullptr) { + _Time_parse_fields _Time{_Istr, _Fmt, _Abbrev, _Offset}; + _Duration _Dur; + if (_Istr && _Time._Make_time_point(_Dur, _Time_parse_fields::_LeapSecondRep::_None)) { + _Tp = local_time<_Duration>{_Dur}; + } else { + _Istr.setstate(ios_base::failbit); + } + return _Istr; + } + + template + struct _Time_parse_iomanip { + _Time_parse_iomanip(const basic_string<_CharT, _Traits, _Alloc>& _Fmt_, _Parsable& _Tp_, + basic_string<_CharT, _Traits, _Alloc>* _Abbrev_ = nullptr, minutes* _Offset_ = nullptr) + : _Fmt{_Fmt_}, _Tp{_Tp_}, _Abbrev{_Abbrev_}, _Offset{_Offset_} {} + + const basic_string<_CharT, _Traits, _Alloc>& _Fmt; + _Parsable& _Tp; + basic_string<_CharT, _Traits, _Alloc>* _Abbrev; + minutes* _Offset; + }; + + // FUNCTION TEMPLATE parse + template &>(), + _STD declval(), _STD declval<_Parsable&>()))>> + _NODISCARD auto parse(const basic_string<_CharT, _Traits, _Alloc>& _Fmt, _Parsable& _Tp) { + return _Time_parse_iomanip{_Fmt, _Tp}; + } + + template &>(), _STD declval(), + _STD declval<_Parsable&>(), _STD declval*>()))>> + _NODISCARD auto parse(const basic_string<_CharT, _Traits, _Alloc>& _Fmt, _Parsable& _Tp, + basic_string<_CharT, _Traits, _Alloc>& _Abbrev) { + return _Time_parse_iomanip{_Fmt, _Tp, _STD addressof(_Abbrev)}; + } + + template &>(), + _STD declval(), _STD declval<_Parsable&>(), + _STD declval*>(), _STD declval()))>> + _NODISCARD auto parse(const basic_string<_CharT, _Traits, _Alloc>& _Fmt, _Parsable& _Tp, minutes& _Offset) { + return _Time_parse_iomanip{_Fmt, _Tp, static_cast*>(nullptr), &_Offset}; + } + + template &>(), + _STD declval(), _STD declval<_Parsable&>(), + _STD declval*>(), _STD declval()))>> + _NODISCARD auto parse(const basic_string<_CharT, _Traits, _Alloc>& _Fmt, _Parsable& _Tp, + basic_string<_CharT, _Traits, _Alloc>& _Abbrev, minutes& _Offset) { + return _Time_parse_iomanip{_Fmt, _Tp, _STD addressof(_Abbrev), &_Offset}; + } + + template + basic_istream<_CharT, _Traits>& operator>>( + basic_istream<_CharT, _Traits>& _Is, const _Time_parse_iomanip<_CharT, _Traits, _Alloc, _Parsable>& _Tpi) { + return _CHRONO from_stream(_Is, _Tpi._Fmt.c_str(), _Tpi._Tp, _Tpi._Abbrev, _Tpi._Offset); + } #endif // _HAS_CXX20 } // namespace chrono diff --git a/tests/std/tests/P0355R7_calendars_and_time_zones_io/test.cpp b/tests/std/tests/P0355R7_calendars_and_time_zones_io/test.cpp index fefbba69291..7b3c5fa40bd 100644 --- a/tests/std/tests/P0355R7_calendars_and_time_zones_io/test.cpp +++ b/tests/std/tests/P0355R7_calendars_and_time_zones_io/test.cpp @@ -1,17 +1,24 @@ // Copyright (c) Microsoft Corporation. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +#include #include +#include #include #include +#include +#include #include #include #include #include +#include +#include #include +#include using namespace std; -using chrono::duration; +using namespace std::chrono; template bool test_duration_basic_out(const duration& d, const CharT* expected) { @@ -119,7 +126,1010 @@ void test_duration_output() { assert(test_duration_locale_out()); } + +template +void test_parse(const CharT* str, const CharT* fmt, Parsable& p, type_identity_t*> abbrev = nullptr, + minutes* offset = nullptr) { + p = Parsable{}; + if (abbrev) { + if constexpr (is_same_v) { + *abbrev = _Time_parse_fields::_Invalid_time_string; + } else { + *abbrev = L"!"; + } + } + + basic_stringstream sstr{str}; + if (abbrev) { + if (offset) { + sstr >> parse(basic_string{fmt}, p, *abbrev, *offset); + } else { + sstr >> parse(basic_string{fmt}, p, *abbrev); + } + } else { + if (offset) { + sstr >> parse(basic_string{fmt}, p, *offset); + } else { + sstr >> parse(basic_string{fmt}, p); + } + } + + assert(sstr); +} + +template +void fail_parse(const CharT* str, const CharT* fmt, Parsable& p, type_identity_t*> abbrev = nullptr, + minutes* offset = nullptr) { + p = Parsable{}; + if (abbrev) { + if constexpr (is_same_v) { + *abbrev = _Time_parse_fields::_Invalid_time_string; + } else { + *abbrev = L"!"; + } + } + + if (offset) { + *offset = minutes::min(); + } + + basic_stringstream sstr{str}; + if (abbrev) { + if (offset) { + sstr >> parse(basic_string{fmt}, p, *abbrev, *offset); + } else { + sstr >> parse(basic_string{fmt}, p, *abbrev); + } + } else { + if (offset) { + sstr >> parse(basic_string{fmt}, p, *offset); + } else { + sstr >> parse(basic_string{fmt}, p); + } + } + + assert(!sstr); +} + +template +void test_limits(const char* flag, const IntType min, const IntType max) { + char buffer[24]; + TimeType value; + auto conv_result = to_chars(begin(buffer), end(buffer), static_cast>(min) - 1); + assert(conv_result.ec == errc{} && conv_result.ptr != end(buffer)); + *conv_result.ptr = '\0'; + fail_parse(buffer, flag, value); + conv_result = to_chars(begin(buffer), end(buffer), max + 1); + assert(conv_result.ec == errc{} && conv_result.ptr != end(buffer)); + *conv_result.ptr = '\0'; + fail_parse(buffer, flag, value); + + conv_result = to_chars(begin(buffer), end(buffer), min); + assert(conv_result.ec == errc{} && conv_result.ptr != end(buffer)); + *conv_result.ptr = '\0'; + test_parse(buffer, flag, value); + assert(value == TimeType{min}); + conv_result = to_chars(begin(buffer), end(buffer), max); + assert(conv_result.ec == errc{} && conv_result.ptr != end(buffer)); + *conv_result.ptr = '\0'; + test_parse(buffer, flag, value); + assert(value == TimeType{max}); +} + +void parse_seconds() { + seconds time; + test_parse("1", "%S", time); + assert(time == 1s); + test_parse("12", "%S", time); + assert(time == 12s); + test_parse("234", "%S4", time); + assert(time == 23s); + test_parse("0345", "%3S5", time); + assert(time == 34s); + test_parse(" 456", "%3S6", time); + assert(time == 45s); + test_parse("99", "%S", time); // not out-of-range for duration + assert(time == 99s); + + milliseconds time_ms; + test_parse("12.543", "%S", time_ms); + assert(time_ms == 12s + 543ms); + assert(time_ms == seconds{12} + milliseconds{543}); + test_parse("01.234", "%S", time_ms); + assert(time_ms == 1'234ms); + test_parse(" 1.234", "%S", time_ms); + assert(time_ms == 1'234ms); + test_parse("1.234", "%S", time_ms); + assert(time_ms == 1'234ms); + test_parse("1. 234", "%S 234", time_ms); // Flag should consume "1.". + assert(time_ms == 1s); + test_parse("1 .234", "%S .234", time_ms); // Flag should consume "1". + assert(time_ms == 1s); + test_parse("12..345", "%S.345", time_ms); // Flag should consume "12.". + assert(time_ms == 12s); + fail_parse("1.2345", "%6S", time_ms); // would truncate + test_parse("1.2340", "%6S", time_ms); + assert(time_ms == 1'234ms); + + duration time_atto; + test_parse("0.400000000000000002", "%S", time_atto); + assert((time_atto == duration{4} + duration{2})); + + fail_parse("1.2 1.3", "%S %S", time_ms); + fail_parse("1.2 2.2", "%S %S", time_ms); +} + +void parse_minutes() { + seconds time; + test_parse("1", "%M", time); + assert(time == 1min); + test_parse("12", "%M", time); + assert(time == 12min); + test_parse("234", "%M4", time); + assert(time == 23min); + test_parse("0345", "%3M5", time); + assert(time == 34min); + test_parse(" 456", "%3M6", time); + assert(time == 45min); + test_parse("99", "%M", time); // not out-of-range for duration + assert(time == 99min); +} + +void parse_hours() { + seconds time; + + fail_parse("0", "%I", time); + test_parse("1", "%I", time); + assert(time == 1h); + test_parse("11", "%I", time); + assert(time == 11h); + test_parse("12", "%I", time); // assumes a.m. + assert(time == 0h); + fail_parse("13", "%I", time); + + fail_parse("0", "%OI", time); + test_parse("1", "%OI", time); + assert(time == 1h); + test_parse("11", "%OI", time); + assert(time == 11h); + test_parse("12", "%OI", time); // assumes a.m. + assert(time == 0h); + fail_parse("13", "%OI", time); + + test_parse("110", "%I0", time); + assert(time == 11h); + test_parse("0011", "%3I1", time); + assert(time == 1h); + test_parse("010", "%4I", time); + assert(time == 10h); + test_parse(" 12", "%4I", time); + assert(time == 0h); // assumes A.M. with no %p flag + + test_parse("1", "%H", time); + assert(time == 1h); + test_parse("12", "%H", time); + assert(time == 12h); + test_parse("234", "%H4", time); + assert(time == 23h); + test_parse("0123", "%3H3", time); + assert(time == 12h); + test_parse(" 234", "%3H4", time); + assert(time == 23h); + test_parse("30", "%H", time); // not out-of-range for duration + assert(time == 30h); + + // any permutation of %I, %p, and %H should be valid, as long as they're consistent + test_parse("8 pm 20", "%I %p %H", time); + assert(time == 20h); + test_parse("8 20 pm", "%I %H %p", time); + assert(time == 20h); + test_parse("pm 8 20", "%p %I %H", time); + assert(time == 20h); + test_parse("pm 20 8", "%p %H %I", time); + assert(time == 20h); + test_parse("20 pm 8", "%H %p %I", time); + assert(time == 20h); + test_parse("20 8 pm", "%H %I %p", time); + assert(time == 20h); + + // inconsistent + fail_parse("8 am 20", "%I %p %H", time); + fail_parse("8 20 am", "%I %H %p", time); + fail_parse("am 8 20", "%p %I %H", time); + fail_parse("am 20 8", "%p %H %I", time); + fail_parse("20 am 8", "%H %p %I", time); + fail_parse("20 8 am", "%H %I %p", time); +} + +void parse_other_duration() { + seconds time; + test_parse("42", "%j", time); + assert(time == days{42}); + duration time_micro; // maximum ~35.8 minutes + fail_parse("1", "%j", time_micro); + test_parse("400", "%j", time); + assert(time == days{400}); // not out-of-range for duration + + test_parse(" 1:23:42", "%T", time); + assert(time == 1h + 23min + 42s); + test_parse("01:23:42", "%T", time); + assert(time == 1h + 23min + 42s); + + test_parse("11: 2:42", "%T", time); + assert(time == 11h + 2min + 42s); + test_parse("11:02:42", "%T", time); + assert(time == 11h + 2min + 42s); + + test_parse("12:34: 4", "%T", time); + assert(time == 12h + 34min + 4s); + test_parse("12:34:04", "%T", time); + assert(time == 12h + 34min + 4s); + test_parse("00:34:04", "%T", time); + assert(time == 34min + 4s); + + milliseconds time_milli; + test_parse("12:34:56.789", "%T", time_milli); + assert(time_milli == 12h + 34min + 56s + 789ms); + + // locale's time representation %X (== "%H : %M : %S") + test_parse("12:34:04", "%X", time); + assert(time == 12h + 34min + 4s); + + // floating-point representations, parsing precision controlled by duration::period + duration df; + test_parse("9.125", "%S", df); + assert(df.count() == 9125.0f); + + duration dd; + test_parse("1.875", "%S", dd); + assert(dd.count() == 1875.0); +} + +void parse_time_zone() { + seconds time; + minutes offset; + test_parse("-0430", "%z", time, nullptr, &offset); + assert(offset == -(4h + 30min)); + test_parse("+0430", "%z", time, nullptr, &offset); + assert(offset == (4h + 30min)); + test_parse("0430", "%z", time, nullptr, &offset); + assert(offset == (4h + 30min)); + test_parse("11", "%z", time, nullptr, &offset); + assert(offset == 11h); + + fail_parse("4", "%z", time, nullptr, &offset); + fail_parse("043", "%z", time, nullptr, &offset); + fail_parse("!0430", "%z", time, nullptr, &offset); + + test_parse("-04:30", "%Ez", time, nullptr, &offset); + assert(offset == -(4h + 30min)); + test_parse("+04:30", "%Ez", time, nullptr, &offset); + assert(offset == (4h + 30min)); + test_parse("04:30", "%Ez", time, nullptr, &offset); + assert(offset == (4h + 30min)); + test_parse("4:30", "%Ez", time, nullptr, &offset); + assert(offset == (4h + 30min)); + test_parse("11", "%Ez", time, nullptr, &offset); + assert(offset == 11h); + test_parse("4", "%Ez", time, nullptr, &offset); + assert(offset == 4h); + + fail_parse("!4", "%Ez", time, nullptr, &offset); + // %Ez matches "04", leaving "30" in the stream. + fail_parse("0430 meow", "%Ez meow", time, nullptr, &offset); + fail_parse("04:", "%Ez", time, nullptr, &offset); + fail_parse("04:3", "%Ez", time, nullptr, &offset); + + string tz_name; + test_parse("UTC", "%Z", time, &tz_name); + assert(tz_name == "UTC"); + test_parse("Etc/GMT+11", "%Z", time, &tz_name); + assert(tz_name == "Etc/GMT+11"); + test_parse("Etc/GMT-4", "%Z", time, &tz_name); + assert(tz_name == "Etc/GMT-4"); + test_parse("Asia/Hong_Kong", "%Z", time, &tz_name); + assert(tz_name == "Asia/Hong_Kong"); + + fail_parse("Not_valid! 00", "%Z %H", time, &tz_name); + test_parse("Valid_Tz! 07", "%Z! %H", time, &tz_name); + assert(tz_name == "Valid_Tz" && time == 7h); +} + +void parse_calendar_types_basic() { + // basic day tests + day d; + test_parse("1", "%d", d); + assert(d == day{1}); + test_parse("23", "%d", d); + assert(d == day{23}); + test_parse("012", "%d2", d); + assert(d == day{1}); + test_parse(" 23", "%d3", d); + assert(d == day{2}); + + test_limits("%d", 1, 31); + + test_parse("1", "%e", d); + assert(d == day{1}); + test_parse("23", "%e", d); + assert(d == day{23}); + test_parse("012", "%e2", d); + assert(d == day{1}); + test_parse(" 23", "%e3", d); + assert(d == day{2}); + + // basic weekday tests + weekday wd; + + test_parse("Mon", "%a", wd); + assert(wd == Monday); + test_parse("wedNesday", "%A", wd); + assert(wd == Wednesday); + + test_parse("1", "%w", wd); // 0-based, Sunday=0 + assert(wd == Monday); + test_parse("1", "%u", wd); // ISO 1-based, Monday=1 + assert(wd == Monday); + test_parse("7", "%u", wd); + assert(wd == Sunday); + + test_limits("%w", 0, 6); + test_limits("%u", 1, 7); + + // basic month tests + month m; + test_parse("Apr", "%b", m); + assert(m == April); + test_parse("deCeMbeR", "%b", m); + assert(m == December); + test_parse("September", "%B", m); + assert(m == September); + test_parse("February", "%h", m); + assert(m == February); + + test_parse("3", "%m", m); + assert(m == March); + test_parse("11", "%m", m); + assert(m == November); + test_parse("110", "%m0", m); + assert(m == November); + test_parse("0011", "%3m1", m); + assert(m == January); + test_parse("010", "%4m", m); + assert(m == October); + test_parse(" 12", "%4m", m); + assert(m == December); + + test_limits("%m", 1, 12); + + // basic year tests + year y; + test_parse("1777", "%Y", y); + assert(y == 1777y); + test_parse("07 17", "%y %C", y); + assert(y == 1707y); + test_parse("18 077", "%C %3y", y); + assert(y == 1877y); + + // interpretation of two-digit year by itself + test_parse("00", "%y", y); + assert(y == 2000y); + test_parse("68", "%y", y); + assert(y == 2068y); + test_parse("69", "%y", y); + assert(y == 1969y); + test_parse("99", "%y", y); + assert(y == 1999y); + + // negative century + test_parse("-1 5", "%C %y", y); + assert(y == -95y); + + // check consistency, or lack thereof + test_parse("1887 18 87", "%Y %C %y", y); + assert(y == 1887y); + fail_parse("1888 18 87", "%Y %C %y", y); + fail_parse("1887 19 87", "%Y %C %y", y); + fail_parse("1887 18 88", "%Y %C %y", y); + + // basic month_day tests + month_day md; + test_parse("1 Jan 1", "%j %b %d", md); + assert(md == January / 1d); + test_parse("32 Feb 1", "%j %b %d", md); + assert(md == February / 1d); + test_parse("59 Feb 28", "%j %b %d", md); + assert(md == February / 28d); + fail_parse("0", "%j", md); + fail_parse("60", "%j", md); // could be Feb 29 or Mar 1 + + test_parse("January 1", "%b %d", md); + assert(md == January / 1d); + test_parse("January 31", "%b %d", md); + assert(md == January / 31d); + + test_parse("February 1", "%b %d", md); + assert(md == February / 1d); + test_parse("February 29", "%b %d", md); + assert(md == February / 29d); + + test_parse("March 1", "%b %d", md); + assert(md == March / 1d); + test_parse("March 31", "%b %d", md); + assert(md == March / 31d); + + test_parse("April 1", "%b %d", md); + assert(md == April / 1d); + test_parse("April 30", "%b %d", md); + assert(md == April / 30d); + + test_parse("May 1", "%b %d", md); + assert(md == May / 1d); + test_parse("May 31", "%b %d", md); + assert(md == May / 31d); + + test_parse("June 1", "%b %d", md); + assert(md == June / 1d); + test_parse("June 30", "%b %d", md); + assert(md == June / 30d); + + test_parse("July 1", "%b %d", md); + assert(md == July / 1d); + test_parse("July 31", "%b %d", md); + assert(md == July / 31d); + + test_parse("August 1", "%b %d", md); + assert(md == August / 1d); + test_parse("August 31", "%b %d", md); + assert(md == August / 31d); + + test_parse("September 1", "%b %d", md); + assert(md == September / 1d); + test_parse("September 30", "%b %d", md); + assert(md == September / 30d); + + test_parse("October 1", "%b %d", md); + assert(md == October / 1d); + test_parse("October 31", "%b %d", md); + assert(md == October / 31d); + + test_parse("November 1", "%b %d", md); + assert(md == November / 1d); + test_parse("November 30", "%b %d", md); + assert(md == November / 30d); + + test_parse("December 1", "%b %d", md); + assert(md == December / 1d); + test_parse("December 31", "%b %d", md); + assert(md == December / 31d); + + // not ambiguous with year + year_month_day ymd; + test_parse("60 2004-02-29", "%j %F", ymd); + assert(ymd == 2004y / February / 29); + + // basic year_month_day tests + // different ways of specifying year + test_parse("12-01-1997", "%d-%m-%Y", ymd); + assert(ymd == 12d / January / 1997y); + test_parse("12-01-19 97", "%d-%m-%C %y", ymd); + assert(ymd == 12d / January / 1997y); + test_parse("12-01-97", "%d-%m-%y", ymd); + assert(ymd == 12d / January / 1997y); + + // basic %D test + test_parse("07/04/76 17", "%D %C", ymd); + assert(ymd == July / 4d / 1776y); + // locale's date representation %x (== "%d / %m / %y") + test_parse("04/07/76 17", "%x %C", ymd); + assert(ymd == 4d / July / 1776y); + test_parse("10/12/15 18", "%x %C", ymd); + assert(ymd == 10d / December / 1815y); + + // day-of-year tests, leap and non-leap years + test_parse("60 2001-03-01", "%j %F", ymd); + assert(ymd == 2001y / March / 1d); + test_parse("61 2004-03-01", "%j %F", ymd); + assert(ymd == 2004y / March / 1d); + test_parse("90 2001-03-31", "%j %F", ymd); + assert(ymd == 2001y / March / last); + test_parse("91 2004-03-31", "%j %F", ymd); + assert(ymd == 2004y / March / last); + + test_parse("91 2001-04-01", "%j %F", ymd); + assert(ymd == 2001y / April / 1d); + test_parse("92 2004-04-01", "%j %F", ymd); + assert(ymd == 2004y / April / 1d); + test_parse("120 2001-04-30", "%j %F", ymd); + assert(ymd == 2001y / April / last); + test_parse("121 2004-04-30", "%j %F", ymd); + assert(ymd == 2004y / April / last); + + test_parse("121 2001-05-01", "%j %F", ymd); + assert(ymd == 2001y / May / 1d); + test_parse("122 2004-05-01", "%j %F", ymd); + assert(ymd == 2004y / May / 1d); + test_parse("151 2001-05-31", "%j %F", ymd); + assert(ymd == 2001y / May / last); + test_parse("152 2004-05-31", "%j %F", ymd); + assert(ymd == 2004y / May / last); + + test_parse("152 2001-06-01", "%j %F", ymd); + assert(ymd == 2001y / June / 1d); + test_parse("153 2004-06-01", "%j %F", ymd); + assert(ymd == 2004y / June / 1d); + test_parse("181 2001-06-30", "%j %F", ymd); + assert(ymd == 2001y / June / last); + test_parse("182 2004-06-30", "%j %F", ymd); + assert(ymd == 2004y / June / last); + + test_parse("182 2001-07-01", "%j %F", ymd); + assert(ymd == 2001y / July / 1d); + test_parse("183 2004-07-01", "%j %F", ymd); + assert(ymd == 2004y / July / 1d); + test_parse("212 2001-07-31", "%j %F", ymd); + assert(ymd == 2001y / July / last); + test_parse("213 2004-07-31", "%j %F", ymd); + assert(ymd == 2004y / July / last); + + test_parse("213 2001-08-01", "%j %F", ymd); + assert(ymd == 2001y / August / 1d); + test_parse("214 2004-08-01", "%j %F", ymd); + assert(ymd == 2004y / August / 1d); + test_parse("243 2001-08-31", "%j %F", ymd); + assert(ymd == 2001y / August / last); + test_parse("244 2004-08-31", "%j %F", ymd); + assert(ymd == 2004y / August / last); + + test_parse("244 2001-09-01", "%j %F", ymd); + assert(ymd == 2001y / September / 1d); + test_parse("245 2004-09-01", "%j %F", ymd); + assert(ymd == 2004y / September / 1d); + test_parse("273 2001-09-30", "%j %F", ymd); + assert(ymd == 2001y / September / last); + test_parse("274 2004-09-30", "%j %F", ymd); + assert(ymd == 2004y / September / last); + + test_parse("274 2001-10-01", "%j %F", ymd); + assert(ymd == 2001y / October / 1d); + test_parse("275 2004-10-01", "%j %F", ymd); + assert(ymd == 2004y / October / 1d); + test_parse("304 2001-10-31", "%j %F", ymd); + assert(ymd == 2001y / October / last); + test_parse("305 2004-10-31", "%j %F", ymd); + assert(ymd == 2004y / October / last); + + test_parse("305 2001-11-01", "%j %F", ymd); + assert(ymd == 2001y / November / 1d); + test_parse("306 2004-11-01", "%j %F", ymd); + assert(ymd == 2004y / November / 1d); + test_parse("334 2001-11-30", "%j %F", ymd); + assert(ymd == 2001y / November / last); + test_parse("335 2004-11-30", "%j %F", ymd); + assert(ymd == 2004y / November / last); + + test_parse("335 2001-12-01", "%j %F", ymd); + assert(ymd == 2001y / December / 1d); + test_parse("336 2004-12-01", "%j %F", ymd); + assert(ymd == 2004y / December / 1d); + test_parse("365 2001-12-31", "%j %F", ymd); + assert(ymd == 2001y / December / last); + test_parse("366 2004-12-31", "%j %F", ymd); + assert(ymd == 2004y / December / last); + + fail_parse("366 2001", "%j %Y", ymd); + fail_parse("367 2004", "%j %Y", ymd); + + // Check consistency between date and day-of-week + test_parse("Wed 2000-03-01", "%a %F", ymd); + fail_parse("Mon 2000-03-01", "%a %F", ymd); + + // For %F, width is applied only to the year + test_parse("12345-06-07", "%5F", ymd); + assert(ymd == 7d / June / 12345y); + fail_parse("12345-00006-07", "%5F", ymd); + fail_parse("12345-06-00007", "%5F", ymd); + fail_parse("12345-00006-00007", "%5F", ymd); +} + +void parse_iso_week_date() { + year_month_day ymd; + test_parse("2005-W52-6", "%G-W%V-%u", ymd); + assert(ymd == 2005y / December / 31d); + test_parse("2005-W52-7", "%G-W%V-%u", ymd); + assert(ymd == 2006y / January / 1d); + test_parse("2006-W01-1", "%G-W%V-%u", ymd); + assert(ymd == 2006y / January / 2d); + fail_parse("2006-W00-1", "%G-W%V-%u", ymd); + + test_parse("2007-W52-7", "%G-W%V-%u", ymd); + assert(ymd == 2007y / December / 30d); + test_parse("2008-W01-1", "%G-W%V-%u", ymd); + assert(ymd == 2007y / December / 31d); + test_parse("2008-W01-2", "%G-W%V-%u", ymd); + assert(ymd == 2008y / January / 1d); + + fail_parse("05-W52-6", "%g-W%V-%u", ymd); // no century + + year_month_day ref{2005y / December / 31d}; + fail_parse("2005-W52-6 19", "%G-W%V-%u %C", ymd); // inconsistent century + test_parse("2005-W52-6 20", "%G-W%V-%u %C", ymd); // consistent century + assert(ymd == ref); + test_parse("05-W52-6 20", "%g-W%V-%u %C", ymd); + assert(ymd == ref); + + fail_parse("2005-W52-6 2004", "%G-W%V-%u %Y", ymd); // inconsistent year + test_parse("2005-W52-6 2005", "%G-W%V-%u %Y", ymd); // consistent year + assert(ymd == ref); + test_parse("05-W52-6 2005", "%g-W%V-%u %Y", ymd); + assert(ymd == ref); + test_parse("2005-W52-6 05", "%G-W%V-%u %y", ymd); + assert(ymd == ref); + test_parse("05-W52-6 05", "%g-W%V-%u %y", ymd); + assert(ymd == ref); + + ref = 2007y / December / 31d; + fail_parse("2008-W01-1 2008", "%G-W%V-%u %Y", ymd); // inconsistent year (!) + test_parse("2008-W01-1 2007", "%G-W%V-%u %Y", ymd); // consistent year + assert(ymd == ref); + test_parse("08-W01-1 2007", "%g-W%V-%u %Y", ymd); + assert(ymd == ref); + test_parse("2008-W01-1 07", "%G-W%V-%u %y", ymd); + assert(ymd == ref); + test_parse("08-W01-1 07", "%g-W%V-%u %y", ymd); + assert(ymd == ref); + + // ISO and Gregorian years in different centuries + test_parse("1699-W53-5 1700", "%G-W%V-%u %Y", ymd); + assert(ymd == 1d / January / 1700y); + fail_parse("1699-W54-5 1700", "%G-W%V-%u %Y", ymd); + fail_parse("1699-W53-5 00", "%G-W%V-%u %y", ymd); // inconsistent %y (== 2000) + fail_parse("99-W53-5 16 00", "%g-W%V-%u %C %y", ymd); // inconsistent %C+%y year (== 1600) + fail_parse("99-W53-5 17 00", "%g-W%V-%u %C %y", ymd); // inconsistent %C+%g ISO year (== 1700) + + // This is expected to parse successfully. Even though %C+%g would give the wrong year, + // as above we don't try to use that for the ISO year when %G is present. + test_parse("1699 99-W53-5 17 00", "%G %g-W%V-%u %C %y", ymd); + assert(ymd == 1d / January / 1700y); + fail_parse("1699 98-W53-5 17 00", "%G %g-W%V-%u %C %y", ymd); +} + +void parse_other_week_date() { + year_month_day ymd; + // Year begins on Sunday. + test_parse("2017-01-0", "%Y-%U-%w", ymd); + assert(ymd == 2017y / January / 1d); + test_parse("2017-00-0", "%Y-%W-%w", ymd); + assert(ymd == 2017y / January / 1d); + test_parse("2017-53-0", "%Y-%U-%w", ymd); + assert(ymd == 2017y / December / 31d); + + fail_parse("2017/-1/0", "%Y/%W/%w", ymd); + fail_parse("2017/-1/0", "%Y/%U/%w", ymd); + fail_parse("2017-54-0", "%Y-%W-%w", ymd); + fail_parse("2017-54-0", "%Y-%U-%w", ymd); + fail_parse("2018-00-0", "%Y-%U-%w", ymd); // refers to 31 Dec. 2017 + fail_parse("2017-53-1", "%Y-%U-%w", ymd); // refers to 01 Jan. 2018 + + // Year begins on Monday. + test_parse("2018-00-1", "%Y-%U-%w", ymd); + assert(ymd == 2018y / January / 1d); + test_parse("2018-01-1", "%Y-%W-%w", ymd); + assert(ymd == 2018y / January / 1d); + test_parse("2018-53-1", "%Y-%W-%w", ymd); + assert(ymd == 2018y / December / 31d); + + // Year begins on Tuesday. + test_parse("2019-00-2", "%Y-%U-%w", ymd); + assert(ymd == 2019y / January / 1d); + test_parse("2019-00-2", "%Y-%W-%w", ymd); + assert(ymd == 2019y / January / 1d); + + // Year begins on Wednesday. + test_parse("2020-00-3", "%Y-%U-%w", ymd); + assert(ymd == 2020y / January / 1d); + test_parse("2020-00-3", "%Y-%W-%w", ymd); + assert(ymd == 2020y / January / 1d); + + // Year begins on Thursday. + test_parse("2015-00-4", "%Y-%U-%w", ymd); + assert(ymd == 2015y / January / 1d); + test_parse("2015-00-4", "%Y-%W-%w", ymd); + assert(ymd == 2015y / January / 1d); + + // Year begins on Friday. + test_parse("2016-00-5", "%Y-%U-%w", ymd); + assert(ymd == 2016y / January / 1d); + test_parse("2016-00-5", "%Y-%W-%w", ymd); + assert(ymd == 2016y / January / 1d); + + // Year begins on Saturday. + test_parse("2022-00-6", "%Y-%U-%w", ymd); + assert(ymd == 2022y / January / 1d); + test_parse("2022-00-6", "%Y-%W-%w", ymd); + assert(ymd == 2022y / January / 1d); +} + +void parse_whitespace() { + seconds time; + fail_parse("ab", "a%nb", time); + test_parse("a b", "a%nb", time); + fail_parse("a b", "a%nb", time); + fail_parse("a", "a%n", time); + test_parse("a ", "a%n", time); + + test_parse("ab", "a%tb", time); + test_parse("a b", "a%tb", time); + fail_parse("a b", "a%tb", time); + test_parse("a", "a%t", time); + test_parse("a ", "a%t", time); + + test_parse("a", "a ", time); + test_parse("", "", time); + test_parse("", " ", time); + test_parse("", "%t", time); + fail_parse("", "%n", time); +} + +void insert_leap_second(const sys_days& date, const seconds& value) { + const auto& my_tzdb = get_tzdb_list().front(); + vector zones; + vector links; + transform(my_tzdb.zones.begin(), my_tzdb.zones.end(), back_inserter(zones), + [](const auto& tz) { return time_zone{tz.name()}; }); + transform(my_tzdb.links.begin(), my_tzdb.links.end(), back_inserter(links), [](const auto& link) { + return time_zone_link{link.name(), link.target()}; + }); + + auto leap_vec = my_tzdb.leap_seconds; + leap_vec.emplace_back(date, value == 1s, leap_vec.back()._Elapsed()); + get_tzdb_list()._Emplace_front( + tzdb{my_tzdb.version, move(zones), move(links), move(leap_vec), my_tzdb._All_ls_positive && (value == 1s)}); +} + +void parse_timepoints() { + sys_seconds ref = sys_days{2020y / October / 29d} + 19h + 1min + 42s; + sys_seconds st; + utc_seconds ut; + file_time ft; + + test_parse("oct 29 19:01:42 2020", "%c", st); + test_parse("oct 29 19:01:42 2020", "%c", ut); + test_parse("oct 29 19:01:42 2020", "%c", ft); + + assert(st == ref); + assert(ut == utc_clock::from_sys(ref)); + assert(ft == clock_cast(ref)); + + test_parse("oct 29 19:01:42 2020 0430", "%c %z", st); + assert(st == ref - (4h + 30min)); + + // N4878 [time.clock.tai]/1: + // The clock tai_clock measures seconds since 1958-01-01 00:00:00 and is offset 10s ahead of UTC at this date. + // That is, 1958-01-01 00:00:00 TAI is equivalent to 1957-12-31 23:59:50 UTC. Leap seconds are not inserted into + // TAI. Therefore every time a leap second is inserted into UTC, UTC shifts another second with respect to TAI. + // For example by 2000-01-01 there had been 22 positive and 0 negative leap seconds inserted so 2000-01-01 + // 00:00:00 UTC is equivalent to 2000-01-01 00:00:32 TAI (22s plus the initial 10s offset). + + ref = sys_days{1957y / December / 31d} + days{1} - 10s; + tai_seconds tt; + test_parse("jan 1 00:00:00 1958", "%c", tt); + assert(tt == clock_cast(ref)); + + ref = sys_days{2000y / January / 1d}; + test_parse("jan 1 00:00:32 2000", "%c", tt); + assert(tt == clock_cast(ref)); + + // N4878 [time.clock.gps]/1: + // The clock gps_clock measures seconds since the first Sunday of January, 1980 00:00:00 UTC. Leap seconds are + // not inserted into GPS. Therefore every time a leap second is inserted into UTC, UTC shifts another second + // with respect to GPS. Aside from the offset from 1958y/January/1 to 1980y/January/Sunday[1], GPS is behind TAI + // by 19s due to the 10s offset between 1958 and 1970 and the additional 9 leap seconds inserted between 1970 + // and 1980. + + gps_seconds gt; + ref = sys_days{1980y / January / 6d}; + test_parse("jan 6 00:00:00 1980", "%c", gt); + assert(gt == clock_cast(ref)); + test_parse("jan 6 00:00:19 1980", "%c", tt); + assert(gt == clock_cast(tt)); + + seconds time; + test_parse(" 1:23:42 am", "%r", time); + assert(time == 1h + 23min + 42s); + test_parse("2000-01-02 01:23:42 pm", "%F %r", st); + assert(st == sys_days{2000y / January / 2d} + (13h + 23min + 42s)); + + test_parse("11: 2:42 am", "%r", time); + assert(time == 11h + 2min + 42s); + test_parse("2000-01-02 11:02:42 pm", "%F %r", st); + assert(st == sys_days{2000y / January / 2d} + (23h + 2min + 42s)); + + test_parse("12:34: 4 am", "%r", time); + assert(time == 34min + 4s); + test_parse("2000-01-02 12:34:04 pm", "%F %r", st); + assert(st == sys_days{2000y / January / 2d} + (12h + 34min + 4s)); + + + test_parse(" 3:14 2000-01-02", "%R %F", st); + assert(st == sys_days{2000y / January / 2d} + (3h + 14min)); + test_parse("03:14 2000-01-02", "%R %F", st); + assert(st == sys_days{2000y / January / 2d} + (3h + 14min)); + + test_parse("11: 3 2000-01-02", "%R %F", st); + assert(st == sys_days{2000y / January / 2d} + (11h + 3min)); + test_parse("11:03 2000-01-02", "%R %F", st); + assert(st == sys_days{2000y / January / 2d} + (11h + 3min)); + + test_parse("00:42 2000-01-02", "%R %F", st); + assert(st == sys_days{2000y / January / 2d} + (42min)); + test_parse("12:42 2000-01-02", "%R %F", st); + assert(st == sys_days{2000y / January / 2d} + (12h + 42min)); + + // Historical leap seconds don't allow complete testing, because they've all been positive and there haven't been + // any since 2016 (as of 2021). + insert_leap_second(1d / January / 2020y, -1s); + insert_leap_second(1d / January / 2022y, 1s); + + utc_seconds ut_ref = utc_clock::from_sys(sys_days{1d / July / 1972y}) - 1s; // leap second insertion + test_parse("june 30 23:59:60 1972", "%c", ut); + assert(ut == ut_ref); + // not leap-second aware + fail_parse("june 30 23:59:60 1972", "%c", st); + fail_parse("june 30 23:59:60 1972", "%c", tt); + fail_parse("june 30 23:59:60 1972", "%c", gt); + fail_parse("june 30 23:59:60 1972", "%c", ft); + + fail_parse("june 30 23:59:60 1973", "%c", ut); // not a leap second insertion + + ref = sys_days{1d / January / 2020y} - 1s; // negative leap second, UTC time doesn't exist + fail_parse("dec 31 23:59:59 2019", "%c", ut); + fail_parse("dec 31 23:59:59 2019", "%c", st); + fail_parse("dec 31 23:59:59 2019", "%c", ft); + + local_seconds lt; + test_parse("dec 31 23:59:59 2019", "%c", lt); // Not UTC, might be valid depending on the time zone. + assert(lt.time_since_epoch() == ref.time_since_epoch()); + + // Initially, TAI - UTC == 37s. + test_parse("dec 31 23:59:59 2019", "%c", tt); + test_parse("dec 31 23:59:22 2019", "%c", ut); + assert(tt == clock_cast(ut)); + + test_parse("jan 01 00:00:35 2020", "%c", tt); + test_parse("dec 31 23:59:58 2019", "%c", ut); + assert(tt == clock_cast(ut)); + + test_parse("jan 01 00:00:36 2020", "%c", tt); + test_parse("jan 01 00:00:00 2020", "%c", ut); + assert(tt == clock_cast(ut)); + + // Initially, GPS - UTC == 18s + test_parse("dec 31 23:59:59 2019", "%c", gt); + test_parse("dec 31 23:59:41 2019", "%c", ut); + assert(gt == clock_cast(ut)); + + test_parse("jan 01 00:00:16 2020", "%c", gt); + test_parse("dec 31 23:59:58 2019", "%c", ut); + assert(gt == clock_cast(ut)); + + test_parse("jan 01 00:00:17 2020", "%c", gt); + test_parse("jan 01 00:00:00 2020", "%c", ut); + assert(gt == clock_cast(ut)); + + + ut_ref = utc_clock::from_sys(sys_days{1d / January / 2022y}) - 1s; // leap second insertion + test_parse("dec 31 23:59:60 2021", "%c", ut); + assert(ut == ut_ref); + test_parse("dec 31 23:59:60 2021", "%c", ft); + assert(ft == clock_cast(ut_ref)); + + + // GH-1606: reads too many leading zeros + test_parse("19700405T000006", "%Y%m%dT%H%M%S", st); + assert(st == sys_days{5d / April / 1970y} + 6s); + + // GH-1280 tests + year_month_day ymd; + test_parse("20200609", "%Y%m%d", ymd); + assert(ymd == 9d / June / 2020y); + + test_parse("20201213", "%Y%m%d", ymd); + assert(ymd == 13d / December / 2020y); + + test_parse("2020112", "%Y%m%d", ymd); + assert(ymd == 2d / November / 2020y); + + test_parse("2020061125", "%Y%m%d", ymd); + assert(ymd == 11d / June / 2020y); + + test_parse("2020120625119", "%Y%m%d", ymd); + assert(ymd == 6d / December / 2020y); + + test_parse("2020092Text", "%Y%m%d", ymd); + assert(ymd == 2d / September / 2020y); + + test_parse("20200609", "%Y%m%d", ymd); + assert(ymd == 9d / June / 2020y); + + test_parse("2020112", "%Y%m%d", ymd); + assert(ymd == 2d / November / 2020y); + + test_parse("2020061125", "%Y%m%d", ymd); + assert(ymd == 11d / June / 2020y); + + test_parse("2020124", "%Y%m%d", ymd); + assert(ymd == 4d / December / 2020y); + + test_parse("2020104Text", "%Y%m%d", ymd); + assert(ymd == 4d / October / 2020y); + + fail_parse("202000000000000923", "%Y%m%d", ymd); + fail_parse("202000000000000923", "%Y%m%d", ymd); + + // time_point out-of-range tests + fail_parse("1887-12-22 00:00:-1", "%F %T", st); + fail_parse("1887-12-22 00:00:60", "%F %T", st); + fail_parse("1887-12-22 00:-1:00", "%F %T", st); + fail_parse("1887-12-22 00:60:00", "%F %T", st); + fail_parse("1887-12-22 -1:00:00", "%F %T", st); + fail_parse("1887-12-22 24:00:00", "%F %T", st); + + test_parse("1912-06-23 00:00:00", "%F %T", st); + assert(st == sys_days{23d / June / 1912y}); + test_parse("1912-06-23 23:59:59", "%F %T", st); + assert(st == sys_days{23d / June / 1912y} + 23h + 59min + 59s); +} + +void parse_wchar() { + seconds time; + test_parse(L"12", L"%S", time); + assert(time == 12s); + test_parse(L"12", L"%M", time); + assert(time == 12min); + test_parse(L"30", L"%H", time); + assert(time == 30h); + test_parse(L" 1:23:42", L"%T", time); + assert(time == 1h + 23min + 42s); + wstring tz_name; + test_parse(L"Etc/GMT+11", L"%Z", time, &tz_name); + assert(tz_name == L"Etc/GMT+11"); + fail_parse(L"Not_valid! 00", L"%Z %H", time, &tz_name); + + weekday wd; + test_parse(L"wedNesday", L"%A", wd); + assert(wd == Wednesday); + + month m; + test_parse(L"deCeMbeR", L"%b", m); + assert(m == December); + + sys_seconds st; + test_parse(L"oct 29 19:01:42 2020", L"%c", st); + assert(st == sys_days{2020y / October / 29d} + 19h + 1min + 42s); + + fail_parse(L"ab", L"a%nb", time); + test_parse(L"a b", L"a%nb", time); + fail_parse(L"a b", L"a%nb", time); +} + +void test_parse() { + parse_seconds(); + parse_minutes(); + parse_hours(); + parse_other_duration(); + parse_time_zone(); + parse_calendar_types_basic(); + parse_iso_week_date(); + parse_other_week_date(); + parse_whitespace(); + parse_timepoints(); + parse_wchar(); +} + + int main() { test_duration_output(); + test_parse(); return 0; }