]> gcc.gnu.org Git - gcc.git/commitdiff
libstdc++: Implement C++20 std::chrono::parse [PR104167]
authorJonathan Wakely <jwakely@redhat.com>
Fri, 21 Jul 2023 16:19:08 +0000 (17:19 +0100)
committerJonathan Wakely <jwakely@redhat.com>
Fri, 11 Aug 2023 18:58:06 +0000 (19:58 +0100)
This adds the missing C++20 features to <chrono>.

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.

20 files changed:
libstdc++-v3/include/bits/chrono_io.h
libstdc++-v3/include/std/chrono
libstdc++-v3/include/std/version
libstdc++-v3/testsuite/20_util/duration/arithmetic/constexpr_c++17.cc
libstdc++-v3/testsuite/20_util/duration/io.cc
libstdc++-v3/testsuite/std/time/clock/file/io.cc
libstdc++-v3/testsuite/std/time/clock/gps/io.cc
libstdc++-v3/testsuite/std/time/clock/local/io.cc [new file with mode: 0644]
libstdc++-v3/testsuite/std/time/clock/system/io.cc
libstdc++-v3/testsuite/std/time/clock/tai/io.cc
libstdc++-v3/testsuite/std/time/clock/utc/io.cc
libstdc++-v3/testsuite/std/time/day/io.cc
libstdc++-v3/testsuite/std/time/month/io.cc
libstdc++-v3/testsuite/std/time/month_day/io.cc
libstdc++-v3/testsuite/std/time/parse.cc [new file with mode: 0644]
libstdc++-v3/testsuite/std/time/syn_c++20.cc
libstdc++-v3/testsuite/std/time/weekday/io.cc
libstdc++-v3/testsuite/std/time/year/io.cc
libstdc++-v3/testsuite/std/time/year_month/io.cc
libstdc++-v3/testsuite/std/time/year_month_day/io.cc

index c95301361d877f1d168b7fe7316222c271b4e8d7..84791d41fb1e5314cce4022e07a62fd4710d6829 100644 (file)
@@ -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<typename _CharT>
     struct __formatter_chrono
@@ -2136,18 +2140,150 @@ namespace chrono
 /// @addtogroup chrono
 /// @{
 
-  // TODO: from_stream for duration
-#if 0
+/// @cond undocumented
+namespace __detail
+{
+  template<typename _Duration = seconds>
+    struct _Parser
+    {
+      static_assert(is_same_v<common_type_t<_Duration, seconds>, _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<typename _CharT, typename _Traits, typename _Alloc>
+       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<typename _CharT, typename _Traits>
+       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<typename _CharT, typename _Traits>
+       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<typename _CharT, typename _Traits>
+       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<typename _CharT, typename _Traits>
+       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<typename _Duration>
+    using _Parser_t = _Parser<common_type_t<_Duration, seconds>>;
+
+} // namespace __detail
+/// ~endcond
+
   template<typename _CharT, typename _Traits, typename _Rep, typename _Period,
           typename _Alloc = allocator<_CharT>>
-    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<duration<_Rep, _Period>> __p(__need);
+      if (__p(__is, __fmt, __abbrev, __offset))
+       __d = chrono::duration_cast<duration<_Rep, _Period>>(__p._M_time);
+      return __is;
     }
-#endif
 
   template<typename _CharT, typename _Traits>
     inline basic_ostream<_CharT, _Traits>&
@@ -2163,7 +2299,19 @@ namespace chrono
       return __os;
     }
 
-  // TODO from_stream for day
+  template<typename _CharT, typename _Traits,
+          typename _Alloc = allocator<_CharT>>
+    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<typename _CharT, typename _Traits>
     inline basic_ostream<_CharT, _Traits>&
@@ -2182,7 +2330,19 @@ namespace chrono
       return __os;
     }
 
-  // TODO from_stream for month
+  template<typename _CharT, typename _Traits,
+          typename _Alloc = allocator<_CharT>>
+    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<typename _CharT, typename _Traits>
     inline basic_ostream<_CharT, _Traits>&
@@ -2203,7 +2363,19 @@ namespace chrono
       return __os;
     }
 
-  // TODO from_stream for year
+  template<typename _CharT, typename _Traits,
+          typename _Alloc = allocator<_CharT>>
+    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<typename _CharT, typename _Traits>
     inline basic_ostream<_CharT, _Traits>&
@@ -2222,7 +2394,19 @@ namespace chrono
       return __os;
     }
 
-  // TODO from_stream for weekday
+  template<typename _CharT, typename _Traits,
+          typename _Alloc = allocator<_CharT>>
+    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<typename _CharT, typename _Traits>
     inline basic_ostream<_CharT, _Traits>&
@@ -2279,7 +2463,21 @@ namespace chrono
       return __os;
     }
 
-  // TODO from_stream for month_day
+  template<typename _CharT, typename _Traits,
+          typename _Alloc = allocator<_CharT>>
+    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<typename _CharT, typename _Traits>
     inline basic_ostream<_CharT, _Traits>&
@@ -2351,7 +2549,21 @@ namespace chrono
       return __os;
     }
 
-  // TODO from_stream for year_month
+  template<typename _CharT, typename _Traits,
+          typename _Alloc = allocator<_CharT>>
+    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<typename _CharT, typename _Traits>
     inline basic_ostream<_CharT, _Traits>&
@@ -2367,7 +2579,22 @@ namespace chrono
       return __os;
     }
 
-  // TODO from_stream for year_month_day
+  template<typename _CharT, typename _Traits,
+          typename _Alloc = allocator<_CharT>>
+    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<typename _CharT, typename _Traits>
     inline basic_ostream<_CharT, _Traits>&
@@ -2498,7 +2725,28 @@ namespace chrono
       return __os;
     }
 
-  // TODO: from_stream for sys_time
+  template<typename _CharT, typename _Traits, typename _Duration,
+          typename _Alloc = allocator<_CharT>>
+    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<typename _CharT, typename _Traits, typename _Duration>
     inline basic_ostream<_CharT, _Traits>&
@@ -2509,7 +2757,19 @@ namespace chrono
       return __os;
     }
 
-  // TODO: from_stream for utc_time
+  template<typename _CharT, typename _Traits, typename _Duration,
+          typename _Alloc = allocator<_CharT>>
+    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<typename _CharT, typename _Traits, typename _Duration>
     inline basic_ostream<_CharT, _Traits>&
@@ -2520,7 +2780,19 @@ namespace chrono
       return __os;
     }
 
-  // TODO: from_stream for tai_time
+  template<typename _CharT, typename _Traits, typename _Duration,
+          typename _Alloc = allocator<_CharT>>
+    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<typename _CharT, typename _Traits, typename _Duration>
     inline basic_ostream<_CharT, _Traits>&
@@ -2531,8 +2803,19 @@ namespace chrono
       return __os;
     }
 
-  // TODO: from_stream for gps_time
-
+  template<typename _CharT, typename _Traits, typename _Duration,
+          typename _Alloc = allocator<_CharT>>
+    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<typename _CharT, typename _Traits, typename _Duration>
     inline basic_ostream<_CharT, _Traits>&
@@ -2543,7 +2826,19 @@ namespace chrono
       return __os;
     }
 
-  // TODO: from_stream for file_time
+  template<typename _CharT, typename _Traits, typename _Duration,
+          typename _Alloc = allocator<_CharT>>
+    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<typename _CharT, typename _Traits, typename _Duration>
     inline basic_ostream<_CharT, _Traits>&
@@ -2554,7 +2849,1365 @@ namespace chrono
       return __os;
     }
 
-  // TODO: from_stream for local_time
+  template<typename _CharT, typename _Traits, typename _Duration,
+          typename _Alloc = allocator<_CharT>>
+    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 _Parsable, typename _CharT,
+          typename _Traits = std::char_traits<_CharT>,
+          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 _Parsable, typename _CharT,
+          typename _Traits = char_traits<_CharT>,
+          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<typename _CharT, __detail::__parsable<_CharT> _Parsable>
+    [[nodiscard, __gnu__::__access__(__read_only__, 1)]]
+    inline auto
+    parse(const _CharT* __fmt, _Parsable& __tp)
+    { return __detail::_Parse<_Parsable, _CharT>(__fmt, __tp); }
+
+  template<typename _CharT, typename _Traits, typename _Alloc,
+          __detail::__parsable<_CharT, _Traits> _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<typename _CharT, typename _Traits, typename _Alloc,
+          typename _StrT = basic_string<_CharT, _Traits, _Alloc>,
+          __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<typename _CharT, typename _Traits, typename _Alloc,
+          typename _StrT = basic_string<_CharT, _Traits, _Alloc>,
+          __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 _CharT, typename _Traits = char_traits<_CharT>,
+          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<typename _CharT, typename _Traits, typename _Alloc,
+          typename _StrT = basic_string<_CharT, _Traits>,
+          __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<typename _CharT, typename _Traits, typename _Alloc,
+          typename _StrT = basic_string<_CharT, _Traits, _Alloc>,
+          __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<typename _CharT, typename _Traits, typename _Alloc,
+          typename _StrT = basic_string<_CharT, _Traits, _Alloc>,
+          __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<typename _Duration>
+  template<typename _CharT, typename _Traits, typename _Alloc>
+    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<std::time_get<_CharT>>(__loc);
+         auto& __tmpunct = std::use_facet<std::__timepunct<_CharT>>(__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<bool>(__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<typename _Duration::period,
+                                                  ratio<1>>)
+                   {
+                     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<numpunct<_CharT>>(__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<num_get<_CharT>>(__loc);
+                         long double __val;
+                         ios_base::iostate __err2{};
+                         __ng.get(__buf, {}, __buf, __err2, __val);
+                         if (__is_failed(__err2)) [[unlikely]]
+                           __err |= __err2;
+                         else
+                           {
+                             duration<long double> __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
index e63d6c71b4a97cb9e48ef764ff742e972724abe1..6fdc0c86a27648636c89e071c3a2d6c00b87956d 100644 (file)
@@ -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)
index 02ead8f1443e8ff7a7020b555a5c81476f670576..5264c8bff34bd68d3dfc3e2f703c7c4d26dbb012 100644 (file)
 #  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
index 8ccce356a2a4970e3abcf680326a65cc4763f236..6fe5475caf02f2df0f8a2db4f0e16d80288e9cde 100644 (file)
@@ -22,7 +22,7 @@
 
 #ifndef __cpp_lib_chrono
 # error "Feature-test macro for constexpr <chrono> missing"
-#elif __cpp_lib_chrono != 201611
+#elif __cpp_lib_chrono < 201611
 # error "Feature-test macro for constexpr <chrono> has wrong value"
 #endif
 
index ea94b062d9697f05abef7b050e606799c89d8101..5cbc050e2101fc1cb16a821bec17ca5d89c879d5 100644 (file)
@@ -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();
 }
index c8e82bb111c23ce150abf4960faaa1e4a0b5ab1d..a6c7c71cfd3acf998fdcbd10ed8ac6f82b7f812f 100644 (file)
@@ -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<seconds> 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<file_clock>(expected) );
+  VERIFY( abbrev == "BST" );
+  VERIFY( offset == 60min );
+}
+
 int main()
 {
   test_ostream();
+  test_parse();
 }
index 29f3148cf14bcf7f3e0cb5512e7b654e267d7bbd..c4fe9bee0f1e596ac4381b7c5f728b9e5181003c 100644 (file)
@@ -6,7 +6,7 @@
 #include <testsuite_hooks.h>
 
 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<gps_clock>(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 (file)
index 0000000..a7c018d
--- /dev/null
@@ -0,0 +1,42 @@
+// { dg-options "-std=gnu++20" }
+// { dg-do run { target c++20 } }
+
+#include <chrono>
+#include <format>
+#include <testsuite_hooks.h>
+
+void
+test_ostream()
+{
+  using std::format;
+  using namespace std::chrono;
+
+  auto st = sys_days{2000y/January/1};
+  auto tt = clock_cast<tai_clock>(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();
+}
index 7bb6851c7decd37a5fcd8ab6c06260eed220c062..8bfaad3278eaf0888cff6fac09114d2e41a9afc1 100644 (file)
@@ -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();
 }
index d0255f5431af8322eb6c36381501e687baf2fe82..530af75442b347c76ff02d4dd3bd32a2ef55d208 100644 (file)
@@ -6,7 +6,7 @@
 #include <testsuite_hooks.h>
 
 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<tai_clock>(expected) );
+  VERIFY( abbrev == "BST" );
+  VERIFY( offset == 60min );
+}
+
 int main()
 {
-  test01();
+  test_ostream();
+  test_parse();
 }
index 977643f11478adca08002f52ff0201946ccb7ebf..c49f6f7e22cffbf0093ea2d8341a6c604b84fc8b 100644 (file)
@@ -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<utc_clock>(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<utc_clock>(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<utc_clock>(expected) );
+}
+
 int main()
 {
   test_ostream();
   test_format();
+  test_parse();
 }
index 6158230f28841955a75737ad47588be8fc233927..d8691b72066f165a10e9d5919f6332b5d14de7c3 100644 (file)
@@ -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();
 }
index 7ceeafd725a88496fe6d1c0121a9d90876787b3d..9cf5b053f2bf08320c9a362d0c13eb3006bcca7d 100644 (file)
@@ -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();
 }
index 454231dd72430eab6eb16914541de54088ecc757..a3f4599fb4e43d7d16d3caabf42a63ee014407a7 100644 (file)
@@ -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 (file)
index 0000000..9b36c5d
--- /dev/null
@@ -0,0 +1,309 @@
+// { dg-do run { target c++20 } }
+// { dg-options "-std=gnu++20" }
+
+#include <chrono>
+#include <sstream>
+#include <testsuite_hooks.h>
+#include <testsuite_allocator.h>
+
+template<typename T, typename CharT>
+  concept stream_extractable
+    = requires (std::basic_istream<CharT>& is) { is >> std::declval<T>(); };
+
+void
+test_recommended_practice()
+{
+  std::chrono::seconds s;
+  using parse_manip = decltype(std::chrono::parse("", s));
+  static_assert( stream_extractable<parse_manip, char> );
+  static_assert( not stream_extractable<parse_manip, wchar_t> );
+  using wparse_manip = decltype(std::chrono::parse(L"", s));
+  static_assert( stream_extractable<wparse_manip, wchar_t> );
+  static_assert( not stream_extractable<wparse_manip, char> );
+
+  // 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<parse_manip> );
+  static_assert( not std::is_move_assignable_v<parse_manip> );
+  static_assert( not stream_extractable<parse_manip&, char> );
+  static_assert( not stream_extractable<const parse_manip&, char> );
+  static_assert( not stream_extractable<wparse_manip&, wchar_t> );
+  static_assert( not stream_extractable<const wparse_manip&, wchar_t> );
+}
+
+template<typename... Args>
+  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<typename... Args>
+    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<const char*, N::C, string, minutes> );
+  N::D d;
+  (void) std::chrono::parse(f, d, abbrev, offset);
+  // This satisfies the concept, but would fail the VERIFY assertion:
+  static_assert( parsable<const char*, N::D, minutes> );
+
+  // Wide strings.
+  static_assert( parsable<const wchar_t*, N::E, wstring> );
+  static_assert( parsable<const wchar_t*, N::E, wstring> );
+  static_assert( parsable<const wchar_t*, N::E, minutes> );
+  static_assert( parsable<const wchar_t*, N::E, wstring, minutes> );
+
+  // Check that invalid calls are properly constrained.
+
+  // from_stream is only overloaded for N::A without abbrev or offset.
+  static_assert( not parsable<const char*, N::A, std::string> );
+  static_assert( not parsable<const char*, N::A, minutes> );
+  static_assert( not parsable<const char*, N::A, string, minutes> );
+  // from_stream is only overloaded for N::B with abbrev.
+  static_assert( not parsable<const char*, N::B> );
+  static_assert( not parsable<const char*, N::B, minutes> );
+  static_assert( not parsable<const char*, N::B, string, minutes> );
+  // from_stream is only overloaded for N::C with abbrev and minutes.
+  static_assert( not parsable<const char*, N::C> );
+  static_assert( not parsable<const char*, N::C, string> );
+  // from_stream is only overloaded for N::D with abbrev and minutes.
+  static_assert( not parsable<const char*, N::D> );
+  static_assert( not parsable<const char*, N::D, string> );
+
+  // Mismatched strings
+  static_assert( not parsable<string, std::chrono::year, wstring> );
+  static_assert( not parsable<string, std::chrono::year, wstring, minutes> );
+
+  using Alloc = __gnu_test::SimpleAllocator<char>;
+  using String = std::basic_string<char, std::char_traits<char>, Alloc>;
+  // Custom allocator
+  static_assert( parsable<String, std::chrono::year> );
+  static_assert( parsable<String, std::chrono::year, String> );
+  static_assert( parsable<String, std::chrono::year, minutes> );
+  static_assert( parsable<String, std::chrono::year, String, minutes> );
+  static_assert( parsable<const char*, std::chrono::year, String> );
+  static_assert( parsable<const char*, std::chrono::year, String, minutes> );
+  // Mismatched allocators
+  static_assert( not parsable<string, std::chrono::year, String> );
+  static_assert( not parsable<string, std::chrono::year, String, minutes> );
+  static_assert( not parsable<String, std::chrono::year, string> );
+  static_assert( not parsable<String, std::chrono::year, string, minutes> );
+}
+
+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();
+}
index 570471aacc719d40cb91b30e3efee5bf3db7aa3c..307d84e54357712a180fbb06f0a15e5e2afe3f78 100644 (file)
@@ -22,9 +22,8 @@
 
 #ifndef __cpp_lib_chrono
 # error "Feature test macro for chrono is missing in <chrono>"
-// FIXME
-// #elif __cpp_lib_chrono < 201907L
-// # error "Feature test macro for chrono has wrong value in <chrono>"
+#elif __cpp_lib_chrono < 201907L
+# error "Feature test macro for chrono has wrong value in <chrono>"
 #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;
index 6cdb98467b13761755ad863448c1ec6b44fd6589..ba9dce00f6ce1aa9b6d46ef81e897f6c3b142c96 100644 (file)
@@ -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();
 }
index 07316e98aa527a58d2917e9db4e17cf32228e101..0c4746479213732832df8711cfbc80fefc0c16f6 100644 (file)
@@ -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();
 }
index 8c0eb9b657948343f92aa58cf9018581c3fe2e2a..8dac24861885516377dd4ac74d06e0ef4ed1c892 100644 (file)
@@ -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();
 }
index 688885b37a1ddcda9cc75531fa9a46701116c626..6c30a8721ea8e3c8086ca8b125ef1286084071f1 100644 (file)
@@ -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();
 }
This page took 0.142611 seconds and 5 git commands to generate.