From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: by sourceware.org (Postfix, from userid 2181) id 3244A3858D20; Fri, 11 Aug 2023 18:58:41 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 3244A3858D20 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gcc.gnu.org; s=default; t=1691780321; bh=dHyj/bQoil+Pr+qtY74FvgKZBqFxGj7tC3dcxnzY6hw=; h=From:To:Subject:Date:From; b=gc7yqNEprBcRL89I2MoaoS9rwzgH2GxxVqflzx+zpIRzsw3MAy+B8qi5adMuE6M9z xRU4Ux9Ks7mwVbaGRKEo/JaQTUnZ+YzSuC917C/J7zMYuxTits36lKsy/NhMS0+vGv 0coRJKlqtstb8KaXNXOB3lZlTkHPpYI3TEeIie0E= MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Type: text/plain; charset="utf-8" From: Jonathan Wakely To: gcc-cvs@gcc.gnu.org, libstdc++-cvs@gcc.gnu.org Subject: [gcc r14-3165] libstdc++: Implement C++20 std::chrono::parse [PR104167] X-Act-Checkin: gcc X-Git-Author: Jonathan Wakely X-Git-Refname: refs/heads/master X-Git-Oldrev: f93a612fc4567652b75ffc916d31a446378e6613 X-Git-Newrev: ce6c4d3b4d336493cab128795467d832cb04c9ee Message-Id: <20230811185841.3244A3858D20@sourceware.org> Date: Fri, 11 Aug 2023 18:58:41 +0000 (GMT) List-Id: https://gcc.gnu.org/g:ce6c4d3b4d336493cab128795467d832cb04c9ee commit r14-3165-gce6c4d3b4d336493cab128795467d832cb04c9ee Author: Jonathan Wakely Date: Fri Jul 21 17:19:08 2023 +0100 libstdc++: Implement C++20 std::chrono::parse [PR104167] This adds the missing C++20 features to . I've implemented my proposed resolutions to LWG issues 3960, 3961, and 3962. There are some unimplemented flags such as %OI which I think are not implementable in general. It might be possible to use na_llanginfo with ALT_DIGITS, but that isn't available on all targets. I intend to file another LWG issue about that. libstdc++-v3/ChangeLog: PR libstdc++/104167 * include/bits/chrono_io.h (operator|=, operator|): Add noexcept to _ChronoParts operators. (from_stream, parse): Define new functions. (__detail::_Parse, __detail::_Parser): New class templates. * include/std/chrono (__cpp_lib_chrono): Define to 201907L for C++20. * include/std/version (__cpp_lib_chrono): Likewise. * testsuite/20_util/duration/arithmetic/constexpr_c++17.cc: Adjust expected value of feature test macro. * testsuite/20_util/duration/io.cc: Test parsing. * testsuite/std/time/clock/file/io.cc: Likewise. * testsuite/std/time/clock/gps/io.cc: Likewise. * testsuite/std/time/clock/system/io.cc: Likewise. * testsuite/std/time/clock/tai/io.cc: Likewise. * testsuite/std/time/clock/utc/io.cc: Likewise. * testsuite/std/time/day/io.cc: Likewise. * testsuite/std/time/month/io.cc: Likewise. * testsuite/std/time/month_day/io.cc: Likewise. * testsuite/std/time/weekday/io.cc: Likewise. * testsuite/std/time/year/io.cc: Likewise. * testsuite/std/time/year_month/io.cc: Likewise. * testsuite/std/time/year_month_day/io.cc: Likewise. * testsuite/std/time/syn_c++20.cc: Check value of macro and for the existence of parse and from_stream in namespace chrono. * testsuite/std/time/clock/local/io.cc: New test. * testsuite/std/time/parse.cc: New test. Diff: --- libstdc++-v3/include/bits/chrono_io.h | 1691 +++++++++++++++++++- libstdc++-v3/include/std/chrono | 5 +- libstdc++-v3/include/std/version | 4 +- .../20_util/duration/arithmetic/constexpr_c++17.cc | 2 +- libstdc++-v3/testsuite/20_util/duration/io.cc | 102 +- libstdc++-v3/testsuite/std/time/clock/file/io.cc | 18 + libstdc++-v3/testsuite/std/time/clock/gps/io.cc | 22 +- libstdc++-v3/testsuite/std/time/clock/local/io.cc | 42 + libstdc++-v3/testsuite/std/time/clock/system/io.cc | 73 + libstdc++-v3/testsuite/std/time/clock/tai/io.cc | 22 +- libstdc++-v3/testsuite/std/time/clock/utc/io.cc | 31 + libstdc++-v3/testsuite/std/time/day/io.cc | 60 +- libstdc++-v3/testsuite/std/time/month/io.cc | 122 +- libstdc++-v3/testsuite/std/time/month_day/io.cc | 79 +- libstdc++-v3/testsuite/std/time/parse.cc | 309 ++++ libstdc++-v3/testsuite/std/time/syn_c++20.cc | 9 +- libstdc++-v3/testsuite/std/time/weekday/io.cc | 78 +- libstdc++-v3/testsuite/std/time/year/io.cc | 74 +- libstdc++-v3/testsuite/std/time/year_month/io.cc | 50 +- .../testsuite/std/time/year_month_day/io.cc | 65 +- 20 files changed, 2816 insertions(+), 42 deletions(-) diff --git a/libstdc++-v3/include/bits/chrono_io.h b/libstdc++-v3/include/bits/chrono_io.h index c95301361d87..84791d41fb1e 100644 --- a/libstdc++-v3/include/bits/chrono_io.h +++ b/libstdc++-v3/include/bits/chrono_io.h @@ -235,9 +235,13 @@ namespace __format }; constexpr _ChronoParts - operator|(_ChronoParts __x, _ChronoParts __y) + operator|(_ChronoParts __x, _ChronoParts __y) noexcept { return static_cast<_ChronoParts>((int)__x | (int)__y); } + constexpr _ChronoParts& + operator|=(_ChronoParts& __x, _ChronoParts __y) noexcept + { return __x = __x | __y; } + // TODO rename this to chrono::__formatter? or chrono::__detail::__formatter? template struct __formatter_chrono @@ -2136,18 +2140,150 @@ namespace chrono /// @addtogroup chrono /// @{ - // TODO: from_stream for duration -#if 0 +/// @cond undocumented +namespace __detail +{ + template + struct _Parser + { + static_assert(is_same_v, _Duration>); + + explicit + _Parser(__format::_ChronoParts __need) : _M_need(__need) { } + + _Parser(_Parser&&) = delete; + void operator=(_Parser&&) = delete; + + _Duration _M_time{}; // since midnight + sys_days _M_sys_days{}; + year_month_day _M_ymd{}; + weekday _M_wd{}; + __format::_ChronoParts _M_need; + + template + basic_istream<_CharT, _Traits>& + operator()(basic_istream<_CharT, _Traits>& __is, const _CharT* __fmt, + basic_string<_CharT, _Traits, _Alloc>* __abbrev = nullptr, + minutes* __offset = nullptr); + + private: + // Read an unsigned integer from the stream and return it. + // Extract no more than __n digits. Set failbit if an integer isn't read. + template + static int_least32_t + _S_read_unsigned(basic_istream<_CharT, _Traits>& __is, + ios_base::iostate& __err, int __n) + { + int_least32_t __val = _S_try_read_digit(__is, __err); + if (__val == -1) [[unlikely]] + __err |= ios_base::failbit; + else + { + int __n1 = (std::min)(__n, 9); + // Cannot overflow __val unless we read more than 9 digits + for (int __i = 1; __i < __n1; ++__i) + if (auto __dig = _S_try_read_digit(__is, __err); __dig != -1) + { + __val *= 10; + __val += __dig; + } + + while (__n1++ < __n) [[unlikely]] + if (auto __dig = _S_try_read_digit(__is, __err); __dig != -1) + { + if (__builtin_mul_overflow(__val, 10, &__val) + || __builtin_add_overflow(__val, __dig, &__val)) + { + __err |= ios_base::failbit; + return -1; + } + } + } + return __val; + } + + // Read an unsigned integer from the stream and return it. + // Extract no more than __n digits. Set failbit if an integer isn't read. + template + static int_least32_t + _S_read_signed(basic_istream<_CharT, _Traits>& __is, + ios_base::iostate& __err, int __n) + { + auto __sign = __is.peek(); + if (__sign == '-' || __sign == '+') + (void) __is.get(); + int_least32_t __val = _S_read_unsigned(__is, __err, __n); + if (__err & ios_base::failbit) + { + if (__sign == '-') [[unlikely]] + __val *= -1; + } + return __val; + } + + // Read a digit from the stream and return it, or return -1. + // If no digit is read eofbit will be set (but not failbit). + template + static int_least32_t + _S_try_read_digit(basic_istream<_CharT, _Traits>& __is, + ios_base::iostate& __err) + { + int_least32_t __val = -1; + auto __i = __is.peek(); + if (!_Traits::eq_int_type(__i, _Traits::eof())) [[likely]] + { + _CharT __c = _Traits::to_char_type(__i); + if (_CharT('0') <= __c && __c <= _CharT('9')) [[likely]] + { + (void) __is.get(); + __val = __c - _CharT('0'); + } + } + else + __err |= ios_base::eofbit; + return __val; + } + + // Read the specified character and return true. + // If the character is not found, set failbit and return false. + template + static bool + _S_read_chr(basic_istream<_CharT, _Traits>& __is, + ios_base::iostate& __err, _CharT __c) + { + auto __i = __is.peek(); + if (_Traits::eq_int_type(__i, _Traits::eof())) + __err |= ios_base::eofbit; + else if (_Traits::to_char_type(__i) == __c) [[likely]] + { + (void) __is.get(); + return true; + } + __err |= ios_base::failbit; + return false; + } + }; + + template + using _Parser_t = _Parser>; + +} // namespace __detail +/// ~endcond + template> - basic_istream<_CharT, _Traits>& + inline basic_istream<_CharT, _Traits>& from_stream(basic_istream<_CharT, _Traits>& __is, const _CharT* __fmt, duration<_Rep, _Period>& __d, basic_string<_CharT, _Traits, _Alloc>* __abbrev = nullptr, minutes* __offset = nullptr) { + auto __need = __format::_ChronoParts::_TimeOfDay; + __detail::_Parser_t> __p(__need); + if (__p(__is, __fmt, __abbrev, __offset)) + __d = chrono::duration_cast>(__p._M_time); + return __is; } -#endif template inline basic_ostream<_CharT, _Traits>& @@ -2163,7 +2299,19 @@ namespace chrono return __os; } - // TODO from_stream for day + template> + inline basic_istream<_CharT, _Traits>& + from_stream(basic_istream<_CharT, _Traits>& __is, const _CharT* __fmt, + day& __d, + basic_string<_CharT, _Traits, _Alloc>* __abbrev = nullptr, + minutes* __offset = nullptr) + { + __detail::_Parser<> __p(__format::_ChronoParts::_Day); + if (__p(__is, __fmt, __abbrev, __offset)) + __d = __p._M_ymd.day(); + return __is; + } template inline basic_ostream<_CharT, _Traits>& @@ -2182,7 +2330,19 @@ namespace chrono return __os; } - // TODO from_stream for month + template> + inline basic_istream<_CharT, _Traits>& + from_stream(basic_istream<_CharT, _Traits>& __is, const _CharT* __fmt, + month& __m, + basic_string<_CharT, _Traits, _Alloc>* __abbrev = nullptr, + minutes* __offset = nullptr) + { + __detail::_Parser<> __p(__format::_ChronoParts::_Month); + if (__p(__is, __fmt, __abbrev, __offset)) + __m = __p._M_ymd.month(); + return __is; + } template inline basic_ostream<_CharT, _Traits>& @@ -2203,7 +2363,19 @@ namespace chrono return __os; } - // TODO from_stream for year + template> + inline basic_istream<_CharT, _Traits>& + from_stream(basic_istream<_CharT, _Traits>& __is, const _CharT* __fmt, + year& __y, + basic_string<_CharT, _Traits, _Alloc>* __abbrev = nullptr, + minutes* __offset = nullptr) + { + __detail::_Parser<> __p(__format::_ChronoParts::_Year); + if (__p(__is, __fmt, __abbrev, __offset)) + __y = __p._M_ymd.year(); + return __is; + } template inline basic_ostream<_CharT, _Traits>& @@ -2222,7 +2394,19 @@ namespace chrono return __os; } - // TODO from_stream for weekday + template> + inline basic_istream<_CharT, _Traits>& + from_stream(basic_istream<_CharT, _Traits>& __is, const _CharT* __fmt, + weekday& __wd, + basic_string<_CharT, _Traits, _Alloc>* __abbrev = nullptr, + minutes* __offset = nullptr) + { + __detail::_Parser<> __p(__format::_ChronoParts::_Weekday); + if (__p(__is, __fmt, __abbrev, __offset)) + __wd = __p._M_wd; + return __is; + } template inline basic_ostream<_CharT, _Traits>& @@ -2279,7 +2463,21 @@ namespace chrono return __os; } - // TODO from_stream for month_day + template> + inline basic_istream<_CharT, _Traits>& + from_stream(basic_istream<_CharT, _Traits>& __is, const _CharT* __fmt, + month_day& __md, + basic_string<_CharT, _Traits, _Alloc>* __abbrev = nullptr, + minutes* __offset = nullptr) + { + using __format::_ChronoParts; + auto __need = _ChronoParts::_Month | _ChronoParts::_Day; + __detail::_Parser<> __p(__need); + if (__p(__is, __fmt, __abbrev, __offset)) + __md = month_day(__p._M_ymd.month(), __p._M_ymd.day()); + return __is; + } template inline basic_ostream<_CharT, _Traits>& @@ -2351,7 +2549,21 @@ namespace chrono return __os; } - // TODO from_stream for year_month + template> + inline basic_istream<_CharT, _Traits>& + from_stream(basic_istream<_CharT, _Traits>& __is, const _CharT* __fmt, + year_month& __ym, + basic_string<_CharT, _Traits, _Alloc>* __abbrev = nullptr, + minutes* __offset = nullptr) + { + using __format::_ChronoParts; + auto __need = _ChronoParts::_Year | _ChronoParts::_Month; + __detail::_Parser<> __p(__need); + if (__p(__is, __fmt, __abbrev, __offset)) + __ym = year_month(__p._M_ymd.year(), __p._M_ymd.month()); + return __is; + } template inline basic_ostream<_CharT, _Traits>& @@ -2367,7 +2579,22 @@ namespace chrono return __os; } - // TODO from_stream for year_month_day + template> + inline basic_istream<_CharT, _Traits>& + from_stream(basic_istream<_CharT, _Traits>& __is, const _CharT* __fmt, + year_month_day& __ymd, + basic_string<_CharT, _Traits, _Alloc>* __abbrev = nullptr, + minutes* __offset = nullptr) + { + using __format::_ChronoParts; + auto __need = _ChronoParts::_Year | _ChronoParts::_Month + | _ChronoParts::_Day; + __detail::_Parser<> __p(__need); + if (__p(__is, __fmt, __abbrev, __offset)) + __ymd = __p._M_ymd; + return __is; + } template inline basic_ostream<_CharT, _Traits>& @@ -2498,7 +2725,28 @@ namespace chrono return __os; } - // TODO: from_stream for sys_time + template> + basic_istream<_CharT, _Traits>& + from_stream(basic_istream<_CharT, _Traits>& __is, const _CharT* __fmt, + sys_time<_Duration>& __tp, + basic_string<_CharT, _Traits, _Alloc>* __abbrev = nullptr, + minutes* __offset = nullptr) + { + minutes __off{}; + if (!__offset) + __offset = &__off; + using __format::_ChronoParts; + auto __need = _ChronoParts::_Year | _ChronoParts::_Month + | _ChronoParts::_Day | _ChronoParts::_TimeOfDay; + __detail::_Parser_t<_Duration> __p(__need); + if (__p(__is, __fmt, __abbrev, __offset)) + { + auto __st = __p._M_sys_days + __p._M_time - *__offset; + __tp = chrono::time_point_cast<_Duration>(__st); + } + return __is; + } template inline basic_ostream<_CharT, _Traits>& @@ -2509,7 +2757,19 @@ namespace chrono return __os; } - // TODO: from_stream for utc_time + template> + inline basic_istream<_CharT, _Traits>& + from_stream(basic_istream<_CharT, _Traits>& __is, const _CharT* __fmt, + utc_time<_Duration>& __tp, + basic_string<_CharT, _Traits, _Alloc>* __abbrev = nullptr, + minutes* __offset = nullptr) + { + sys_time<_Duration> __st; + if (chrono::from_stream(__is, __fmt, __st, __abbrev, __offset)) + __tp = utc_clock::from_sys(__st); + return __is; + } template inline basic_ostream<_CharT, _Traits>& @@ -2520,7 +2780,19 @@ namespace chrono return __os; } - // TODO: from_stream for tai_time + template> + inline basic_istream<_CharT, _Traits>& + from_stream(basic_istream<_CharT, _Traits>& __is, const _CharT* __fmt, + tai_time<_Duration>& __tp, + basic_string<_CharT, _Traits, _Alloc>* __abbrev = nullptr, + minutes* __offset = nullptr) + { + utc_time<_Duration> __ut; + if (chrono::from_stream(__is, __fmt, __ut, __abbrev, __offset)) + __tp = tai_clock::from_utc(__ut); + return __is; + } template inline basic_ostream<_CharT, _Traits>& @@ -2531,8 +2803,19 @@ namespace chrono return __os; } - // TODO: from_stream for gps_time - + template> + inline basic_istream<_CharT, _Traits>& + from_stream(basic_istream<_CharT, _Traits>& __is, const _CharT* __fmt, + gps_time<_Duration>& __tp, + basic_string<_CharT, _Traits, _Alloc>* __abbrev = nullptr, + minutes* __offset = nullptr) + { + utc_time<_Duration> __ut; + if (chrono::from_stream(__is, __fmt, __ut, __abbrev, __offset)) + __tp = gps_clock::from_utc(__ut); + return __is; + } template inline basic_ostream<_CharT, _Traits>& @@ -2543,7 +2826,19 @@ namespace chrono return __os; } - // TODO: from_stream for file_time + template> + inline basic_istream<_CharT, _Traits>& + from_stream(basic_istream<_CharT, _Traits>& __is, const _CharT* __fmt, + file_time<_Duration>& __tp, + basic_string<_CharT, _Traits, _Alloc>* __abbrev = nullptr, + minutes* __offset = nullptr) + { + sys_time<_Duration> __st; + if (chrono::from_stream(__is, __fmt, __st, __abbrev, __offset)) + __tp = file_clock::from_sys(__st); + return __is; + } template inline basic_ostream<_CharT, _Traits>& @@ -2554,7 +2849,1365 @@ namespace chrono return __os; } - // TODO: from_stream for local_time + template> + basic_istream<_CharT, _Traits>& + from_stream(basic_istream<_CharT, _Traits>& __is, const _CharT* __fmt, + local_time<_Duration>& __tp, + basic_string<_CharT, _Traits, _Alloc>* __abbrev = nullptr, + minutes* __offset = nullptr) + { + using __format::_ChronoParts; + auto __need = _ChronoParts::_Year | _ChronoParts::_Month + | _ChronoParts::_Day | _ChronoParts::_TimeOfDay; + __detail::_Parser_t<_Duration> __p(__need); + if (__p(__is, __fmt, __abbrev, __offset)) + { + days __d = __p._M_sys_days.time_since_epoch(); + auto __t = local_days(__d) + __p._M_time; // ignore offset + __tp = chrono::time_point_cast<_Duration>(__t); + } + return __is; + } + + // [time.parse] parsing + +namespace __detail +{ + template, + typename... _OptArgs> + concept __parsable = requires (basic_istream<_CharT, _Traits>& __is, + const _CharT* __fmt, _Parsable& __tp, + _OptArgs*... __args) + { from_stream(__is, __fmt, __tp, __args...); }; + + template, + typename _Alloc = allocator<_CharT>> + struct _Parse + { + private: + using __string_type = basic_string<_CharT, _Traits, _Alloc>; + + public: + _Parse(const _CharT* __fmt, _Parsable& __tp, + basic_string<_CharT, _Traits, _Alloc>* __abbrev = nullptr, + minutes* __offset = nullptr) + : _M_fmt(__fmt), _M_tp(std::__addressof(__tp)), + _M_abbrev(__abbrev), _M_offset(__offset) + { } + + _Parse(_Parse&&) = delete; + _Parse& operator=(_Parse&&) = delete; + + private: + using __stream_type = basic_istream<_CharT, _Traits>; + + const _CharT* const _M_fmt; + _Parsable* const _M_tp; + __string_type* const _M_abbrev; + minutes* const _M_offset; + + friend __stream_type& + operator>>(__stream_type& __is, _Parse&& __p) + { + if (__p._M_offset) + from_stream(__is, __p._M_fmt, *__p._M_tp, __p._M_abbrev, + __p._M_offset); + else if (__p._M_abbrev) + from_stream(__is, __p._M_fmt, *__p._M_tp, __p._M_abbrev); + else + from_stream(__is, __p._M_fmt, *__p._M_tp); + return __is; + } + + friend void operator>>(__stream_type&, _Parse&) = delete; + friend void operator>>(__stream_type&, const _Parse&) = delete; + }; +} // namespace __detail + + template _Parsable> + [[nodiscard, __gnu__::__access__(__read_only__, 1)]] + inline auto + parse(const _CharT* __fmt, _Parsable& __tp) + { return __detail::_Parse<_Parsable, _CharT>(__fmt, __tp); } + + template _Parsable> + [[nodiscard]] + inline auto + parse(const basic_string<_CharT, _Traits, _Alloc>& __fmt, _Parsable& __tp) + { + return __detail::_Parse<_Parsable, _CharT, _Traits>(__fmt.c_str(), __tp); + } + + template, + __detail::__parsable<_CharT, _Traits, _StrT> _Parsable> + [[nodiscard, __gnu__::__access__(__read_only__, 1)]] + inline auto + parse(const _CharT* __fmt, _Parsable& __tp, + basic_string<_CharT, _Traits, _Alloc>& __abbrev) + { + auto __pa = std::__addressof(__abbrev); + return __detail::_Parse<_Parsable, _CharT, _Traits, _Alloc>(__fmt, __tp, + __pa); + } + + template, + __detail::__parsable<_CharT, _Traits, _StrT> _Parsable> + [[nodiscard]] + inline auto + parse(const basic_string<_CharT, _Traits, _Alloc>& __fmt, _Parsable& __tp, + basic_string<_CharT, _Traits, _Alloc>& __abbrev) + { + auto __pa = std::__addressof(__abbrev); + return __detail::_Parse<_Parsable, _CharT, _Traits, _Alloc>(__fmt.c_str(), + __tp, __pa); + } + + template, + typename _StrT = basic_string<_CharT, _Traits>, + __detail::__parsable<_CharT, _Traits, _StrT, minutes> _Parsable> + [[nodiscard, __gnu__::__access__(__read_only__, 1)]] + inline auto + parse(const _CharT* __fmt, _Parsable& __tp, minutes& __offset) + { + return __detail::_Parse<_Parsable, _CharT>(__fmt, __tp, nullptr, + &__offset); + } + + template, + __detail::__parsable<_CharT, _Traits, _StrT, minutes> _Parsable> + [[nodiscard]] + inline auto + parse(const basic_string<_CharT, _Traits, _Alloc>& __fmt, _Parsable& __tp, + minutes& __offset) + { + return __detail::_Parse<_Parsable, _CharT, _Traits, _Alloc>(__fmt.c_str(), + __tp, nullptr, + &__offset); + } + + template, + __detail::__parsable<_CharT, _Traits, _StrT, minutes> _Parsable> + [[nodiscard, __gnu__::__access__(__read_only__, 1)]] + inline auto + parse(const _CharT* __fmt, _Parsable& __tp, + basic_string<_CharT, _Traits, _Alloc>& __abbrev, minutes& __offset) + { + auto __pa = std::__addressof(__abbrev); + return __detail::_Parse<_Parsable, _CharT, _Traits, _Alloc>(__fmt, __tp, + __pa, + &__offset); + } + + template, + __detail::__parsable<_CharT, _Traits, _StrT, minutes> _Parsable> + [[nodiscard]] + inline auto + parse(const basic_string<_CharT, _Traits, _Alloc>& __fmt, _Parsable& __tp, + basic_string<_CharT, _Traits, _Alloc>& __abbrev, minutes& __offset) + { + auto __pa = std::__addressof(__abbrev); + return __detail::_Parse<_Parsable, _CharT, _Traits, _Alloc>(__fmt.c_str(), + __tp, __pa, + &__offset); + } + + /// @cond undocumented + template + template + basic_istream<_CharT, _Traits>& + __detail::_Parser<_Duration>:: + operator()(basic_istream<_CharT, _Traits>& __is, const _CharT* __fmt, + basic_string<_CharT, _Traits, _Alloc>* __abbrev, + minutes* __offset) + { + using sentry = typename basic_istream<_CharT, _Traits>::sentry; + ios_base::iostate __err = ios_base::goodbit; + if (sentry __cerb(__is, true); __cerb) + { + locale __loc = __is.getloc(); + auto& __tmget = std::use_facet>(__loc); + auto& __tmpunct = std::use_facet>(__loc); + + // RAII type to save and restore stream state. + struct _Stream_state + { + explicit + _Stream_state(basic_istream<_CharT, _Traits>& __i) + : _M_is(__i), + _M_flags(__i.flags(ios_base::skipws | ios_base::dec)), + _M_w(__i.width(0)) + { } + + ~_Stream_state() + { + _M_is.flags(_M_flags); + _M_is.width(_M_w); + } + + _Stream_state(_Stream_state&&) = delete; + + basic_istream<_CharT, _Traits>& _M_is; + ios_base::fmtflags _M_flags; + streamsize _M_w; + }; + + auto __is_failed = [](ios_base::iostate __e) { + return static_cast(__e & ios_base::failbit); + }; + + // Read an unsigned integer from the stream and return it. + // Extract no more than __n digits. Set __err on error. + auto __read_unsigned = [&] (int __n) { + return _S_read_unsigned(__is, __err, __n); + }; + + // Read a signed integer from the stream and return it. + // Extract no more than __n digits. Set __err on error. + auto __read_signed = [&] (int __n) { + return _S_read_signed(__is, __err, __n); + }; + + // Read an expected character from the stream. + auto __read_chr = [&__is, &__err] (_CharT __c) { + return _S_read_chr(__is, __err, __c); + }; + + using __format::_ChronoParts; + _ChronoParts __parts{}; + + const year __bad_y = --year::min(); // SHRT_MIN + const month __bad_mon(255); + const day __bad_day(255); + const weekday __bad_wday(255); + const hours __bad_h(-1); + const minutes __bad_min(-9999); + const seconds __bad_sec(-1); + + year __y = __bad_y, __yy = __bad_y; // %Y, %yy + year __iso_y = __bad_y, __iso_yy = __bad_y; // %G, %g + month __m = __bad_mon; // %m + day __d = __bad_day; // %d + weekday __wday = __bad_wday; // %a %A %u %w + hours __h = __bad_h, __h12 = __bad_h; // %H, %I + minutes __min = __bad_min; // %M + _Duration __s = __bad_sec; // %S + int __ampm = 0; // %p + int __iso_wk = -1, __sunday_wk = -1, __monday_wk = -1; // %V, %U, %W + int __century = -1; // %C + int __dayofyear = -1; // %j (for non-duration) + + minutes __tz_offset = __bad_min; + basic_string<_CharT, _Traits> __tz_abbr; + + // bool __is_neg = false; // TODO: how is this handled for parsing? + + _CharT __mod{}; // One of 'E' or 'O' or nul. + unsigned __num = 0; // Non-zero for N modifier. + bool __is_flag = false; // True if we're processing a % flag. + + // If an out-of-range value is extracted (e.g. 61min for %M), + // do not set failbit immediately because we might not need it + // (e.g. parsing chrono::year doesn't care about invalid %M values). + // Instead set the variable back to its initial 'bad' state, + // and also set related variables corresponding to the same field + // (e.g. a bad %M value for __min should also reset __h and __s). + // If a valid value is needed later the bad value will cause failure. + + // For some fields we don't know the correct range when parsing and + // we have to be liberal in what we accept, e.g. we allow 366 for + // day-of-year because that's valid in leap years, and we allow 31 + // for day-of-month. If those values are needed to determine the + // result then we can do a correct range check at the end when we + // know the how many days the relevant year or month actually has. + + while (*__fmt) + { + _CharT __c = *__fmt++; + if (!__is_flag) + { + if (__c == '%') + __is_flag = true; // This is the start of a flag. + else if (std::isspace(__c, __loc)) + std::ws(__is); // Match zero or more whitespace characters. + else if (!__read_chr(__c)) [[unlikely]] + break; // Failed to match the expected character. + + continue; // Process next character in the format string. + } + + // Now processing a flag. + switch (__c) + { + case 'a': // Locale's weekday name + case 'A': // (full or abbreviated, matched case-insensitively). + if (__mod || __num) [[unlikely]] + __err = ios_base::failbit; + else + { + struct tm __tm{}; + __tmget.get(__is, {}, __is, __err, &__tm, + __fmt - 2, __fmt); + if (!__is_failed(__err)) + __wday = weekday(__tm.tm_wday); + } + __parts |= _ChronoParts::_Weekday; + break; + + case 'b': // Locale's month name + case 'h': // (full or abbreviated, matched case-insensitively). + case 'B': + if (__mod || __num) [[unlikely]] + __err = ios_base::failbit; + else + { + // strptime behaves differently for %b and %B, + // but chrono::parse says they're equivalent. + // Luckily libstdc++ std::time_get works as needed. + struct tm __tm{}; + __tmget.get(__is, {}, __is, __err, &__tm, + __fmt - 2, __fmt); + if (!__is_failed(__err)) + __m = month(__tm.tm_mon + 1); + } + __parts |= _ChronoParts::_Month; + break; + + case 'c': // Locale's date and time representation. + if (__mod == 'O' || __num) [[unlikely]] + __err |= ios_base::failbit; + else + { + struct tm __tm{}; + __tmget.get(__is, {}, __is, __err, &__tm, + __fmt - 2 - (__mod == 'E'), __fmt); + if (!__is_failed(__err)) + { + __y = year(__tm.tm_year + 1900); + __m = month(__tm.tm_mon + 1); + __d = day(__tm.tm_mday); + __h = hours(__tm.tm_hour); + __min = minutes(__tm.tm_min); + __s = duration_cast<_Duration>(seconds(__tm.tm_sec)); + } + } + __parts |= _ChronoParts::_DateTime; + break; + + case 'C': // Century + if (!__mod) [[likely]] + { + auto __v = __read_signed(__num ? __num : 2); + if (!__is_failed(__err)) + __century = __v * 100; + } + else if (__mod == 'E') + { + struct tm __tm{}; + __tmget.get(__is, {}, __is, __err, &__tm, + __fmt - 3, __fmt); + if (!__is_failed(__err)) + __century = __tm.tm_year; + } + else [[unlikely]] + __err |= ios_base::failbit; + // N.B. don't set this here: __parts |= _ChronoParts::_Year; + break; + + case 'd': // Day of month (1-31) + case 'e': + if (!__mod) [[likely]] + { + auto __v = __read_unsigned(__num ? __num : 2); + if (!__is_failed(__err)) + __d = day(__v); + } + else if (__mod == 'O') + { + struct tm __tm{}; + __tmget.get(__is, {}, __is, __err, &__tm, + __fmt - 3, __fmt); + if (!__is_failed(__err)) + __d = day(__tm.tm_mday); + } + else [[unlikely]] + __err |= ios_base::failbit; + __parts |= _ChronoParts::_Day; + break; + + case 'D': // %m/%d/%y + if (__mod || __num) [[unlikely]] + __err |= ios_base::failbit; + else + { + auto __month = __read_unsigned(2); // %m + __read_chr('/'); + auto __day = __read_unsigned(2); // %d + __read_chr('/'); + auto __year = __read_unsigned(2); // %y + if (__is_failed(__err)) + break; + __y = year(__year + 1900 + 100 * int(__year < 69)); + __m = month(__month); + __d = day(__day); + if (!year_month_day(__y, __m, __d).ok()) + { + __y = __yy = __iso_y = __iso_yy = __bad_y; + __m = __bad_mon; + __d = __bad_day; + break; + } + } + __parts |= _ChronoParts::_Date; + break; + + case 'F': // %Y-%m-%d - any N modifier only applies to %Y. + if (__mod) [[unlikely]] + __err |= ios_base::failbit; + else + { + auto __year = __read_signed(__num ? __num : 4); // %Y + __read_chr('-'); + auto __month = __read_unsigned(2); // %m + __read_chr('-'); + auto __day = __read_unsigned(2); // %d + if (__is_failed(__err)) + break; + __y = year(__year); + __m = month(__month); + __d = day(__day); + if (!year_month_day(__y, __m, __d).ok()) + { + __y = __yy = __iso_y = __iso_yy = __bad_y; + __m = __bad_mon; + __d = __bad_day; + break; + } + } + __parts |= _ChronoParts::_Date; + break; + + case 'g': // Last two digits of ISO week-based year. + if (__mod) [[unlikely]] + __err |= ios_base::failbit; + else + { + auto __val = __read_unsigned(__num ? __num : 2); + if (__val >= 0 && __val <= 99) + { + __iso_yy = year(__val); + if (__century == -1) // No %C has been parsed yet. + __century = 2000; + } + else + __iso_yy = __iso_y = __y = __yy = __bad_y; + } + __parts |= _ChronoParts::_Year; + break; + + case 'G': // ISO week-based year. + if (__mod) [[unlikely]] + __err |= ios_base::failbit; + else + __iso_y = year(__read_unsigned(__num ? __num : 4)); + __parts |= _ChronoParts::_Year; + break; + + case 'H': // 24-hour (00-23) + case 'I': // 12-hour (1-12) + if (__mod == 'E') [[unlikely]] + __err |= ios_base::failbit; + else if (__mod == 'O') + { +#if 0 + struct tm __tm{}; + __tm.tm_ampm = 1; + __tmget.get(__is, {}, __is, __err, &__tm, + __fmt - 3, __fmt); + if (!__is_failed(__err)) + { + if (__c == 'I') + { + __h12 = hours(__tm.tm_hour); + __h = __bad_h; + } + else + __h = hours(__tm.tm_hour); + } +#else + // XXX %OI seems to be unimplementable. + __err |= ios_base::failbit; +#endif + } + else + { + auto __val = __read_unsigned(__num ? __num : 2); + if (__c == 'I' && __val >= 1 && __val <= 12) + { + __h12 = hours(__val); + __h = __bad_h; + } + else if (__c == 'H' && __val >= 0 && __val <= 23) + __h = hours(__val); + } + __parts |= _ChronoParts::_TimeOfDay; + break; + + case 'j': // For duration, count of days, otherwise day of year + if (__mod) [[unlikely]] + __err |= ios_base::failbit; + else if (_M_need == _ChronoParts::_TimeOfDay) // duration + { + auto __val = __read_signed(__num ? __num : 3); + if (!__is_failed(__err)) + { + __h = days(__val); // __h will get added to _M_time + __parts |= _ChronoParts::_TimeOfDay; + } + } + else + { + __dayofyear = __read_unsigned(__num ? __num : 3); + // N.B. do not alter __parts here, done after loop. + // No need for range checking here either. + } + break; + + case 'm': // Month (1-12) + if (__mod == 'E') [[unlikely]] + __err |= ios_base::failbit; + else if (__mod == 'O') + { + struct tm __tm{}; + __tmget.get(__is, {}, __is, __err, &__tm, + __fmt - 2, __fmt); + if (!__is_failed(__err)) + __m = month(__tm.tm_mon + 1); + } + else + { + auto __val = __read_unsigned(__num ? __num : 2); + if (__val >= 1 && __val <= 12) + __m = month(__val); + else + __m = __bad_mon; + } + __parts |= _ChronoParts::_Month; + break; + + case 'M': // Minutes + if (__mod == 'E') [[unlikely]] + __err |= ios_base::failbit; + else if (__mod == 'O') + { + struct tm __tm{}; + __tmget.get(__is, {}, __is, __err, &__tm, + __fmt - 2, __fmt); + if (!__is_failed(__err)) + __min = minutes(__tm.tm_min); + } + else + { + auto __val = __read_unsigned(__num ? __num : 2); + if (0 <= __val && __val < 60) + __min = minutes(__val); + else + { + __h = __bad_h; + __min = __bad_min; + __s = __bad_sec; + break; + } + } + __parts |= _ChronoParts::_TimeOfDay; + break; + + case 'p': // Locale's AM/PM designation for 12-hour clock. + if (__mod || __num) + __err |= ios_base::failbit; + else + { + // Can't use std::time_get here as it can't parse %p + // in isolation without %I. This might be faster anyway. + const _CharT* __ampms[2]; + __tmpunct._M_am_pm(__ampms); + int __n = 0, __which = 3; + while (__which != 0) + { + auto __i = __is.peek(); + if (_Traits::eq_int_type(__i, _Traits::eof())) + { + __err |= ios_base::eofbit | ios_base::failbit; + break; + } + __i = std::toupper(_Traits::to_char_type(__i), __loc); + if (__which & 1) + { + if (__i != std::toupper(__ampms[0][__n], __loc)) + __which ^= 1; + else if (__ampms[0][__n + 1] == _CharT()) + { + __which = 1; + (void) __is.get(); + break; + } + } + if (__which & 2) + { + if (__i != std::toupper(__ampms[1][__n], __loc)) + __which ^= 2; + else if (__ampms[1][__n + 1] == _CharT()) + { + __which = 2; + (void) __is.get(); + break; + } + } + if (__which) + (void) __is.get(); + ++__n; + } + if (__which == 0 || __which == 3) + __err |= ios_base::failbit; + else + __ampm = __which; + } + break; + + case 'r': // Locale's 12-hour time. + if (__mod || __num) + __err |= ios_base::failbit; + else + { + struct tm __tm{}; + __tmget.get(__is, {}, __is, __err, &__tm, + __fmt - 2, __fmt); + if (!__is_failed(__err)) + { + __h = hours(__tm.tm_hour); + __min = minutes(__tm.tm_min); + __s = seconds(__tm.tm_sec); + } + } + __parts |= _ChronoParts::_TimeOfDay; + break; + + case 'R': // %H:%M + case 'T': // %H:%M:%S + if (__mod || __num) [[unlikely]] + { + __err |= ios_base::failbit; + break; + } + else + { + auto __val = __read_unsigned(2); + if (__val == -1 || __val > 23) + { + __h = __bad_h; + __min = __bad_min; + __s = __bad_sec; + break; + } + if (!__read_chr(':')) + { + __err |= ios_base::failbit; + break; + } + __h = hours(__val); + + __val = __read_unsigned(2); + if (__val == -1 || __val > 60) + { + __h = __bad_h; + __min = __bad_min; + __s = __bad_sec; + break; + } + __min = minutes(__val); + + __parts |= _ChronoParts::_TimeOfDay; + + if (__c != 'T' || !__read_chr(':')) + break; + } + [[fallthrough]]; + + case 'S': // Seconds + if (__mod == 'E') [[unlikely]] + __err |= ios_base::failbit; + else if (__mod == 'O') + { + struct tm __tm{}; + __tmget.get(__is, {}, __is, __err, &__tm, + __fmt - 3, __fmt); + if (!__is_failed(__err)) + __s = seconds(__tm.tm_sec); + } + else if constexpr (ratio_equal_v>) + { + auto __val = __read_unsigned(__num ? __num : 2); + if (0 <= __val && __val <= 59) + __s = seconds(__val); + else + { + __h = __bad_h; + __min = __bad_min; + __s = __bad_sec; + break; + } + } + else + { + basic_stringstream<_CharT> __buf; + auto __digit = _S_try_read_digit(__is, __err); + if (__digit != -1) + { + __buf.put(_CharT('0') + __digit); + __digit = _S_try_read_digit(__is, __err); + if (__digit != -1) + __buf.put(_CharT('0') + __digit); + } + + auto __i = __is.peek(); + if (_Traits::eq_int_type(__i, _Traits::eof())) + __err |= ios_base::eofbit; + else + { + auto& __np = use_facet>(__loc); + auto __dp = __np.decimal_point(); + _CharT __c = _Traits::to_char_type(__i); + if (__c == __dp) + { + (void) __is.get(); + __buf.put(__c); + int __prec + = hh_mm_ss<_Duration>::fractional_width; + do + { + __digit = _S_try_read_digit(__is, __err); + if (__digit != -1) + __buf.put(_CharT('0') + __digit); + else + break; + } + while (--__prec); + } + } + + if (!__is_failed(__err)) + { + auto& __ng = use_facet>(__loc); + long double __val; + ios_base::iostate __err2{}; + __ng.get(__buf, {}, __buf, __err2, __val); + if (__is_failed(__err2)) [[unlikely]] + __err |= __err2; + else + { + duration __fs(__val); + __s = duration_cast<_Duration>(__fs); + } + } + } + __parts |= _ChronoParts::_TimeOfDay; + break; + + case 'u': // ISO weekday (1-7) + case 'w': // Weekday (0-6) + if (__mod == 'E') [[unlikely]] + __err |= ios_base::failbit; + else if (__mod == 'O') + { + if (__c == 'w') + { + struct tm __tm{}; + __tmget.get(__is, {}, __is, __err, &__tm, + __fmt - 3, __fmt); + if (!__is_failed(__err)) + __wday = weekday(__tm.tm_wday); + } + else + __err |= ios_base::failbit; + } + else + { + const int __lo = __c == 'u' ? 1 : 0; + const int __hi = __lo + 6; + auto __val = __read_unsigned(__num ? __num : 1); + if (__lo <= __val && __val <= __hi) + __wday = weekday(__val); + else + { + __wday = __bad_wday; + break; + } + } + __parts |= _ChronoParts::_Weekday; + break; + + case 'U': // Week number of the year (from first Sunday). + case 'V': // ISO week-based week number. + case 'W': // Week number of the year (from first Monday). + if (__mod == 'E') [[unlikely]] + __err |= ios_base::failbit; + else if (__mod == 'O') + { + if (__c == 'V') [[unlikely]] + __err |= ios_base::failbit; + else + { + // TODO nl_langinfo_l(ALT_DIGITS) ? + // Not implementable using std::time_get. + } + } + else + { + const int __lo = __c == 'V' ? 1 : 0; + const int __hi = 53; + auto __val = __read_unsigned(__num ? __num : 2); + if (__lo <= __val && __val <= __hi) + { + switch (__c) + { + case 'U': + __sunday_wk = __val; + break; + case 'V': + __iso_wk = __val; + break; + case 'W': + __monday_wk = __val; + break; + } + } + else + __iso_wk = __sunday_wk = __monday_wk = -1; + } + // N.B. do not alter __parts here, done after loop. + break; + + case 'x': // Locale's date representation. + if (__mod == 'O' || __num) [[unlikely]] + __err |= ios_base::failbit; + else + { + struct tm __tm{}; + __tmget.get(__is, {}, __is, __err, &__tm, + __fmt - 2 - (__mod == 'E'), __fmt); + if (!__is_failed(__err)) + { + __y = year(__tm.tm_year + 1900); + __m = month(__tm.tm_mon + 1); + __d = day(__tm.tm_mday); + } + } + __parts |= _ChronoParts::_Date; + break; + + case 'X': // Locale's time representation. + if (__mod == 'O' || __num) [[unlikely]] + __err |= ios_base::failbit; + else + { + struct tm __tm{}; + __tmget.get(__is, {}, __is, __err, &__tm, + __fmt - 2 - (__mod == 'E'), __fmt); + if (!__is_failed(__err)) + { + __h = hours(__tm.tm_hour); + __min = minutes(__tm.tm_min); + __s = duration_cast<_Duration>(seconds(__tm.tm_sec)); + } + } + __parts |= _ChronoParts::_TimeOfDay; + break; + + case 'y': // Last two digits of year. + if (__mod) [[unlikely]] + { + struct tm __tm{}; + __tmget.get(__is, {}, __is, __err, &__tm, + __fmt - 3, __fmt); + if (!__is_failed(__err)) + { + int __cent = __tm.tm_year < 2000 ? 1900 : 2000; + __yy = year(__tm.tm_year - __cent); + if (__century == -1) // No %C has been parsed yet. + __century = __cent; + } + } + else + { + auto __val = __read_unsigned(__num ? __num : 2); + if (__val >= 0 && __val <= 99) + { + __yy = year(__val); + if (__century == -1) // No %C has been parsed yet. + __century = __val < 69 ? 2000 : 1900; + } + else + __y = __yy = __iso_yy = __iso_y = __bad_y; + } + __parts |= _ChronoParts::_Year; + break; + + case 'Y': // Year + if (__mod == 'O') [[unlikely]] + __err |= ios_base::failbit; + else if (__mod == 'E') + { + struct tm __tm{}; + __tmget.get(__is, {}, __is, __err, &__tm, + __fmt - 3, __fmt); + if (!__is_failed(__err)) + __y = year(__tm.tm_year); + } + else + { + auto __val = __read_unsigned(__num ? __num : 4); + if (!__is_failed(__err)) + __y = year(__val); + } + __parts |= _ChronoParts::_Year; + break; + + case 'z': + if (__num) [[unlikely]] + __err |= ios_base::failbit; + else + { + // For %Ez and %Oz read [+|-][h]h[:mm]. + // For %z read [+|-]hh[mm]. + + auto __i = __is.peek(); + if (_Traits::eq_int_type(__i, _Traits::eof())) + { + __err |= ios_base::eofbit | ios_base::failbit; + break; + } + _CharT __ic = _Traits::to_char_type(__i); + const bool __neg = __ic == _CharT('-'); + if (__ic == _CharT('-') || __ic == _CharT('+')) + (void) __is.get(); + + int_least32_t __hh; + if (__mod) + { + // Read h[h] + __hh = __read_unsigned(2); + } + else + { + // Read hh + __hh = 10 * _S_try_read_digit(__is, __err); + __hh += _S_try_read_digit(__is, __err); + } + + if (__is_failed(__err)) + break; + + __i = __is.peek(); + if (_Traits::eq_int_type(__i, _Traits::eof())) + { + __err |= ios_base::eofbit; + __tz_offset = minutes(__hh * (__neg ? -60 : 60)); + break; + } + __ic = _Traits::to_char_type(__i); + + bool __read_mm = false; + if (__mod) + { + if (__ic == _GLIBCXX_WIDEN(":")[0]) + { + // Read [:mm] part. + (void) __is.get(); + __read_mm = true; + } + } + else if (_CharT('0') <= __ic && __ic <= _CharT('9')) + { + // Read [mm] part. + __read_mm = true; + } + + int_least32_t __mm = 0; + if (__read_mm) + { + __mm = 10 * _S_try_read_digit(__is, __err); + __mm += _S_try_read_digit(__is, __err); + } + + if (!__is_failed(__err)) + { + auto __z = __hh * 60 + __mm; + __tz_offset = minutes(__neg ? -__z : __z); + } + } + break; + + case 'Z': + if (__mod || __num) [[unlikely]] + __err |= ios_base::failbit; + else + { + basic_string_view<_CharT> __x = _GLIBCXX_WIDEN("_/-+"); + __tz_abbr.clear(); + while (true) + { + auto __i = __is.peek(); + if (!_Traits::eq_int_type(__i, _Traits::eof())) + { + _CharT __a = _Traits::to_char_type(__i); + if (std::isalnum(__a, __loc) + || __x.find(__a) != __x.npos) + { + __tz_abbr.push_back(__a); + (void) __is.get(); + continue; + } + } + else + __err |= ios_base::eofbit; + break; + } + if (__tz_abbr.empty()) + __err |= ios_base::failbit; + } + break; + + case 'n': // Exactly one whitespace character. + if (__mod || __num) [[unlikely]] + __err |= ios_base::failbit; + else + { + _CharT __i = __is.peek(); + if (_Traits::eq_int_type(__i, _Traits::eof())) + __err |= ios_base::eofbit | ios_base::failbit; + else if (std::isspace(_Traits::to_char_type(__i), __loc)) + (void) __is.get(); + else + __err |= ios_base::failbit; + } + break; + + case 't': // Zero or one whitespace characters. + if (__mod || __num) [[unlikely]] + __err |= ios_base::failbit; + else + { + _CharT __i = __is.peek(); + if (_Traits::eq_int_type(__i, _Traits::eof())) + __err |= ios_base::eofbit; + else if (std::isspace(_Traits::to_char_type(__i), __loc)) + (void) __is.get(); + } + break; + + case '%': // A % character. + if (__mod || __num) [[unlikely]] + __err |= ios_base::failbit; + else + __read_chr('%'); + break; + + case 'O': // Modifiers + case 'E': + if (__mod || __num) [[unlikely]] + { + __err |= ios_base::failbit; + break; + } + __mod = __c; + continue; + + default: + if (_CharT('1') <= __c && __c <= _CharT('9')) + { + if (!__mod) [[likely]] + { + // %Nx - extract positive decimal integer N + auto __end = __fmt + _Traits::length(__fmt); + auto [__v, __ptr] + = __format::__parse_integer(__fmt - 1, __end); + if (__ptr) [[likely]] + { + __num = __v; + __fmt = __ptr; + continue; + } + } + } + __err |= ios_base::failbit; + } + + if (__is_failed(__err)) [[unlikely]] + break; + + __is_flag = false; + __num = 0; + __mod = _CharT(); + } + + if (__century >= 0) + { + if (__yy != __bad_y && __y == __bad_y) + __y = years(__century) + __yy; // Use %y instead of %Y + if (__iso_yy != __bad_y && __iso_y == __bad_y) + __iso_y = years(__century) + __iso_yy; // Use %g instead of %G + } + + bool __can_use_doy = false; + bool __can_use_iso_wk = false; + bool __can_use_sun_wk = false; + bool __can_use_mon_wk = false; + + // A year + day-of-year can be converted to a full date. + if (__y != __bad_y && __dayofyear >= 0) + { + __can_use_doy = true; + __parts |= _ChronoParts::_Date; + } + else if (__y != __bad_y && __wday != __bad_wday && __sunday_wk >= 0) + { + __can_use_sun_wk = true; + __parts |= _ChronoParts::_Date; + } + else if (__y != __bad_y && __wday != __bad_wday && __monday_wk >= 0) + { + __can_use_mon_wk = true; + __parts |= _ChronoParts::_Date; + } + else if (__iso_y != __bad_y && __wday != __bad_wday && __iso_wk > 0) + { + // An ISO week date can be converted to a full date. + __can_use_iso_wk = true; + __parts |= _ChronoParts::_Date; + } + + if (__is_failed(__err)) [[unlikely]] + ; // Don't bother doing any more work. + else if (__is_flag) [[unlikely]] // incomplete format flag + __err |= ios_base::failbit; + else if ((_M_need & __parts) == _M_need) [[likely]] + { + // We try to avoid calculating _M_sys_days and _M_ymd unless + // necessary, because converting sys_days to year_month_day + // (or vice versa) requires non-trivial calculations. + // If we have y/m/d values then use them to populate _M_ymd + // and only convert it to _M_sys_days if the caller needs that. + // But if we don't have y/m/d and need to calculate the date + // from the day-of-year or a week+weekday then we set _M_sys_days + // and only convert it to _M_ymd if the caller needs that. + + // We do more error checking here, but only for the fields that + // we actually need to use. For example, we will not diagnose + // an invalid dayofyear==366 for non-leap years unless actually + // using __dayofyear. This should mean we never produce invalid + // results, but it means not all invalid inputs are diagnosed, + // e.g. "2023-01-01 366" >> "%F %j" ignores the invalid 366. + // We also do not diagnose inconsistent values for the same + // field, e.g. "2021 2022 2023" >> "%C%y %Y %Y" just uses 2023. + + // Whether the caller wants _M_wd. + // The _Weekday bit is only set for chrono::weekday. + const bool __need_wday = _M_need & _ChronoParts::_Weekday; + + // Whether the caller wants _M_sys_days and _M_time. + // Only true for time_points. + const bool __need_time = _M_need & _ChronoParts::_TimeOfDay; + + if (__need_wday && __wday != __bad_wday) + _M_wd = __wday; // Caller only wants a weekday and we have one. + else if (_M_need & _ChronoParts::_Date) // subsumes __need_wday + { + // Whether the caller wants _M_ymd. + // True for chrono::year etc., false for time_points. + const bool __need_ymd = !__need_wday && !__need_time; + + if ((_M_need & _ChronoParts::_Year && __y == __bad_y) + || (_M_need & _ChronoParts::_Month && __m == __bad_mon) + || (_M_need & _ChronoParts::_Day && __d == __bad_day)) + { + // Missing at least one of y/m/d so calculate sys_days + // from the other data we have available. + + if (__can_use_doy) + { + if ((0 < __dayofyear && __dayofyear <= 365) + || (__dayofyear == 366 && __y.is_leap())) + [[likely]] + { + _M_sys_days = sys_days(__y/January/1) + + days(__dayofyear - 1); + if (__need_ymd) + _M_ymd = year_month_day(_M_sys_days); + } + else + __err |= ios_base::failbit; + } + else if (__can_use_iso_wk) + { + // Calculate y/m/d from ISO week date. + + if (__iso_wk == 53) + { + // A year has 53 weeks iff Jan 1st is a Thursday + // or Jan 1 is a Wednesday and it's a leap year. + const sys_days __jan4(__iso_y/January/4); + weekday __wd1(__jan4 - days(3)); + if (__wd1 != Thursday) + if (__wd1 != Wednesday || !__iso_y.is_leap()) + __err |= ios_base::failbit; + } + + if (!__is_failed(__err)) [[likely]] + { + // First Thursday is always in week one: + sys_days __w(Thursday[1]/January/__iso_y); + // First day of week-based year: + __w -= Thursday - Monday; + __w += days(weeks(__iso_wk - 1)); + __w += __wday - Monday; + _M_sys_days = __w; + + if (__need_ymd) + _M_ymd = year_month_day(_M_sys_days); + } + } + else if (__can_use_sun_wk) + { + // Calculate y/m/d from week number + weekday. + sys_days __wk1(__y/January/Sunday[1]); + _M_sys_days = __wk1 + weeks(__sunday_wk - 1) + + days(__wday.c_encoding()); + _M_ymd = year_month_day(_M_sys_days); + if (_M_ymd.year() != __y) [[unlikely]] + __err |= ios_base::failbit; + } + else if (__can_use_mon_wk) + { + // Calculate y/m/d from week number + weekday. + sys_days __wk1(__y/January/Monday[1]); + _M_sys_days = __wk1 + weeks(__monday_wk - 1) + + days(__wday.c_encoding() - 1); + _M_ymd = year_month_day(_M_sys_days); + if (_M_ymd.year() != __y) [[unlikely]] + __err |= ios_base::failbit; + } + else // Should not be able to get here. + __err |= ios_base::failbit; + } + else + { + // We know that all fields the caller needs are present, + // but check that their values are in range. + // Make unwanted fields valid so that _M_ymd.ok() is true. + + if (_M_need & _ChronoParts::_Year) + { + if (!__y.ok()) [[unlikely]] + __err |= ios_base::failbit; + } + else if (__y == __bad_y) + __y = 1972y; // Leap year so that Feb 29 is valid. + + if (_M_need & _ChronoParts::_Month) + { + if (!__m.ok()) [[unlikely]] + __err |= ios_base::failbit; + } + else if (__m == __bad_mon) + __m = January; + + if (_M_need & _ChronoParts::_Day) + { + if (__d < day(1) || __d > (__y/__m/last).day()) + __err |= ios_base::failbit; + } + else if (__d == __bad_day) + __d = 1d; + + if (year_month_day __ymd(__y, __m, __d); __ymd.ok()) + { + _M_ymd = __ymd; + if (__need_wday || __need_time) + _M_sys_days = sys_days(_M_ymd); + } + else [[unlikely]] + __err |= ios_base::failbit; + } + + if (__need_wday) + _M_wd = weekday(_M_sys_days); + } + + // Need to set _M_time for both durations and time_points. + if (__need_time) + { + if (__h == __bad_h && __h12 != __bad_h) + { + if (__ampm == 1) + __h = __h12 == hours(12) ? hours(0) : __h12; + else if (__ampm == 2) + __h = __h12 == hours(12) ? __h12 : __h12 + hours(12); + else [[unlikely]] + __err |= ios_base::failbit; + } + + auto __t = _M_time.zero(); + bool __ok = false; + + if (__h != __bad_h) + { + __ok = true; + __t += __h; + } + + if (__min != __bad_min) + { + __ok = true; + __t += __min; + } + + if (__s != __bad_sec) + { + __ok = true; + __t += __s; + } + + if (__ok) + _M_time = __t; + else + __err |= ios_base::failbit; + } + + if (!__is_failed(__err)) [[likely]] + { + if (__offset && __tz_offset != __bad_min) + *__offset = __tz_offset; + if (__abbrev && !__tz_abbr.empty()) + *__abbrev = std::move(__tz_abbr); + } + } + else + __err |= ios_base::failbit; + } + if (__err) + __is.setstate(__err); + return __is; + } + /// @endcond #undef _GLIBCXX_WIDEN /// @} group chrono diff --git a/libstdc++-v3/include/std/chrono b/libstdc++-v3/include/std/chrono index e63d6c71b4a9..6fdc0c86a276 100644 --- a/libstdc++-v3/include/std/chrono +++ b/libstdc++-v3/include/std/chrono @@ -51,9 +51,8 @@ #endif #if __cplusplus >= 202002L -// TODO formatting and parsing -// # undef __cpp_lib_chrono -// # define __cpp_lib_chrono 201907L +# undef __cpp_lib_chrono +# define __cpp_lib_chrono 201907L #endif namespace std _GLIBCXX_VISIBILITY(default) diff --git a/libstdc++-v3/include/std/version b/libstdc++-v3/include/std/version index 02ead8f1443e..5264c8bff34b 100644 --- a/libstdc++-v3/include/std/version +++ b/libstdc++-v3/include/std/version @@ -279,8 +279,8 @@ # define __cpp_lib_barrier 201907L # endif #endif -// #undef __cpp_lib_chrono -// #define __cpp_lib_chrono 201907L +#undef __cpp_lib_chrono +#define __cpp_lib_chrono 201907L // FIXME: #define __cpp_lib_execution 201902L #define __cpp_lib_constexpr_complex 201711L #define __cpp_lib_constexpr_dynamic_alloc 201907L diff --git a/libstdc++-v3/testsuite/20_util/duration/arithmetic/constexpr_c++17.cc b/libstdc++-v3/testsuite/20_util/duration/arithmetic/constexpr_c++17.cc index 8ccce356a2a4..6fe5475caf02 100644 --- a/libstdc++-v3/testsuite/20_util/duration/arithmetic/constexpr_c++17.cc +++ b/libstdc++-v3/testsuite/20_util/duration/arithmetic/constexpr_c++17.cc @@ -22,7 +22,7 @@ #ifndef __cpp_lib_chrono # error "Feature-test macro for constexpr missing" -#elif __cpp_lib_chrono != 201611 +#elif __cpp_lib_chrono < 201611 # error "Feature-test macro for constexpr has wrong value" #endif diff --git a/libstdc++-v3/testsuite/20_util/duration/io.cc b/libstdc++-v3/testsuite/20_util/duration/io.cc index ea94b062d969..5cbc050e2101 100644 --- a/libstdc++-v3/testsuite/20_util/duration/io.cc +++ b/libstdc++-v3/testsuite/20_util/duration/io.cc @@ -97,10 +97,110 @@ test_format() } } +void +test_parse() +{ + using namespace std::chrono; + seconds s; + milliseconds ms; + microseconds us; + + std::istringstream is(" 2023-07-24 13:05"); + VERIFY( is >> parse(" %Y-%m-%d %H:%M", s) ); + VERIFY( is.good() ); + VERIFY( s == 13h + 5min ); + + s = 999s; + + is.clear(); + is.str("Thursday July 2023"); + VERIFY( !(is >> parse("%a %b %C%y", s)) ); + VERIFY( ! is.eof() ); + VERIFY( s == 999s ); + + is.clear(); + is.str("27"); + VERIFY( is >> parse("%j", s) ); + VERIFY( is.eof() ); + VERIFY( s == 24h * 27 ); + + is.clear(); + is.str("027"); + VERIFY( is >> parse("%j", s) ); + VERIFY( ! is.eof() ); + VERIFY( s == 24h * 27 ); + + is.clear(); + is.str("0027"); + VERIFY( is >> parse("%j", s) ); // defaults to %3j + VERIFY( is.get() == '7' ); + VERIFY( s == 24h * 2 ); + + is.clear(); + is.str("1234"); + VERIFY( is >> parse("%2j", s) ); + VERIFY( is.get() == '3' ); + VERIFY( s == 24h * 12 ); + + is.clear(); + is.str("001234"); + VERIFY( is >> parse("%4j", s) ); + VERIFY( is.get() == '3' ); + VERIFY( s == 24h * 12 ); + + is.clear(); + is.str("1234"); + VERIFY( is >> parse("%4j", s) ); + VERIFY( ! is.eof() ); + VERIFY( s == 24h * 1234 ); + + is.clear(); + is.str("125"); + VERIFY( is >> parse("%S", s) ); + VERIFY( s == 12s ); + VERIFY( is.get() == '5' ); + + is.clear(); + is.str("0.125"); + VERIFY( is >> parse("%S", s) ); + VERIFY( s == 0s ); + VERIFY( is.get() == '.' ); + + is.clear(); + is.str("0.125"); + VERIFY( is >> parse("%S", ms) ); + VERIFY( ms == 125ms ); + VERIFY( ! is.eof() ); + + is.clear(); + is.str("00.125"); + VERIFY( is >> parse("%S", ms) ); + VERIFY( ms == 125ms ); + VERIFY( ! is.eof() ); + + is.clear(); + is.str("012.345"); + VERIFY( is >> parse("%S", ms) ); + VERIFY( ms == 1000ms ); + VERIFY( is.get() == '2' ); + + is.clear(); + is.str("0.1256"); + VERIFY( is >> parse("%S", ms) ); + VERIFY( ms == 125ms ); + VERIFY( is.get() == '6' ); + + is.clear(); + is.str("0.0009765"); + VERIFY( is >> parse("%S", us) ); + VERIFY( us == 976us ); + VERIFY( is.get() == '5' ); +} + int main() { test01(); test02(); test_format(); - // TODO: test_parse(); + test_parse(); } diff --git a/libstdc++-v3/testsuite/std/time/clock/file/io.cc b/libstdc++-v3/testsuite/std/time/clock/file/io.cc index c8e82bb111c2..a6c7c71cfd3a 100644 --- a/libstdc++-v3/testsuite/std/time/clock/file/io.cc +++ b/libstdc++-v3/testsuite/std/time/clock/file/io.cc @@ -17,7 +17,25 @@ test_ostream() VERIFY( ss1.str() == ss2.str() ); } +void +test_parse() +{ + using namespace std::chrono; + const sys_seconds expected = sys_days(2023y/August/9) + 20h + 44min; + file_time tp; + + minutes offset; + std::string abbrev; + std::istringstream is("002023-08-09 21:44 +01 BST!"); + VERIFY( is >> parse("%6F %R %z %Z", tp, abbrev, offset) ); + VERIFY( ! is.eof() ); + VERIFY( tp == clock_cast(expected) ); + VERIFY( abbrev == "BST" ); + VERIFY( offset == 60min ); +} + int main() { test_ostream(); + test_parse(); } diff --git a/libstdc++-v3/testsuite/std/time/clock/gps/io.cc b/libstdc++-v3/testsuite/std/time/clock/gps/io.cc index 29f3148cf14b..c4fe9bee0f1e 100644 --- a/libstdc++-v3/testsuite/std/time/clock/gps/io.cc +++ b/libstdc++-v3/testsuite/std/time/clock/gps/io.cc @@ -6,7 +6,7 @@ #include void -test01() +test_ostream() { using std::format; using namespace std::chrono; @@ -18,7 +18,25 @@ test01() VERIFY( s == "2000-01-01 00:00:00 UTC == 2000-01-01 00:00:13 GPS" ); } +void +test_parse() +{ + using namespace std::chrono; + const sys_seconds expected = sys_days(2023y/August/9) + 20h + 44min + 3s; + gps_seconds tp; + + minutes offset; + std::string abbrev; + std::istringstream is("2023-8-9 21:44:3 +1 BST#"); + VERIFY( is >> parse("%9F %T %Oz %Z", tp, abbrev, offset) ); + VERIFY( ! is.eof() ); + VERIFY( tp == clock_cast(expected) ); + VERIFY( abbrev == "BST" ); + VERIFY( offset == 60min ); +} + int main() { - test01(); + test_ostream(); + test_parse(); } diff --git a/libstdc++-v3/testsuite/std/time/clock/local/io.cc b/libstdc++-v3/testsuite/std/time/clock/local/io.cc new file mode 100644 index 000000000000..a7c018d95d4d --- /dev/null +++ b/libstdc++-v3/testsuite/std/time/clock/local/io.cc @@ -0,0 +1,42 @@ +// { dg-options "-std=gnu++20" } +// { dg-do run { target c++20 } } + +#include +#include +#include + +void +test_ostream() +{ + using std::format; + using namespace std::chrono; + + auto st = sys_days{2000y/January/1}; + auto tt = clock_cast(st); + + auto s = format("{0:%F %T %Z} == {1:%F %T %Z}", st, tt); + VERIFY( s == "2000-01-01 00:00:00 UTC == 2000-01-01 00:00:32 TAI" ); +} + +void +test_parse() +{ + using namespace std::chrono; + const sys_seconds expected = sys_days(2023y/August/9) + 21h + 44min; + local_seconds tp; + + minutes offset; + std::string abbrev; + std::istringstream is("2023-8-9 21:44 +1 BST#"); // Not adjusted for offset. + VERIFY( is >> parse("%F %R %Oz %Z", tp, abbrev, offset) ); + VERIFY( ! is.eof() ); + VERIFY( tp == local_seconds(expected.time_since_epoch()) ); + VERIFY( abbrev == "BST" ); + VERIFY( offset == 60min ); +} + +int main() +{ + test_ostream(); + test_parse(); +} diff --git a/libstdc++-v3/testsuite/std/time/clock/system/io.cc b/libstdc++-v3/testsuite/std/time/clock/system/io.cc index 7bb6851c7dec..8bfaad3278ea 100644 --- a/libstdc++-v3/testsuite/std/time/clock/system/io.cc +++ b/libstdc++-v3/testsuite/std/time/clock/system/io.cc @@ -73,8 +73,81 @@ test_format() VERIFY( smod == s ); } +void +test_parse() +{ + using namespace std::chrono; + sys_seconds tp, expected = sys_days(2023y/July/24) + 13h + 05min; + + std::istringstream is("24-hour time: 2023-07-24 13:05"); + VERIFY( is >> parse("24-hour time: %Y-%m-%d %H:%M", tp) ); + VERIFY( ! is.eof() ); + VERIFY( tp == expected ); + + tp = {}; + is.clear(); + is.str("12-hour time: 2023-07-24 1.05 PM "); + VERIFY( is >> parse("12-hour time: %F %I.%M %p", tp) ); + VERIFY( ! is.eof() ); + VERIFY( tp == expected ); + + tp = {}; + is.clear(); + is.str("2023-07-24 14:05 +01"); + VERIFY( is >> parse("%F %H:%M %z", tp) ); // %z is used even without offset + VERIFY( is.eof() ); + VERIFY( tp == expected ); + + tp = {}; + minutes offset{}; + is.clear(); + is.str("2023-07-24 15:35 0230"); + VERIFY( is >> parse("%F %H:%M %z", tp, offset) ); + VERIFY( ! is.eof() ); + VERIFY( tp == expected ); + + tp = {}; + std::string abbrev; + is.clear(); + is.str("2023-07-24 08:05 -5:00 EST EST"); + VERIFY( is >> parse("%F %H:%M %Ez %Z %Z", tp, abbrev) ); + VERIFY( is.eof() ); + VERIFY( tp == expected ); + VERIFY( abbrev == "EST" ); + + tp = {}; + abbrev = {}; + offset = {}; + is.clear(); + is.str("2023-07-24 07:05 -06:00 ABC/+123/-456/_="); + VERIFY( is >> parse("%F %H:%M %Ez %Z", tp, abbrev, offset) ); + VERIFY( ! is.eof() ); + VERIFY( tp == expected ); + VERIFY( offset == -360min ); + VERIFY( abbrev == "ABC/+123/-456/_" ); + + tp = sys_seconds(99s); + offset = 99min; + is.clear(); + is.str("-02:00 "); + VERIFY( ! (is >> parse("%Ez ", tp, offset)) ); + VERIFY( is.fail() ); + VERIFY( tp == sys_seconds(99s) ); // tp is only updated on successful parse. + VERIFY( offset == 99min ); // offset is only updated on successful parse. + + tp = sys_seconds(99s); + abbrev = "99"; + is.clear(); + is.str("GMT "); + VERIFY( ! (is >> parse("%Z ", tp, abbrev)) ); + VERIFY( is.fail() ); + VERIFY( tp == sys_seconds(99s) ); // tp is only updated on successful parse. + VERIFY( abbrev == "99" ); // abbrev is only updated on successful parse. +} + int main() { test_ostream(); test_format(); + test_parse(); } diff --git a/libstdc++-v3/testsuite/std/time/clock/tai/io.cc b/libstdc++-v3/testsuite/std/time/clock/tai/io.cc index d0255f5431af..530af75442b3 100644 --- a/libstdc++-v3/testsuite/std/time/clock/tai/io.cc +++ b/libstdc++-v3/testsuite/std/time/clock/tai/io.cc @@ -6,7 +6,7 @@ #include void -test01() +test_ostream() { using std::format; using namespace std::chrono; @@ -18,7 +18,25 @@ test01() VERIFY( s == "2000-01-01 00:00:00 UTC == 2000-01-01 00:00:32 TAI" ); } +void +test_parse() +{ + using namespace std::chrono; + const sys_seconds expected = sys_days(2023y/August/9) + 20h + 44min + 3s; + tai_seconds tp; + + minutes offset; + std::string abbrev; + std::istringstream is("8/9/23 214403 +1 BST#"); + VERIFY( is >> parse("%D %2H%2M%2S %Oz %Z", tp, abbrev, offset) ); + VERIFY( ! is.eof() ); + VERIFY( tp == clock_cast(expected) ); + VERIFY( abbrev == "BST" ); + VERIFY( offset == 60min ); +} + int main() { - test01(); + test_ostream(); + test_parse(); } diff --git a/libstdc++-v3/testsuite/std/time/clock/utc/io.cc b/libstdc++-v3/testsuite/std/time/clock/utc/io.cc index 977643f11478..c49f6f7e22cf 100644 --- a/libstdc++-v3/testsuite/std/time/clock/utc/io.cc +++ b/libstdc++-v3/testsuite/std/time/clock/utc/io.cc @@ -114,8 +114,39 @@ test_format() VERIFY( s == "00:00:00" ); } +void +test_parse() +{ + using namespace std::chrono; + const sys_seconds expected = sys_days(2023y/August/9) + 20h + 44min + 3s; + utc_seconds tp; + + minutes offset; + std::string abbrev; + std::istringstream is("23 2210 21:44:3 +1 BST#"); + VERIFY( is >> parse("%y %j0 %4H:%5M:%6S %Oz %Z", tp, abbrev, offset) ); + VERIFY( ! is.eof() ); + VERIFY( tp == clock_cast(expected) ); + VERIFY( abbrev == "BST" ); + VERIFY( offset == 60min ); + + tp = {}; + is.clear(); + is.str("20230809214403 0100 BST:"); + VERIFY( is >> parse("%Y%m%d%H%M%S %z %Z:", tp) ); + VERIFY( ! is.eof() ); + VERIFY( tp == clock_cast(expected) ); + + is.clear(); + is.str("2023-W32-3 20:44:03"); + VERIFY( is >> parse("%G-W%V-%u %T", tp) ); + VERIFY( ! is.eof() ); + VERIFY( tp == clock_cast(expected) ); +} + int main() { test_ostream(); test_format(); + test_parse(); } diff --git a/libstdc++-v3/testsuite/std/time/day/io.cc b/libstdc++-v3/testsuite/std/time/day/io.cc index 6158230f2884..d8691b72066f 100644 --- a/libstdc++-v3/testsuite/std/time/day/io.cc +++ b/libstdc++-v3/testsuite/std/time/day/io.cc @@ -67,9 +67,67 @@ test_format() } } +void +test_parse() +{ + using namespace std::chrono; + day d(0); + minutes offset; + std::string abbrev; + std::istringstream is("2023-08-10 12:46 +01 BST<"); + VERIFY( is >> parse("%F %R %z %Z", d, abbrev, offset) ); + VERIFY( ! is.eof() ); + VERIFY( d == 10d ); + VERIFY( abbrev == "BST" ); + VERIFY( offset == 60min ); + + abbrev = "nope"; + offset = 999min; + is.clear(); + is.str("30"); + VERIFY( is >> parse("%d", d, abbrev, offset) ); + VERIFY( ! is.eof() ); + VERIFY( d == 30d ); + VERIFY( abbrev == "nope" ); + VERIFY( offset == 999min ); + + d = day(255); + is.clear(); + is.str("2023-02-30"); + is >> parse("%F", d); // Feb 30 is not a valid day + VERIFY( is.fail() ); + VERIFY( d == day(255) ); + + is.clear(); + is.str("February 30"); + is >> parse("%B %d", d); // Feb 30 is not a valid day + VERIFY( is.fail() ); + VERIFY( d == day(255) ); + + is.clear(); + is.str("February 29"); + is >> parse("%B %d", d); // But Feb 29 could be valid. + VERIFY( is.good() ); + VERIFY( d == 29d ); + + d = day(255); + is.clear(); + is.str("2023 Feb 29"); + is >> parse("%Y %B %d", d); // But 2023 is not a leap year. + VERIFY( is.fail() ); + VERIFY( d == day(255) ); + + d = day(255); + is.clear(); + is.str("20 Feb 29"); + is >> parse("%y %B %d", d); // But 2020 is a leap year. + VERIFY( is.good() ); + VERIFY( d == 29d ); +} + int main() { test_ostream(); test_format(); - // TODO: test_parse(); + test_parse(); } diff --git a/libstdc++-v3/testsuite/std/time/month/io.cc b/libstdc++-v3/testsuite/std/time/month/io.cc index 7ceeafd725a8..9cf5b053f2bf 100644 --- a/libstdc++-v3/testsuite/std/time/month/io.cc +++ b/libstdc++-v3/testsuite/std/time/month/io.cc @@ -90,9 +90,129 @@ test_format() } } +void +test_parse() +{ + using namespace std::chrono; + std::istringstream is; + month m{}; + + is.str("JUL"); + VERIFY( is >> parse("%B", m) ); + VERIFY( is.eof() ); + VERIFY( m == July ); + + is.clear(); + is.str("junE bug"); + VERIFY( is >> parse(" %b bug ", m) ); + VERIFY( is.eof() ); + VERIFY( m == June ); + + is.clear(); + is.str("012"); + VERIFY( is >> parse("%m", m) ); + VERIFY( ! is.eof() ); + VERIFY( is.peek() == '2' ); + VERIFY( m == January ); + + is.clear(); + is.str("012"); + VERIFY( is >> parse("%4m", m) ); + VERIFY( is.eof() ); + VERIFY( m == December ); + + m = month(15); + is.clear(); + is.str("Janvember"); + VERIFY( is >> parse("%B", m) ); // Stops parsing after "Jan" + VERIFY( ! is.eof() ); + VERIFY( is.peek() == 'v' ); + VERIFY( m == January ); + + m = month(15); + is.clear(); + is.str("Junuary"); + VERIFY( is >> parse("%B", m) ); // Stops parsing after "Jun" + VERIFY( ! is.eof() ); + VERIFY( is.peek() == 'u' ); + VERIFY( m == June ); + + m = month(15); + is.clear(); + is.str("Jebruary"); + VERIFY( ! (is >> parse("%B", m)) ); + VERIFY( is.fail() ); + VERIFY( ! is.eof() ); + is.clear(); + VERIFY( is.peek() == 'e' ); + VERIFY( m == month(15) ); + + m = month(13); + is.clear(); + is.str("2023-6-31"); + VERIFY( ! (is >> parse("%F", m)) ); // June only has 30 days. + VERIFY( ! is.eof() ); + VERIFY( m == month(13) ); + + m = month(14); + is.clear(); + is.str("2023-2-29"); + VERIFY( ! (is >> parse("%Y-%m-%e", m)) ); // Feb only has 28 days in 2023. + VERIFY( ! is.eof() ); + VERIFY( m == month(14) ); + + is.clear(); + is.str("2-29"); + VERIFY( is >> parse("%m-%d", m) ); // But Feb has 29 days in some years. + VERIFY( ! is.eof() ); + VERIFY( m == February ); + + m = month(14); + is.clear(); + is.str("6-31"); + VERIFY( ! (is >> parse("%m-%d", m)) ); // June only has 30 days in all years. + VERIFY( ! is.eof() ); + VERIFY( m == month(14) ); + + m = month(15); + is.clear(); + is.str("2023-13-1"); + VERIFY( ! (is >> parse("%F", m)) ); + VERIFY( is.eof() ); + VERIFY( m == month(15) ); + + m = month(16); + is.clear(); + is.str("13/1/23"); + VERIFY( ! (is >> parse("%D", m)) ); + VERIFY( m == month(16) ); + + m = month(17); + is.clear(); + is.str("13"); + VERIFY( ! (is >> parse("%m", m)) ); + VERIFY( ! is.eof() ); + VERIFY( m == month(17) ); + + m = month(18); + is.clear(); + is.str("1234"); + VERIFY( ! (is >> parse("%3m", m)) ); + VERIFY( ! is.eof() ); + is.clear(); + VERIFY( is.peek() == '4' ); + VERIFY( m == month(18) ); + + is.clear(); + is.str("2023-W32-5"); + VERIFY( is >> parse("%G-W%V-%u", m) ); + VERIFY( ! is.eof() ); + VERIFY( m == August ); +} + int main() { test_ostream(); test_format(); - // TODO: test_parse(); + test_parse(); } diff --git a/libstdc++-v3/testsuite/std/time/month_day/io.cc b/libstdc++-v3/testsuite/std/time/month_day/io.cc index 454231dd7243..a3f4599fb4e4 100644 --- a/libstdc++-v3/testsuite/std/time/month_day/io.cc +++ b/libstdc++-v3/testsuite/std/time/month_day/io.cc @@ -22,9 +22,86 @@ test_ostream() VERIFY( ss.str() == "juil./27" ); } +void +test_parse() +{ + using namespace std::chrono; + std::istringstream is; + month_day md{}; + + is.str("jul 0123"); + VERIFY( is >> parse("%B %3e", md) ); + VERIFY( ! is.eof() ); + VERIFY( is.peek() == '3' ); + VERIFY( md == July/12 ); + + is.str("August 11"); + VERIFY( is >> parse("%b %d", md) ); + VERIFY( ! is.eof() ); + VERIFY( md == August/11 ); + + is.clear(); + is.str("012"); + VERIFY( is >> parse("%m%2d", md) ); + VERIFY( is.eof() ); + VERIFY( md == January/2 ); + + is.clear(); + is.str("012/311"); + VERIFY( is >> parse("%4m/%d", md) ); + VERIFY( ! is.eof() ); + VERIFY( md == December/31 ); + + is.clear(); + is.str("2023-7-31"); + VERIFY( is >> parse("%F", md) ); + VERIFY( ! is.eof() ); + VERIFY( md == July/31 ); + + md = month(13)/day(32); + is.clear(); + is.str("2023-13-1"); + VERIFY( ! (is >> parse("%F", md)) ); + VERIFY( is.eof() ); + VERIFY( md == month(13)/day(32) ); + + md = month(13)/day(33); + is.clear(); + is.str("2023-6-31"); + VERIFY( ! (is >> parse("%F", md)) ); // June only has 30 days. + VERIFY( ! is.eof() ); + VERIFY( md == month(13)/day(33) ); + + md = month(13)/day(34); + is.clear(); + is.str("6-31"); + VERIFY( ! (is >> parse("%m-%d", md)) ); // June only has 30 days in any year. + VERIFY( ! is.eof() ); + VERIFY( md == month(13)/day(34) ); + + md = month(13)/day(35); + is.clear(); + is.str("2023-2-29"); + VERIFY( ! (is >> parse("%Y-%m-%e", md)) ); // Feb only has 28 days in 2023. + VERIFY( ! is.eof() ); + VERIFY( md == month(13)/day(35) ); + + is.clear(); + is.str("2-29"); + VERIFY( is >> parse("%m-%d", md) ); // But Feb has 29 days in some years. + VERIFY( ! is.eof() ); + VERIFY( md == February/29 ); + + is.clear(); + is.str("2023-W32-5"); + VERIFY( is >> parse("%G-W%V-%u", md) ); + VERIFY( ! is.eof() ); + VERIFY( md == August/11 ); +} + int main() { test_ostream(); // TODO: test_format(); - // TODO: test_parse(); + test_parse(); } diff --git a/libstdc++-v3/testsuite/std/time/parse.cc b/libstdc++-v3/testsuite/std/time/parse.cc new file mode 100644 index 000000000000..9b36c5d7db4c --- /dev/null +++ b/libstdc++-v3/testsuite/std/time/parse.cc @@ -0,0 +1,309 @@ +// { dg-do run { target c++20 } } +// { dg-options "-std=gnu++20" } + +#include +#include +#include +#include + +template + concept stream_extractable + = requires (std::basic_istream& is) { is >> std::declval(); }; + +void +test_recommended_practice() +{ + std::chrono::seconds s; + using parse_manip = decltype(std::chrono::parse("", s)); + static_assert( stream_extractable ); + static_assert( not stream_extractable ); + using wparse_manip = decltype(std::chrono::parse(L"", s)); + static_assert( stream_extractable ); + static_assert( not stream_extractable ); + + // These properties are recommended by the standard, to avoid using a + // parse manipulator that has a dangling reference to a format string. + static_assert( not std::is_move_constructible_v ); + static_assert( not std::is_move_assignable_v ); + static_assert( not stream_extractable ); + static_assert( not stream_extractable ); + static_assert( not stream_extractable ); + static_assert( not stream_extractable ); +} + +template + concept parsable = requires(Args... args) { std::chrono::parse(args...); }; + +const std::string f = "format string"; + +namespace N +{ + struct A { }; + + void + from_stream(std::istream&, const char* fmt, A&) + { + VERIFY( fmt == f.c_str() ); + } + + template + void + from_stream(std::istream&, const char*, A&, void*, void*) = delete; + + struct B { }; + + void + from_stream(std::istream&, const char* fmt, B&, std::string* abbrev) + { + VERIFY( fmt == f.c_str() ); + VERIFY( abbrev != nullptr ); + } + + void + from_stream(std::istream&, const char*, B&, std::string*, void*) = delete; + + struct C { }; + + void + from_stream(std::istream&, const char* fmt, C&, std::string* abbrev, + std::chrono::minutes* offset) + { + VERIFY( fmt == f.c_str() ); + VERIFY( abbrev == nullptr ); + VERIFY( offset != nullptr ); + } + + struct D { }; + + void + from_stream(std::istream&, const char* fmt, D&, std::string* abbrev, + std::chrono::minutes* offset) + { + VERIFY( fmt == f.c_str() ); + VERIFY( abbrev != nullptr ); + VERIFY( offset != nullptr ); + } + + struct E { }; + + void + from_stream(std::wistream&, const wchar_t*, E&, std::wstring* = nullptr, + std::chrono::minutes* = nullptr) + { } +} + +void +test_adl() +{ + using std::string; + using std::wstring; + using std::chrono::minutes; + + string abbrev; + minutes offset; + + // Check that valid calls are well-formed. + N::A a; + (void) std::chrono::parse(f, a); + N::B b; + (void) std::chrono::parse(f, b, abbrev); + N::C c; + (void) std::chrono::parse(f, c, offset); + // This satisfies the concept, but would fail the VERIFY assertion: + static_assert( parsable ); + N::D d; + (void) std::chrono::parse(f, d, abbrev, offset); + // This satisfies the concept, but would fail the VERIFY assertion: + static_assert( parsable ); + + // Wide strings. + static_assert( parsable ); + static_assert( parsable ); + static_assert( parsable ); + static_assert( parsable ); + + // Check that invalid calls are properly constrained. + + // from_stream is only overloaded for N::A without abbrev or offset. + static_assert( not parsable ); + static_assert( not parsable ); + static_assert( not parsable ); + // from_stream is only overloaded for N::B with abbrev. + static_assert( not parsable ); + static_assert( not parsable ); + static_assert( not parsable ); + // from_stream is only overloaded for N::C with abbrev and minutes. + static_assert( not parsable ); + static_assert( not parsable ); + // from_stream is only overloaded for N::D with abbrev and minutes. + static_assert( not parsable ); + static_assert( not parsable ); + + // Mismatched strings + static_assert( not parsable ); + static_assert( not parsable ); + + using Alloc = __gnu_test::SimpleAllocator; + using String = std::basic_string, Alloc>; + // Custom allocator + static_assert( parsable ); + static_assert( parsable ); + static_assert( parsable ); + static_assert( parsable ); + static_assert( parsable ); + static_assert( parsable ); + // Mismatched allocators + static_assert( not parsable ); + static_assert( not parsable ); + static_assert( not parsable ); + static_assert( not parsable ); +} + +void +test_whitespace() +{ + using namespace std::chrono_literals; + std::chrono::minutes min; + std::istringstream is; + is.str(" a b 1 "); + is >> parse(" a b %M", min); + VERIFY( is.good() ); + VERIFY( min == 1min ); + is.str(" a b 1 "); + is >> parse(" a b %M ", min); + VERIFY( is.eof() && !is.fail() ); + VERIFY( min == 1min ); + is.clear(); + is.str(" 1"); + is >> parse(" %n%M%n", min); + VERIFY( is.fail() ); + is.clear(); + is.str(" a b 1 "); + is >> parse("%n a%n%nb %t%M%n", min); + VERIFY( is.good() ); + VERIFY( min == 1min ); + is.str("a b 1 "); + is >> parse("%ta b %M%n%t", min); + VERIFY( is.good() ); + VERIFY( min == 1min ); + is.str("1 "); + is >> parse("%M%n%t%t", min); + VERIFY( is.eof() && !is.fail() ); + VERIFY( min == 1min ); + is.clear(); + is.str("1 "); + is >> parse("%M%n%t%t", min); + VERIFY( is.eof() && !is.fail() ); + VERIFY( min == 1min ); + is.clear(); + is.str("1 "); + is >> parse("%M%n%t%n", min); + VERIFY( is.eof() && is.fail() ); + VERIFY( min == 1min ); +} + +void +test_errors() +{ + using namespace std::chrono_literals; + std::chrono::minutes min(999); + std::chrono::year y(-1); + std::istringstream is; + + is.str("x"); + is >> parse("x", min); // Matches expected pattern, but no minutes present. + VERIFY( !is.eof() && is.fail() ); + VERIFY( min == 999min ); + + is.clear(); + is.str("x"); + is >> parse("%M", min); // Doesn't match expected pattern. + VERIFY( !is.eof() && is.fail() ); + VERIFY( min == 999min ); + + is.clear(); + is.str("001:002"); + is >> parse("%H:%M", min); // Extracts "00" then fails to find ':' next. + VERIFY( !is.eof() && is.fail() ); + VERIFY( min == 999min ); + + is.clear(); + is.str("12:61"); + is >> parse("%H:%M", min); // 61min is out of range. + VERIFY( !is.eof() && is.fail() ); + VERIFY( min == 999min ); + + is.clear(); + is.str("12:15 100"); + is >> parse("%H:%M %3y", min); // 100y is out of range for %y but not needed + VERIFY( is.good() ); + VERIFY( min == (12h + 15min) ); + + min = 999min; + is.clear(); + is.str("12:15 100"); + is >> parse("%H:%M %3y", y); // 100y is out of range for %y and needed + VERIFY( is.fail() ); + VERIFY( y == -1y ); + + is.clear(); + is.str("23:61 10"); + is >> parse("%H:%M %3y", y); // 61min is out of range but not needed + VERIFY( is.eof() && ! is.fail() ); + VERIFY( y == 2010y ); +} + +void +test_modifiers() +{ + using namespace std::chrono_literals; + std::chrono::minutes min; + std::istringstream is; + + is.str("0001:000002"); + is >> parse("%4H:%5M", min); + VERIFY( is.good() ); + VERIFY( min == 60min ); + + is.str("0001:000002"); + is >> parse("%6H:%6M", min); + VERIFY( is.good() ); + VERIFY( min == 62min ); + + is.str("002"); + is >> parse("%4M", min); + VERIFY( is.eof() && !is.fail() ); + VERIFY( min == 2min ); + + is.clear(); + is.str("0061"); + is >> parse("%3M", min); + VERIFY( is.good() ); + VERIFY( min == 6min ); + + is.clear(); + is.str("0061"); + is >> parse("%4M", min); + VERIFY( !is.eof() && is.fail() ); + + is.clear(); + is.str("0061"); + is >> parse("%5M", min); + VERIFY( is.eof() && is.fail() ); + + std::chrono::seconds s; + is.clear(); + is.str("000000000012345"); + is >> parse("%12S", s); // Read more than 10 digits to check overflow logic. + VERIFY( is.good() ); + VERIFY( s == 12s ); +} + +int main() +{ + test_recommended_practice(); + test_adl(); + test_whitespace(); + test_errors(); + test_modifiers(); +} diff --git a/libstdc++-v3/testsuite/std/time/syn_c++20.cc b/libstdc++-v3/testsuite/std/time/syn_c++20.cc index 570471aacc71..307d84e54357 100644 --- a/libstdc++-v3/testsuite/std/time/syn_c++20.cc +++ b/libstdc++-v3/testsuite/std/time/syn_c++20.cc @@ -22,9 +22,8 @@ #ifndef __cpp_lib_chrono # error "Feature test macro for chrono is missing in " -// FIXME -// #elif __cpp_lib_chrono < 201907L -// # error "Feature test macro for chrono has wrong value in " +#elif __cpp_lib_chrono < 201907L +# error "Feature test macro for chrono has wrong value in " #endif namespace __gnu_test @@ -126,8 +125,8 @@ namespace __gnu_test using std::chrono::local_time_format; - // FIXME - // using std::chrono::parse; + using std::chrono::from_stream; + using std::chrono::parse; using std::chrono::last; using std::chrono::Sunday; diff --git a/libstdc++-v3/testsuite/std/time/weekday/io.cc b/libstdc++-v3/testsuite/std/time/weekday/io.cc index 6cdb98467b13..ba9dce00f6ce 100644 --- a/libstdc++-v3/testsuite/std/time/weekday/io.cc +++ b/libstdc++-v3/testsuite/std/time/weekday/io.cc @@ -93,9 +93,85 @@ test_format() } } +void +test_parse() +{ + using namespace std::chrono; + std::istringstream is; + weekday wd{}; + + is.str("fRi funday"); + VERIFY( is >> std::chrono::parse(" %A funday", wd) ); + VERIFY( wd == Friday ); + + is.str("MONDAY xxx"); + VERIFY( is >> std::chrono::parse(" %a xxx ", wd) ); + VERIFY( wd == Monday ); + + is.clear(); + is.str("1"); + VERIFY( is >> std::chrono::parse("%u", wd) ); + VERIFY( wd == Monday ); + is.clear(); + is.str("7"); + VERIFY( is >> std::chrono::parse("%u", wd) ); + VERIFY( wd == Sunday ); + wd = weekday(99); + is.clear(); + is.str("0"); + VERIFY( ! (is >> std::chrono::parse("%u", wd)) ); + VERIFY( wd == weekday(99) ); + is.clear(); + is.str("8"); + VERIFY( ! (is >> std::chrono::parse("%u", wd)) ); + VERIFY( wd == weekday(99) ); + + is.clear(); + is.str("003"); + VERIFY( is >> std::chrono::parse("%3u", wd) ); + VERIFY( wd == Wednesday ); + wd = weekday(99); + is.clear(); + is.str("004"); + VERIFY( ! (is >> std::chrono::parse("%2u", wd)) ); + VERIFY( wd == weekday(99) ); + + is.clear(); + is.str("1"); + VERIFY( is >> std::chrono::parse("%w", wd) ); + VERIFY( wd == Monday ); + is.clear(); + is.str("0"); + VERIFY( is >> std::chrono::parse("%w", wd) ); + VERIFY( wd == Sunday ); + wd = weekday(99); + is.clear(); + is.str("7"); + VERIFY( ! (is >> std::chrono::parse("%w", wd)) ); + VERIFY( wd == weekday(99) ); + is.clear(); + is.str("8"); + VERIFY( ! (is >> std::chrono::parse("%w", wd)) ); + VERIFY( wd == weekday(99) ); + + is.clear(); + is.str("003"); + VERIFY( is >> std::chrono::parse("%3w", wd) ); + VERIFY( wd == Wednesday ); + is.clear(); + is.str("004"); + VERIFY( is >> std::chrono::parse("%2w", wd) ); + VERIFY( wd == Sunday ); + + is.clear(); + is.str("2023-8-11"); + VERIFY( is >> std::chrono::parse("%F", wd) ); + VERIFY( wd == Friday ); +} + int main() { test_ostream(); test_format(); - // TODO: test_parse(); + test_parse(); } diff --git a/libstdc++-v3/testsuite/std/time/year/io.cc b/libstdc++-v3/testsuite/std/time/year/io.cc index 07316e98aa52..0c4746479213 100644 --- a/libstdc++-v3/testsuite/std/time/year/io.cc +++ b/libstdc++-v3/testsuite/std/time/year/io.cc @@ -81,9 +81,81 @@ test_format() } } +void +test_parse() +{ + using namespace std::chrono; + year y; + + std::istringstream is("2023"); + VERIFY( is >> parse("%Y", y) ); + VERIFY( ! is.eof() ); + VERIFY( y == year(2023) ); + + is.clear(); + is.str("2023"); + VERIFY( is >> parse("%5Y", y) ); + VERIFY( is.eof() ); + VERIFY( y == year(2023) ); + + is.clear(); + is.str("2023"); + VERIFY( is >> parse("%2Y", y) ); + VERIFY( ! is.eof() ); + VERIFY( y == year(20) ); + + is.clear(); + is.str("2023"); + VERIFY( is >> parse("%y", y) ); + VERIFY( ! is.eof() ); + VERIFY( y == year(2020) ); + + minutes offset; + std::string abbrev; + + is.clear(); + is.str("23 20 25:61 +1:30 WAT"); // Invalid %H:%M doesn't matter for year. + VERIFY( is >> parse("%y %C %H:%M %Oz %Z", y, abbrev, offset) ); + VERIFY( is.eof() ); + VERIFY( y == year(2023) ); + VERIFY( abbrev == "WAT" ); + VERIFY( offset == 90min ); + + is.clear(); + is.str("2022 367"); + VERIFY( is >> parse("%Y %j", y) ); // Invalid day-of-year doesn't matter. + VERIFY( ! is.eof() ); + VERIFY( y == 2022y ); + + y = 999y; + is.clear(); + is.str("2023"); + VERIFY( ! (is >> parse("%G", y)) ); // ISO year not aligned with Gregorian. + VERIFY( y == 999y ); + + is.clear(); + is.str("2023-W01-1"); // 2023-1-2 + is >> parse("%G-W%V-%u", y); // Can get Gregorian year from full ISO date. + VERIFY( ! is.eof() ); + VERIFY( y == 2023y ); + + is.clear(); + is.str("2022-W052-7"); // 2023-1-1 + is >> parse("%G-W%3V-%2u", y); + VERIFY( is.eof() ); + VERIFY( y == 2023y ); + + y = year(1); + is.clear(); + is.str("2023 01"); + VERIFY( !( is >> parse("%Y %z xx", y)) ); // Gets EOF and can't parse " xx". + VERIFY( is.eof() ); + VERIFY( y == year(1) ); +} + int main() { test_ostream(); test_format(); - // TODO: test_parse(); + test_parse(); } diff --git a/libstdc++-v3/testsuite/std/time/year_month/io.cc b/libstdc++-v3/testsuite/std/time/year_month/io.cc index 8c0eb9b65794..8dac24861885 100644 --- a/libstdc++-v3/testsuite/std/time/year_month/io.cc +++ b/libstdc++-v3/testsuite/std/time/year_month/io.cc @@ -22,9 +22,57 @@ test_ostream() VERIFY( ss.str() == "2023/juil." ); } +void +test_parse() +{ + using namespace std::chrono; + year_month ym; + + std::istringstream is("20238"); + VERIFY( is >> parse("%Y%m", ym) ); + VERIFY( is.eof() ); + VERIFY( ym == 2023y/August ); + + ym = 1y/January; + is.clear(); + is.str("20238"); + VERIFY( ! (is >> parse("%5Y%m", ym)) ); + VERIFY( is.eof() ); + VERIFY( ym == 1y/January ); + + is.clear(); + is.str("2023"); + VERIFY( is >> parse("%2Y%1m", ym) ); + VERIFY( ! is.eof() ); + VERIFY( ym == 20y/February ); + + is.clear(); + is.str("2012"); + VERIFY( is >> parse("%y%m", ym) ); + VERIFY( ! is.eof() ); + VERIFY( ym == 2020y/December ); + + minutes offset; + std::string abbrev; + + is.clear(); + is.str("4/1/20 25:61 +1:30 WAT"); // Invalid %H:%M doesn't matter for year_mon + VERIFY( is >> parse("%D %H:%M %Oz %Z", ym, abbrev, offset) ); + VERIFY( is.eof() ); + VERIFY( ym == 2020y/April ); + VERIFY( abbrev == "WAT" ); + VERIFY( offset == 90min ); + + is.clear(); + is.str("02022-W052-7"); + is >> parse("%6G-W%4V-%2u", ym); + VERIFY( is.eof() ); + VERIFY( ym == 2023y/January ); +} + int main() { test_ostream(); // TODO: test_format(); - // TODO: test_parse(); + test_parse(); } diff --git a/libstdc++-v3/testsuite/std/time/year_month_day/io.cc b/libstdc++-v3/testsuite/std/time/year_month_day/io.cc index 688885b37a1d..6c30a8721ea8 100644 --- a/libstdc++-v3/testsuite/std/time/year_month_day/io.cc +++ b/libstdc++-v3/testsuite/std/time/year_month_day/io.cc @@ -113,9 +113,72 @@ test_format() } } +void +test_parse() +{ + using namespace std::chrono; + const year_month_day expected = 2023y/August/10; + year_month_day ymd; + + minutes offset; + std::string abbrev; + std::istringstream is("23 2220 21:44:3 +1 'BST'"); + VERIFY( is >> parse("%y %j0 %4H:%5M:%6S %Oz '%Z'", ymd, abbrev, offset) ); + VERIFY( ! is.eof() ); + VERIFY( ymd == expected ); + VERIFY( abbrev == "BST" ); + VERIFY( offset == 60min ); + + is.clear(); + is.str("2023 365"); + VERIFY( is >> parse("%Y %j", ymd) ); + VERIFY( ymd == 2023y/December/31 ); + + ymd = 1970y/January/1; + is.clear(); + is.str("2023 366"); + VERIFY( ! (is >> parse("%Y %j", ymd)) ); // Not a leap year, no 366th day. + VERIFY( ymd == 1970y/January/1 ); + + is.clear(); + is.str("2020 366"); + VERIFY( is >> parse("%Y %j", ymd) ); + VERIFY( ! is.eof() ); + VERIFY( ymd == 2020y/December/31 ); + + ymd = 1970y/January/1; + is.clear(); + is.str("2020 0"); + VERIFY( ! (is >> parse("%Y %j", ymd)) ); // zero is invalid for day-of-year + VERIFY( is.eof() ); + VERIFY( ymd == 1970y/January/1 ); + + is.clear(); + is.str("2023-01-01 00:30 0100"); + VERIFY( is >> parse("%F %R %z", ymd) ); + VERIFY( ! is.eof() ); + VERIFY( ymd == 2023y/January/1 ); // Date not adjusted by TZ offset. + + ymd = {}; + is.clear(); + is.str("2022-W52-6"); + VERIFY( is >> parse("%G-W%V-%u", ymd) ); + VERIFY( ymd == 2022y/December/31 ); + + is.clear(); + is.str("2022-W52-8"); + VERIFY( ! (is >> parse("%G-W%V-%u", ymd)) ); // 8 is not a valid weekday + is.clear(); + is.str("2022-W52-0"); + VERIFY( ! (is >> parse("%G-W%V-%u", ymd)) ); // 0 is not a valid weekday + is.clear(); + is.str("2022-W53-1"); + VERIFY( ! (is >> parse("%G-W%V-%u", ymd)) ); // W53 is not valid for 2022 +} + int main() { test_ostream(); test_format(); - // TODO: test_parse(); + test_parse(); }