[libcxx-commits] [libcxx] [libc++][chrono] P0355R7 Implement from_stream and parse (PR #227590)
via libcxx-commits
libcxx-commits at lists.llvm.org
Tue Oct 20 01:23:06 PDT 2026
https://github.com/emmett2020 updated https://github.com/llvm/llvm-project/pull/227590
>From c19c95cba9037acae270a190481710f0d85a4b2a Mon Sep 17 00:00:00 2001
From: Emmett <emmettzhang2020 at outlook.com>
Date: Mon, 28 Sep 2026 16:01:35 +0800
Subject: [PATCH 1/5] [libc++][chrono] Implement from_stream and parse
---
libcxx/include/CMakeLists.txt | 3 +
libcxx/include/__chrono/from_stream.h | 1712 +++++++++++++++++
libcxx/include/__chrono/parse.h | 226 +++
libcxx/include/__chrono/parser_data.h | 146 ++
libcxx/include/chrono | 112 ++
libcxx/include/module.modulemap.in | 3 +
libcxx/modules/std/chrono.inc | 6 +-
libcxx/test/libcxx/time/from_stream.pass.cpp | 164 ++
.../std/time/time.parse/from_stream.pass.cpp | 463 +++++
.../test/std/time/time.parse/parse.pass.cpp | 176 ++
10 files changed, 3009 insertions(+), 2 deletions(-)
create mode 100644 libcxx/include/__chrono/from_stream.h
create mode 100644 libcxx/include/__chrono/parse.h
create mode 100644 libcxx/include/__chrono/parser_data.h
create mode 100644 libcxx/test/libcxx/time/from_stream.pass.cpp
create mode 100644 libcxx/test/std/time/time.parse/from_stream.pass.cpp
create mode 100644 libcxx/test/std/time/time.parse/parse.pass.cpp
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 2219c69f99addc..88c6ca83b1e6b0 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -266,6 +266,7 @@ set(files
__chrono/exception.h
__chrono/file_clock.h
__chrono/formatter.h
+ __chrono/from_stream.h
__chrono/gps_clock.h
__chrono/hh_mm_ss.h
__chrono/high_resolution_clock.h
@@ -277,6 +278,8 @@ set(files
__chrono/month_weekday.h
__chrono/monthday.h
__chrono/ostream.h
+ __chrono/parse.h
+ __chrono/parser_data.h
__chrono/parser_std_format_spec.h
__chrono/statically_widen.h
__chrono/steady_clock.h
diff --git a/libcxx/include/__chrono/from_stream.h b/libcxx/include/__chrono/from_stream.h
new file mode 100644
index 00000000000000..bb5d5af9d1696b
--- /dev/null
+++ b/libcxx/include/__chrono/from_stream.h
@@ -0,0 +1,1712 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___CHRONO_FROM_STREAM_H
+#define _LIBCPP___CHRONO_FROM_STREAM_H
+
+#include <__config>
+
+#if _LIBCPP_HAS_LOCALIZATION
+
+# include <__chrono/calendar.h>
+# include <__chrono/concepts.h>
+# include <__chrono/day.h>
+# include <__chrono/duration.h>
+# include <__chrono/file_clock.h>
+# include <__chrono/gps_clock.h>
+# include <__chrono/hh_mm_ss.h>
+# include <__chrono/month.h>
+# include <__chrono/monthday.h>
+# include <__chrono/parser_data.h>
+# include <__chrono/statically_widen.h>
+# include <__chrono/system_clock.h>
+# include <__chrono/tai_clock.h>
+# include <__chrono/time_point.h>
+# include <__chrono/tzdb.h>
+# include <__chrono/tzdb_list.h>
+# include <__chrono/utc_clock.h>
+# include <__chrono/weekday.h>
+# include <__chrono/year.h>
+# include <__chrono/year_month.h>
+# include <__chrono/year_month_day.h>
+# include <__fwd/memory.h>
+# include <__fwd/string.h>
+# include <__iterator/istreambuf_iterator.h>
+# include <__locale_dir/ctype.h>
+# include <__locale_dir/locale.h>
+# include <__locale_dir/num.h>
+# include <__locale_dir/time.h>
+# include <__memory/addressof.h>
+# include <__type_traits/common_type.h>
+# include <__utility/move.h>
+# include <cctype>
+# include <cstdint>
+# include <ctime>
+# include <istream>
+# include <limits>
+# include <ratio>
+# include <string>
+
+# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+# pragma GCC system_header
+# endif
+
+_LIBCPP_PUSH_MACROS
+# include <__undef_macros>
+
+# if _LIBCPP_STD_VER >= 20
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+namespace chrono {
+struct __parse_options {
+ // The number of fractional digits used to determine the default width of %S.
+ unsigned __default_fractional_width_ = 0;
+
+ _LIBCPP_HIDE_FROM_ABI constexpr bool __allows_fraction() const { return __default_fractional_width_ != 0; }
+};
+
+template <class _Tp>
+_LIBCPP_HIDE_FROM_ABI constexpr __parse_options __get_parse_options() {
+ if constexpr (__is_duration_v<_Tp>) {
+ constexpr unsigned __width = hh_mm_ss<_Tp>::fractional_width;
+ // Types such as duration<double> have fractional_width == 0 but can store 1.5 seconds.
+ // Use __subsecond_precision as the default number of fractional digits for %S.
+ if constexpr (__width == 0 && treat_as_floating_point_v<typename _Tp::rep>)
+ return {__fields_storage::__subsecond_precision};
+ else
+ return {__width};
+ } else if constexpr (__is_time_point<_Tp>) {
+ return chrono::__get_parse_options<typename _Tp::duration>();
+ } else {
+ return {};
+ }
+}
+
+// Check an inclusive range without narrowing parsed int or int64_t fields.
+_LIBCPP_HIDE_FROM_ABI constexpr bool __in_range(int64_t __value, int64_t __lo, int64_t __hi) {
+ return __lo <= __value && __value <= __hi;
+}
+
+_LIBCPP_HIDE_FROM_ABI constexpr int64_t __pow10(unsigned __exp) {
+ int64_t __result = 1;
+ for (unsigned __i = 0; __i < __exp; ++__i)
+ __result *= 10;
+ return __result;
+}
+
+struct __read_digits_result {
+ uint64_t __value = 0;
+ unsigned __digits_read = 0;
+ bool __overflow = false;
+};
+
+_LIBCPP_HIDE_FROM_ABI constexpr bool __width_allowed(char __spec) {
+ switch (__spec) {
+ case 'C':
+ case 'd':
+ case 'e':
+ case 'F':
+ case 'g':
+ case 'G':
+ case 'H':
+ case 'I':
+ case 'j':
+ case 'm':
+ case 'M':
+ case 'S':
+ case 'u':
+ case 'U':
+ case 'V':
+ case 'w':
+ case 'W':
+ case 'y':
+ case 'Y':
+ return true;
+ default:
+ return false;
+ }
+}
+
+// Returns whether [time.parse] permits '__modifier' for '__spec'.
+_LIBCPP_HIDE_FROM_ABI constexpr bool __modifier_allowed(char __modifier, char __spec) {
+ if (__modifier == 'E')
+ switch (__spec) {
+ case 'c':
+ case 'C':
+ case 'x':
+ case 'X':
+ case 'y':
+ case 'Y':
+ case 'z':
+ return true;
+ default:
+ return false;
+ }
+
+ switch (__spec) {
+ case 'd':
+ case 'e':
+ case 'H':
+ case 'I':
+ case 'm':
+ case 'M':
+ case 'S':
+ case 'U':
+ case 'w':
+ case 'W':
+ case 'y':
+ case 'z':
+ return true;
+ default:
+ return false;
+ }
+}
+
+template <class _CharT, class _Traits>
+class __from_stream_parser {
+ // Share the input stream and local error state across all parsing operations.
+ basic_istream<_CharT, _Traits>& __is_;
+ ios_base::iostate& __state_;
+
+public:
+ _LIBCPP_HIDE_FROM_ABI __from_stream_parser(basic_istream<_CharT, _Traits>& __is, ios_base::iostate& __state) noexcept
+ : __is_(__is), __state_(__state) {}
+
+ _LIBCPP_HIDE_FROM_ABI bool __fail() const noexcept {
+ return (__state_ & (ios_base::failbit | ios_base::badbit)) != 0;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI bool __peek(_CharT& __c) {
+ if (__state_ != ios_base::goodbit)
+ return false;
+
+ typename _Traits::int_type __i = __is_.rdbuf()->sgetc();
+ if (_Traits::eq_int_type(__i, _Traits::eof())) {
+ __state_ |= ios_base::eofbit;
+ return false;
+ }
+
+ __c = _Traits::to_char_type(__i);
+ return true;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI void __consume() {
+ // A character is required, but the input has already reached EOF.
+ if (__state_ & ios_base::eofbit) {
+ __state_ |= ios_base::failbit;
+ return;
+ }
+
+ const auto __i = __is_.rdbuf()->sbumpc();
+ if (_Traits::eq_int_type(__i, _Traits::eof()))
+ __state_ |= ios_base::eofbit | ios_base::failbit;
+ }
+
+ // Extracts digits and reports their value, count, and overflow status.
+ // Stops after consuming the first digit that would overflow '__max_value'.
+ _LIBCPP_HIDE_FROM_ABI __read_digits_result __read_digits(unsigned __max_digits, uint64_t __max_value) {
+ uint64_t __result = 0;
+ unsigned __digits_read = 0;
+
+ for (_CharT __c{}; __digits_read < __max_digits && __peek(__c);) {
+ if (__c < _CharT('0') || __c > _CharT('9'))
+ break;
+ __consume();
+ ++__digits_read;
+
+ const uint64_t __digit = static_cast<uint64_t>(__c - _CharT('0'));
+ if (__result > __max_value / 10 || (__result == __max_value / 10 && __digit > __max_value % 10))
+ return {__result, __digits_read, true};
+ __result = __result * 10 + __digit;
+ }
+
+ return {__result, __digits_read, false};
+ }
+
+ // Reads an integer without a sign into int. Returns the number of digits read.
+ // Failure leaves '__value' unchanged.
+ _LIBCPP_HIDE_FROM_ABI unsigned __read_unsigned(unsigned __max_digits, int& __value) {
+ auto __result = __read_digits(__max_digits, (numeric_limits<int>::max)());
+
+ if (__result.__digits_read == 0 || __result.__overflow)
+ __state_ |= ios_base::failbit;
+ else
+ __value = static_cast<int>(__result.__value);
+
+ return __result.__digits_read;
+ }
+
+ // Reads an integer with an optional '+' or '-'. Failure leaves '__value' unchanged.
+ // The width includes an optional sign.
+ _LIBCPP_HIDE_FROM_ABI void __read_signed(unsigned __width, int& __value) {
+ bool __negative = false;
+ if (_CharT __c{}; __width != 0 && __peek(__c) && (_Traits::eq(__c, _CharT('-')) || _Traits::eq(__c, _CharT('+')))) {
+ __negative = _Traits::eq(__c, _CharT('-'));
+ __consume();
+ --__width;
+ }
+
+ const uint64_t __positive_limit = static_cast<uint64_t>((numeric_limits<int>::max)());
+ const uint64_t __negative_limit = __positive_limit + 1;
+ const uint64_t __limit = __negative ? __negative_limit : __positive_limit;
+
+ auto __result = __read_digits(__width, __limit);
+ if (__result.__digits_read == 0 || __result.__overflow) {
+ __state_ |= ios_base::failbit;
+ return;
+ }
+
+ if (__negative)
+ __value = static_cast<int>(-static_cast<int64_t>(__result.__value));
+ else
+ __value = static_cast<int>(__result.__value);
+ }
+
+ // Parses one locale-dependent conversion specifier with time_get.
+ _LIBCPP_HIDE_FROM_ABI bool __read_with_time_get(tm& __tm, char __spec, char __modifier = 0) {
+ using _Iter = istreambuf_iterator<_CharT, _Traits>;
+ using _Facet = time_get<_CharT, _Iter>;
+
+ const _Facet& __tf = std::use_facet<_Facet>(__is_.getloc());
+ __tf.get(_Iter(__is_), _Iter(), __is_, __state_, std::addressof(__tm), __spec, __modifier);
+
+ // time_get may report only eofbit for incomplete locale formats
+ // (e.g. "01:30" parsed with %X in the classic locale), causing truncated
+ // input to be accepted here. Fix format completeness checking in time_get.
+ //
+ // Propagate eofbit as well as parse failures. Reaching EOF after a
+ // successful match is not itself a parsing failure.
+ return !__fail();
+ }
+
+ // Parses a locale-dependent month name into a one-based month.
+ _LIBCPP_HIDE_FROM_ABI void __read_month_name(int& __value) {
+ tm __tm{};
+ if (__read_with_time_get(__tm, 'b')) {
+ if (__tm.tm_mon == (numeric_limits<int>::max)())
+ __state_ |= ios_base::failbit;
+ else
+ __value = __tm.tm_mon + 1; // tm_mon is 0-based [0, 11].
+ }
+ }
+
+ // Parses a locale-dependent weekday name using the chrono::weekday convention.
+ _LIBCPP_HIDE_FROM_ABI void __read_weekday_name(int& __value) {
+ tm __tm{};
+ if (__read_with_time_get(__tm, 'a'))
+ __value = __tm.tm_wday; // tm_wday is already [0, 6], Sunday == 0.
+ }
+
+ // time_get reports %p through tm_hour. Starting at zero leaves AM as 0
+ // and changes PM to 12.
+ _LIBCPP_HIDE_FROM_ABI void __read_am_pm(bool& __is_pm) {
+ tm __tm{};
+ if (__read_with_time_get(__tm, 'p'))
+ __is_pm = __tm.tm_hour == 12;
+ }
+
+ // Parses an O-modified field through time_get and converts its tm member.
+ _LIBCPP_HIDE_FROM_ABI void __read_alternative_field(char __spec, int& __field) {
+ tm __tm{};
+ if (!__read_with_time_get(__tm, __spec, 'O'))
+ return;
+
+ switch (__spec) {
+ case 'd':
+ case 'e':
+ __field = __tm.tm_mday;
+ break;
+ case 'm':
+ if (__tm.tm_mon == (numeric_limits<int>::max)())
+ __state_ |= ios_base::failbit;
+ else
+ __field = __tm.tm_mon + 1;
+ break;
+ case 'H':
+ __field = __tm.tm_hour;
+ break;
+ case 'I':
+ // Keep the 12-hour value separate from %p until the fields are combined.
+ __field = __tm.tm_hour == 0 ? 12 : __tm.tm_hour;
+ break;
+ case 'M':
+ __field = __tm.tm_min;
+ break;
+ case 'w':
+ __field = __tm.tm_wday;
+ break;
+ case 'y': {
+ // tm_year is relative to 1900; %Oy supplies only the last two digits.
+ int64_t __year = static_cast<int64_t>(__tm.tm_year) + 1900;
+ __field = static_cast<int>((__year < 0 ? -__year : __year) % 100);
+ break;
+ }
+ default:
+ __state_ |= ios_base::failbit;
+ return;
+ }
+ }
+
+ // Parses %S and its optional fractional part. '__width' includes the decimal point.
+ _LIBCPP_HIDE_FROM_ABI void
+ __read_seconds(unsigned __width, bool __allow_fraction, int& __seconds, int64_t& __subseconds) {
+ constexpr unsigned __precision = __fields_storage::__subsecond_precision;
+ const auto __integer = __read_digits(__width, (numeric_limits<int>::max)());
+ if (__integer.__overflow) {
+ __state_ |= ios_base::failbit;
+ return;
+ }
+
+ unsigned __remaining = __width - __integer.__digits_read;
+ __read_digits_result __fraction{};
+ _CharT __c{};
+ const _CharT __decimal_point = std::use_facet<numpunct<_CharT> >(__is_.getloc()).decimal_point();
+ if (__allow_fraction && __remaining != 0 && __peek(__c) && _Traits::eq(__c, __decimal_point)) {
+ __consume();
+ --__remaining;
+ __fraction =
+ __read_digits(__remaining < __precision ? __remaining : __precision, (numeric_limits<int64_t>::max)());
+ __remaining -= __fraction.__digits_read;
+ // Consume excess fractional digits within the field width, discarding them without rounding.
+ while (__remaining != 0 && __peek(__c) && _CharT('0') <= __c && __c <= _CharT('9')) {
+ __consume();
+ --__remaining;
+ }
+ }
+
+ // Either side of the decimal point may be empty, but at least one digit is required.
+ if (__integer.__digits_read == 0 && __fraction.__digits_read == 0) {
+ __state_ |= ios_base::failbit;
+ return;
+ }
+
+ // The fraction is stored scaled to attoseconds, so that the conversion to the
+ // target's precision does not depend on the number of digits that were read.
+ __seconds = static_cast<int>(__integer.__value);
+ __subseconds = static_cast<int64_t>(__fraction.__value) * chrono::__pow10(__precision - __fraction.__digits_read);
+ }
+
+ // Parses %z as [+|-]hh[mm], and %Ez/%Oz as [+|-]h[h][:mm].
+ _LIBCPP_HIDE_FROM_ABI void __read_utc_offset(bool __is_modified, int& __value) {
+ _CharT __c{};
+ if (!__peek(__c)) {
+ __state_ |= ios_base::failbit;
+ return;
+ }
+ int __sign = 1;
+ if (_Traits::eq(__c, _CharT('-'))) {
+ __sign = -1;
+ __consume();
+ } else if (_Traits::eq(__c, _CharT('+'))) {
+ __consume();
+ }
+
+ int __hours = 0;
+ unsigned __digits_read = __read_unsigned(2, __hours);
+
+ // %z requires exactly two hour digits, while %Ez and %Oz allow one or two.
+ if (__fail() || (!__is_modified && __digits_read != 2)) {
+ __state_ |= ios_base::failbit;
+ return;
+ }
+
+ // The minutes are optional in both forms, but the modified form requires the
+ // colon before them, and a colon requires the minutes to follow.
+ int __minutes = 0;
+ bool __has_minutes{};
+ if (__is_modified) {
+ __has_minutes = __peek(__c) && _Traits::eq(__c, _CharT(':'));
+ if (__has_minutes)
+ __consume();
+ } else {
+ __has_minutes = __peek(__c) && __c >= _CharT('0') && __c <= _CharT('9');
+ }
+
+ if (__has_minutes && __read_unsigned(2, __minutes) != 2) {
+ __state_ |= ios_base::failbit;
+ return;
+ }
+
+ __value = __sign * (__hours * 60 + __minutes);
+ }
+
+ // Parses a nonempty %Z token containing alphanumerics or '_', '/', '-', and '+'.
+ _LIBCPP_HIDE_FROM_ABI void __read_time_zone_abbrev(basic_string<_CharT, _Traits>& __abbrev) {
+ const auto& __ctype = std::use_facet<ctype<_CharT> >(__is_.getloc());
+ __abbrev.clear();
+
+ for (_CharT __c{}; __peek(__c);) {
+ char __narrow = __ctype.narrow(__c, '\0');
+ if (!std::isdigit(static_cast<unsigned char>(__narrow)) && ('a' > __narrow || __narrow > 'z') &&
+ ('A' > __narrow || __narrow > 'Z') && __narrow != '_' && __narrow != '/' && __narrow != '-' &&
+ __narrow != '+')
+ break;
+
+ __consume();
+ __abbrev.push_back(__c);
+ }
+
+ if (__abbrev.empty())
+ __state_ |= ios_base::failbit;
+ }
+
+ // Parses '__fmt' into '__f', setting failbit on a mismatch.
+ _LIBCPP_HIDE_FROM_ABI void __parse(
+ const _CharT* __fmt, __fields_storage& __f, basic_string<_CharT, _Traits>& __abbrev, __parse_options __options) {
+ const auto& __ctype = std::use_facet<ctype<_CharT> >(__is_.getloc());
+
+ auto __skip_one_whitespace = [&] {
+ _CharT __c{};
+ if (!__peek(__c) || !__ctype.is(ctype_base::space, __c))
+ return false;
+ __consume();
+ return true;
+ };
+
+ auto __match = [&](_CharT __expected) {
+ _CharT __c{};
+ if (!__peek(__c) || !_Traits::eq(__c, __expected)) {
+ __state_ |= ios_base::failbit;
+ return;
+ }
+ __consume();
+ };
+
+ // Accept repeated fields only when their values agree.
+ auto __assign = [&](auto& __field, auto __value, __fields_set __field_bit) {
+ if (__f.__has(__field_bit) && __field != __value) {
+ __state_ |= ios_base::failbit;
+ return false;
+ }
+ __field = __value;
+ __f.__set(__field_bit);
+ return true;
+ };
+
+ auto __assign_seconds = [&](int __seconds, int64_t __subseconds) {
+ // Repeated seconds fields must agree in both their integer and fractional parts.
+ if (__f.__has(__fields_set::__seconds) && (__f.__seconds_ != __seconds || __f.__subseconds_ != __subseconds)) {
+ __state_ |= ios_base::failbit;
+ return false;
+ }
+
+ __f.__seconds_ = __seconds;
+ __f.__subseconds_ = __subseconds;
+ __f.__set(__fields_set::__seconds);
+ return true;
+ };
+
+ auto __assign_date = [&](const tm& __tm) {
+ const int64_t __year = static_cast<int64_t>(__tm.tm_year) + 1900;
+ if (__year > (numeric_limits<int>::max)() || __tm.tm_mon == (numeric_limits<int>::max)()) {
+ __state_ |= ios_base::failbit;
+ return false;
+ }
+ return __assign(__f.__year_, static_cast<int>(__year), __fields_set::__year) &&
+ __assign(__f.__month_, __tm.tm_mon + 1, __fields_set::__month) &&
+ __assign(__f.__day_, __tm.tm_mday, __fields_set::__day) &&
+ (__tm.tm_wday == -1 || __assign(__f.__weekday_, __tm.tm_wday, __fields_set::__weekday));
+ };
+
+ auto __assign_time = [&](const tm& __tm) {
+ return __assign(__f.__hours_, __tm.tm_hour, __fields_set::__hours) &&
+ __assign(__f.__minutes_, __tm.tm_min, __fields_set::__minutes) && __assign_seconds(__tm.tm_sec, 0);
+ };
+
+ while (*__fmt != _CharT('\0')) {
+ if (__fail())
+ return;
+
+ if (__ctype.is(ctype_base::space, *__fmt)) {
+ while (__skip_one_whitespace()) {
+ }
+ ++__fmt;
+ continue;
+ }
+
+ if (*__fmt != _CharT('%')) {
+ __match(*__fmt);
+ ++__fmt;
+ continue;
+ }
+
+ ++__fmt;
+
+ unsigned __width = 0;
+ bool __has_width = false;
+ while (_CharT('0') <= *__fmt && *__fmt <= _CharT('9')) {
+ __has_width = true;
+ const unsigned __digit = static_cast<unsigned>(*__fmt - _CharT('0'));
+ if (__width > ((numeric_limits<unsigned>::max)() - __digit) / 10) {
+ __state_ |= ios_base::failbit;
+ return;
+ }
+ __width = __width * 10 + __digit;
+ ++__fmt;
+ }
+
+ char __modifier = '\0';
+ if (*__fmt == _CharT('E') || *__fmt == _CharT('O')) {
+ __modifier = static_cast<char>(*__fmt == _CharT('E') ? 'E' : 'O');
+ ++__fmt;
+ }
+
+ if (__has_width && __modifier != '\0') {
+ __state_ |= ios_base::failbit;
+ return;
+ }
+
+ char __spec = __ctype.narrow(*__fmt, '\0');
+
+ if ((__has_width && !__width_allowed(__spec)) || (__modifier != 0 && !__modifier_allowed(__modifier, __spec))) {
+ __state_ |= ios_base::failbit;
+ return;
+ }
+
+ int __value{};
+ switch (__spec) {
+ case 'a':
+ case 'A':
+ __read_weekday_name(__value);
+ if (!__fail())
+ __assign(__f.__weekday_, __value, __fields_set::__weekday);
+ break;
+
+ case 'b':
+ case 'h':
+ case 'B':
+ __read_month_name(__value);
+ if (!__fail())
+ __assign(__f.__month_, __value, __fields_set::__month);
+ break;
+
+ case 'c': {
+ tm __tm{};
+ // The locale's format may omit the weekday. Use -1 to distinguish an
+ // unparsed weekday from Sunday, whose tm_wday value is 0.
+ __tm.tm_wday = -1;
+ if (__read_with_time_get(__tm, __spec, __modifier) && __assign_date(__tm))
+ __assign_time(__tm);
+ break;
+ }
+
+ case 'C':
+ if (__modifier == 'E') {
+ // time_get does not support %C; parse %EC numerically.
+ __read_signed(2, __value);
+ } else {
+ __read_signed(__has_width ? __width : 2, __value);
+ }
+ if (!__fail())
+ __assign(__f.__century_, __value, __fields_set::__century);
+ break;
+
+ case 'd':
+ case 'e':
+ if (__modifier == 'O')
+ __read_alternative_field(__spec, __value);
+ else
+ __read_unsigned(__has_width ? __width : 2, __value);
+ if (!__fail())
+ __assign(__f.__day_, __value, __fields_set::__day);
+ break;
+
+ case 'D':
+ __parse(_LIBCPP_STATICALLY_WIDEN(_CharT, "%m/%d/%y"), __f, __abbrev, __options);
+ break;
+
+ case 'F':
+ // A width on %F applies only to %Y.
+ __read_signed(__has_width ? __width : 4, __value);
+ if (!__fail() && __assign(__f.__year_, __value, __fields_set::__year)) {
+ __parse(_LIBCPP_STATICALLY_WIDEN(_CharT, "-%m-%d"), __f, __abbrev, __options);
+ }
+ break;
+
+ case 'g':
+ __read_unsigned(__has_width ? __width : 2, __value);
+ if (!__fail()) {
+ if (!__in_range(__value, 0, 99))
+ __state_ |= ios_base::failbit;
+ else
+ __assign(__f.__iso_year_of_century_, __value, __fields_set::__iso_year_of_century);
+ }
+ break;
+
+ case 'G':
+ __read_signed(__has_width ? __width : 4, __value);
+ if (!__fail())
+ __assign(__f.__iso_year_, __value, __fields_set::__iso_year);
+ break;
+
+ case 'H':
+ if (__modifier == 'O')
+ __read_alternative_field(__spec, __value);
+ else
+ __read_unsigned(__has_width ? __width : 2, __value);
+ if (!__fail())
+ __assign(__f.__hours_, __value, __fields_set::__hours);
+ break;
+
+ case 'I':
+ if (__modifier == 'O')
+ __read_alternative_field(__spec, __value);
+ else
+ __read_unsigned(__has_width ? __width : 2, __value);
+ if (!__fail())
+ __assign(__f.__hour12_, __value, __fields_set::__hour12);
+ break;
+
+ case 'j':
+ // The day of the year for a calendar type; a plain number of days when
+ // the target is a duration, in which case it is not limited to [1, 366].
+ __read_unsigned(__has_width ? __width : 3, __value);
+ if (!__fail())
+ __assign(__f.__day_of_year_, __value, __fields_set::__day_of_year);
+ break;
+
+ case 'm':
+ if (__modifier == 'O')
+ __read_alternative_field(__spec, __value);
+ else
+ __read_unsigned(__has_width ? __width : 2, __value);
+ if (!__fail())
+ __assign(__f.__month_, __value, __fields_set::__month);
+ break;
+
+ case 'M':
+ if (__modifier == 'O')
+ __read_alternative_field(__spec, __value);
+ else
+ __read_unsigned(__has_width ? __width : 2, __value);
+ if (!__fail())
+ __assign(__f.__minutes_, __value, __fields_set::__minutes);
+ break;
+
+ case 'p': {
+ bool __is_pm{};
+ __read_am_pm(__is_pm);
+ if (!__fail())
+ __assign(__f.__is_pm_, __is_pm, __fields_set::__am_pm);
+ break;
+ }
+
+ case 'r':
+ case 'X': {
+ tm __tm{};
+ if (__read_with_time_get(__tm, __spec, __modifier))
+ __assign_time(__tm);
+ break;
+ }
+
+ case 'R':
+ __parse(_LIBCPP_STATICALLY_WIDEN(_CharT, "%H:%M"), __f, __abbrev, __options);
+ break;
+
+ case 'T':
+ __parse(_LIBCPP_STATICALLY_WIDEN(_CharT, "%H:%M:%S"), __f, __abbrev, __options);
+ break;
+
+ case 'S': {
+ // The target type determines whether fractions are allowed; the width only
+ // limits the number of characters consumed.
+ unsigned __default_width = __options.__allows_fraction() ? 3 + __options.__default_fractional_width_ : 2;
+ // time_get ignores the O modifier; parse %OS using ordinary digits.
+ int64_t __subseconds{};
+ __read_seconds(__has_width ? __width : __default_width, __options.__allows_fraction(), __value, __subseconds);
+ if (!__fail())
+ __assign_seconds(__value, __subseconds);
+ break;
+ }
+
+ case 'u': {
+ int __weekday = 0;
+ __read_unsigned(__has_width ? __width : 1, __weekday);
+ if (!__fail()) {
+ if (!__in_range(__weekday, 1, 7))
+ __state_ |= ios_base::failbit;
+ else
+ __assign(__f.__weekday_, __weekday % 7, __fields_set::__weekday);
+ }
+ break;
+ }
+
+ case 'w': {
+ if (__modifier == 'O')
+ __read_alternative_field(__spec, __value);
+ else
+ __read_unsigned(__has_width ? __width : 1, __value);
+ if (!__fail())
+ __assign(__f.__weekday_, __value, __fields_set::__weekday);
+ break;
+ }
+
+ case 'U':
+ // time_get does not support %U; parse %OU numerically.
+ __read_unsigned(__has_width ? __width : 2, __value);
+ if (!__fail())
+ __assign(__f.__week_sun_, __value, __fields_set::__week_sun);
+ break;
+
+ case 'V':
+ __read_unsigned(__has_width ? __width : 2, __value);
+ if (!__fail())
+ __assign(__f.__iso_week_, __value, __fields_set::__iso_week);
+ break;
+
+ case 'W':
+ // time_get does not support %W; parse %OW numerically.
+ __read_unsigned(__has_width ? __width : 2, __value);
+ if (!__fail())
+ __assign(__f.__week_mon_, __value, __fields_set::__week_mon);
+ break;
+
+ case 'x': {
+ tm __tm{};
+ // The locale's format may omit the weekday. Use -1 to distinguish an
+ // unparsed weekday from Sunday, whose tm_wday value is 0.
+ __tm.tm_wday = -1;
+ if (__read_with_time_get(__tm, __spec, __modifier))
+ __assign_date(__tm);
+ break;
+ }
+
+ case 'y':
+ if (__modifier == 'O')
+ __read_alternative_field(__spec, __value);
+ else if (__modifier == 'E') {
+ tm __tm{};
+ if (__read_with_time_get(__tm, __spec, __modifier)) {
+ const int64_t __year = static_cast<int64_t>(__tm.tm_year) + 1900;
+ __value = static_cast<int>((__year < 0 ? -__year : __year) % 100);
+ }
+ } else
+ __read_unsigned(__has_width ? __width : 2, __value);
+ if (!__fail())
+ __assign(__f.__year_of_century_, __value, __fields_set::__year_of_century);
+ break;
+
+ case 'Y':
+ if (__modifier == 'E') {
+ tm __tm{};
+ if (__read_with_time_get(__tm, __spec, __modifier)) {
+ const int64_t __year = static_cast<int64_t>(__tm.tm_year) + 1900;
+ if (__year > (numeric_limits<int>::max)())
+ __state_ |= ios_base::failbit;
+ else
+ __value = static_cast<int>(__year);
+ }
+ } else
+ __read_signed(__has_width ? __width : 4, __value);
+ if (!__fail())
+ __assign(__f.__year_, __value, __fields_set::__year);
+ break;
+
+ case 'z':
+ __read_utc_offset(__modifier != 0, __value);
+ if (!__fail())
+ __assign(__f.__utc_offset_, __value, __fields_set::__utc_offset);
+ break;
+
+ case 'Z': {
+ basic_string<_CharT, _Traits> __parsed;
+ __read_time_zone_abbrev(__parsed);
+ if (!__fail()) {
+ // A parsed abbreviation is nonempty; an empty string means no prior %Z.
+ if (!__abbrev.empty() && __abbrev != __parsed)
+ __state_ |= ios_base::failbit;
+ else
+ __abbrev = std::move(__parsed);
+ }
+ break;
+ }
+
+ case 'n':
+ // %n matches exactly one white space character, %t at most one. Combining
+ // them and a literal space matches a range, e.g. "%n%t%t" matches one to
+ // three white space characters.
+ if (!__skip_one_whitespace())
+ __state_ |= ios_base::failbit;
+ break;
+ case 't':
+ __skip_one_whitespace();
+ break;
+ case '%':
+ __match(_CharT('%'));
+ break;
+ default:
+ __state_ |= ios_base::failbit;
+ return;
+ }
+ ++__fmt;
+ }
+ }
+};
+
+_LIBCPP_HIDE_FROM_ABI inline int64_t __compose_year(int __century, int __last_two_digits) {
+ // %C uses floored division; %y and %g contain the absolute last two digits.
+ return static_cast<int64_t>(__century) * 100 +
+ (__century < 0 && __last_two_digits != 0 ? 100 - __last_two_digits : __last_two_digits);
+}
+
+// Construct a candidate year from %C/%y or %Y, then check consistency with any
+// other supplied year fields. Store the result only if it is representable.
+_LIBCPP_HIDE_FROM_ABI inline bool __try_get_year(const __fields_storage& __f, int& __result) {
+ int64_t __year{};
+
+ if (__f.__has(__fields_set::__year_of_century)) {
+ if (!__in_range(__f.__year_of_century_, 0, 99))
+ return false;
+
+ // Use an explicit century or full year before falling back to the default century.
+ if (__f.__has(__fields_set::__century)) {
+ __year = __compose_year(__f.__century_, __f.__year_of_century_);
+ } else if (__f.__has(__fields_set::__year)) {
+ __year = __f.__year_;
+ if ((__year < 0 ? -__year : __year) % 100 != __f.__year_of_century_)
+ return false;
+ } else {
+ __year = (__f.__year_of_century_ <= 68 ? 2000 : 1900) + __f.__year_of_century_;
+ }
+
+ // Check the constructed year against %Y, if supplied.
+ if (__f.__has(__fields_set::__year) && __f.__year_ != __year)
+ return false;
+ } else if (__f.__has(__fields_set::__year)) {
+ __year = __f.__year_;
+
+ // Check the year obtained from %Y against %C, if supplied.
+ if (__f.__has(__fields_set::__century) && __f.__century_ != __year / 100 - (__year % 100 < 0))
+ return false;
+ } else {
+ return false;
+ }
+
+ if (!__in_range(__year, static_cast<int>((year::min)()), static_cast<int>((year::max)())))
+ return false;
+
+ __result = static_cast<int>(__year);
+ return true;
+}
+
+// Converts an ISO year (%G, or expanded %g), week (%V), and weekday (%u/%w)
+// to sys_days.
+_LIBCPP_HIDE_FROM_ABI inline bool __iso_week_to_sys_days(int64_t __g, int __v, weekday __wd, sys_days& __result) {
+ const int __min_year = static_cast<int>((year::min)());
+ const int __max_year = static_cast<int>((year::max)());
+ if (!__in_range(__g, __min_year - 1, __max_year + 1) || !__in_range(__v, 1, 53))
+ return false;
+
+ const days __year_length{__g % 4 == 0 && (__g % 100 != 0 || __g % 400 == 0) ? 366 : 365};
+ // An ISO year can extend one year beyond chrono::year's range. Anchor its
+ // January 1 in a representable year before doing arithmetic in sys_days.
+ sys_days __jan1;
+ if (__g < __min_year)
+ __jan1 = sys_days{(year::min)() / January / 1} - __year_length;
+ else if (__g > __max_year)
+ __jan1 = sys_days{(year::max)() / January / 1} + days{(year::max)().is_leap() ? 366 : 365};
+ else
+ __jan1 = sys_days{year{static_cast<int>(__g)} / January / 1};
+
+ // ISO week 1 contains __g-01-04 and starts on Monday.
+ // Compute days from 1970-01-01 to __g/__v/__wd in four parts:
+ // 1. Days from 1970-01-01 to __g-01-04.
+ // 2. Subtract the initial partial week from that week's Monday to __g-01-04.
+ // 3. (__v - 1) complete weeks preceding the requested week.
+ // 4. The final partial week from Monday to __wd.
+ sys_days __jan4 = __jan1 + days{3};
+ weekday __jan4_wd{__jan4};
+ sys_days __week1_start = __jan4 - days{static_cast<int>(__jan4_wd.iso_encoding()) - 1};
+ sys_days __date = __week1_start + weeks{__v - 1} + days{static_cast<int>(__wd.iso_encoding()) - 1};
+
+ // Reject a nonexistent week: the Thursday of the result's week must fall in
+ // the ISO year '__g'.
+ sys_days __thursday = __date + days{4 - static_cast<int>(__wd.iso_encoding())};
+ if (__thursday < __jan1 || __thursday >= __jan1 + __year_length)
+ return false;
+
+ // Check before converting to year_month_day, which could wrap the year.
+ if (__date < sys_days{(year::min)() / January / 1} || __date > sys_days{(year::max)() / December / 31})
+ return false;
+
+ __result = __date;
+ return true;
+}
+
+// Converts a calendar year (%Y or %C/%y), week (%U/%W), and weekday (%u/%w)
+// to sys_days, including week zero.
+// The caller must supply a valid year.
+_LIBCPP_HIDE_FROM_ABI inline bool
+__week_to_sys_days(int __year, int __week, weekday __first, weekday __wd, sys_days& __result) {
+ if (!__in_range(__week, 0, 53))
+ return false;
+
+ // Compute days from 1970-01-01 to __year/__week/__wd in four parts:
+ // 1. Days from 1970-01-01 to __year-01-01.
+ // 2. The initial partial week from __year-01-01 to the next week start (__first).
+ // 3. (__week - 1) complete weeks preceding the requested week.
+ // 4. The final partial week from the week's start (__first) to __wd.
+ sys_days __jan1{year{__year} / January / 1};
+ sys_days __week1_start = __jan1 + (__first - weekday{__jan1});
+ sys_days __date = __week1_start + weeks{__week - 1} + (__wd - __first);
+ if (year_month_day{__date}.year() != year{__year})
+ return false;
+
+ __result = __date;
+ return true;
+}
+
+// Construct a valid date from the first complete combination of parsed fields.
+_LIBCPP_HIDE_FROM_ABI inline bool __make_date(const __fields_storage& __f, year_month_day& __result) {
+ // Calendar-year combinations use %Y or a year obtained from %C/%y.
+ if (int __year{}; __try_get_year(__f, __year)) {
+ // Calendar date: %Y %m %d, also supplied by formats such as %F, %D, or %x.
+ if (__f.__has(__fields_set::__month | __fields_set::__day)) {
+ if (!__in_range(__f.__month_, 1, 12) || !__in_range(__f.__day_, 1, 31))
+ return false;
+
+ const year_month_day __ymd{
+ year{__year}, month{static_cast<unsigned>(__f.__month_)}, day{static_cast<unsigned>(__f.__day_)}};
+ if (!__ymd.ok())
+ return false;
+ __result = __ymd;
+ return true;
+ }
+
+ // Ordinal date: %Y %j.
+ if (__f.__has(__fields_set::__day_of_year)) {
+ if (!__in_range(__f.__day_of_year_, 1, year{__year}.is_leap() ? 366 : 365))
+ return false;
+
+ __result = year_month_day{sys_days{year{__year} / January / 1} + days{__f.__day_of_year_ - 1}};
+ return true;
+ }
+
+ // Week date: %Y with %U or %W and a weekday (%a/%A/%u/%w).
+ if (__f.__has(__fields_set::__weekday) && __f.__has_any(__fields_set::__week_sun | __fields_set::__week_mon)) {
+ if (!__in_range(__f.__weekday_, 0, 6))
+ return false;
+ const bool __use_sunday = __f.__has(__fields_set::__week_sun);
+ sys_days __date{};
+ if (!__week_to_sys_days(
+ __year,
+ __use_sunday ? __f.__week_sun_ : __f.__week_mon_,
+ __use_sunday ? Sunday : Monday,
+ weekday{static_cast<unsigned>(__f.__weekday_)},
+ __date))
+ return false;
+
+ __result = year_month_day{__date};
+ return true;
+ }
+ }
+
+ // ISO week date: %G (or expanded %g), %V, and a weekday (%a/%A/%u/%w).
+ if (__f.__has(__fields_set::__iso_week | __fields_set::__weekday) &&
+ __f.__has_any(__fields_set::__iso_year | __fields_set::__iso_year_of_century)) {
+ // Use %G when supplied; expand %g only if needed to construct the date.
+ int64_t __iso_year{};
+ if (!__f.__has(__fields_set::__iso_year)) {
+ int __century = __f.__iso_year_of_century_ <= 68 ? 20 : 19;
+ if (__f.__has(__fields_set::__century))
+ __century = __f.__century_;
+ else if (__f.__has(__fields_set::__year))
+ __century = __f.__year_ / 100 - (__f.__year_ % 100 < 0);
+ else if (__f.__has(__fields_set::__year_of_century))
+ __century = __f.__year_of_century_ <= 68 ? 20 : 19;
+ __iso_year = __compose_year(__century, __f.__iso_year_of_century_);
+ } else {
+ __iso_year = __f.__iso_year_;
+ }
+ sys_days __date{};
+ if (!__in_range(__f.__weekday_, 0, 6) ||
+ !__iso_week_to_sys_days(__iso_year, __f.__iso_week_, weekday{static_cast<unsigned>(__f.__weekday_)}, __date))
+ return false;
+
+ __result = year_month_day{__date};
+ return true;
+ }
+
+ return false;
+}
+
+// Compare all parsed date fields with a valid date, including fields that do
+// not form a complete representation on their own, e.g. %F followed by only %G.
+_LIBCPP_HIDE_FROM_ABI inline bool __validate_date(const __fields_storage& __f, const year_month_day& __ymd) {
+ auto __matches = [&](__fields_set __field_bit, int __parsed, int __expected) {
+ return !__f.__has(__field_bit) || __parsed == __expected;
+ };
+
+ // Calendar year: %Y, %C, and %y, including a standalone %C.
+ const auto __year = static_cast<int>(__ymd.year());
+ if (!__matches(__fields_set::__year, __f.__year_, __year) ||
+ !__matches(__fields_set::__century, __f.__century_, __year / 100 - (__year % 100 < 0)) ||
+ !__matches(__fields_set::__year_of_century, __f.__year_of_century_, (__year < 0 ? -__year : __year) % 100))
+ return false;
+
+ // Without %C or %Y, %y denotes a year in [1969, 2068], not just matching last digits.
+ if (__f.__has(__fields_set::__year_of_century) && !__f.__has_any(__fields_set::__century | __fields_set::__year) &&
+ !__in_range(__year, 1969, 2068))
+ return false;
+
+ // Month (%m/%b/%B/%h), day (%d/%e), and weekday (%a/%A/%u/%w).
+ const sys_days __date{__ymd};
+ const weekday __weekday{__date};
+ if (!__matches(__fields_set::__month, __f.__month_, static_cast<unsigned>(__ymd.month())) ||
+ !__matches(__fields_set::__day, __f.__day_, static_cast<unsigned>(__ymd.day())) ||
+ !__matches(__fields_set::__weekday, __f.__weekday_, __weekday.c_encoding()))
+ return false;
+
+ // Day of year (%j) and Sunday-/Monday-based week numbers (%U/%W).
+ const sys_days __jan1{__ymd.year() / January / 1};
+ const int __day_of_year = static_cast<int>((__date - __jan1).count()) + 1;
+ if (!__matches(__fields_set::__day_of_year, __f.__day_of_year_, __day_of_year) ||
+ !__matches(__fields_set::__week_sun,
+ __f.__week_sun_,
+ (__day_of_year - static_cast<int>(__weekday.c_encoding()) + 6) / 7) ||
+ !__matches(__fields_set::__week_mon,
+ __f.__week_mon_,
+ (__day_of_year - (static_cast<int>(__weekday.iso_encoding()) - 1) + 6) / 7))
+ return false;
+
+ if (__f.__has_any(__fields_set::__iso_year | __fields_set::__iso_year_of_century | __fields_set::__iso_week)) {
+ // Find the Thursday of the week containing __date.
+ // Its calendar year is the ISO year: adjust __iso_year and __iso_jan1
+ // if that Thursday falls in the previous or next calendar year.
+ // Compute the ISO week number from that Thursday's distance from __iso_jan1,
+ // then compare the ISO year and week with any supplied %G/%g and %V fields.
+ // Keep __iso_year as int because it can exceed chrono::year's valid range
+ // near year::min()/max().
+ const sys_days __thursday = __date + days{4 - static_cast<int>(__weekday.iso_encoding())};
+ const sys_days __next_jan1 = __jan1 + days{__ymd.year().is_leap() ? 366 : 365};
+ int __iso_year = __year;
+ sys_days __iso_jan1 = __jan1;
+ if (__thursday < __jan1) {
+ // This week's Thursday falls in the previous calendar year, so the ISO year is __year - 1.
+ --__iso_year;
+ const bool __is_leap = __iso_year % 4 == 0 && (__iso_year % 100 != 0 || __iso_year % 400 == 0);
+ __iso_jan1 -= days{__is_leap ? 366 : 365};
+ } else if (__thursday >= __next_jan1) {
+ ++__iso_year;
+ __iso_jan1 = __next_jan1;
+ }
+ const int __iso_week = static_cast<int>((__thursday - __iso_jan1).count()) / 7 + 1;
+ if (!__matches(__fields_set::__iso_year, __f.__iso_year_, __iso_year) ||
+ !__matches(__fields_set::__iso_year_of_century,
+ __f.__iso_year_of_century_,
+ (__iso_year < 0 ? -__iso_year : __iso_year) % 100) ||
+ !__matches(__fields_set::__iso_week, __f.__iso_week_, __iso_week))
+ return false;
+ }
+
+ return true;
+}
+
+_LIBCPP_HIDE_FROM_ABI inline bool __try_get_date(const __fields_storage& __f, sys_days& __result) {
+ // First construct a valid candidate, e.g. from %F/%x, %Y %j, or %G %V %u.
+ year_month_day __ymd{};
+ if (!__make_date(__f, __ymd))
+ return false;
+
+ // Then check all parsed date fields, including incomplete representations
+ // such as a standalone %G or %V alongside %F.
+ if (!__validate_date(__f, __ymd))
+ return false;
+
+ __result = sys_days{__ymd};
+ return true;
+}
+
+// Validate and combine %H, %I, and %p, storing the hour only on success.
+_LIBCPP_HIDE_FROM_ABI inline bool __try_get_hour(const __fields_storage& __f, int& __result) {
+ int __hour{};
+ if (__f.__has(__fields_set::__hours)) {
+ // %H determines the hour; check agreement with %I and %p if supplied.
+ __hour = __f.__hours_;
+ if (!__in_range(__hour, 0, 23))
+ return false;
+ if (__f.__has(__fields_set::__hour12)) {
+ const int __hour12 = static_cast<int>(chrono::make12(hours{__hour}).count());
+ if (__f.__hour12_ != __hour12)
+ return false;
+ }
+ if (__f.__has(__fields_set::__am_pm) && (__hour >= 12) != __f.__is_pm_)
+ return false;
+ } else if (__f.__has(__fields_set::__hour12)) {
+ // Without %H, %I requires %p to distinguish AM from PM.
+ if (!__in_range(__f.__hour12_, 1, 12) || !__f.__has(__fields_set::__am_pm))
+ return false;
+ __hour = static_cast<int>(chrono::make24(hours{__f.__hour12_}, __f.__is_pm_).count());
+ } else {
+ // Default to zero when neither hour field was supplied.
+ __hour = 0;
+ }
+
+ __result = __hour;
+ return true;
+}
+
+_LIBCPP_HIDE_FROM_ABI inline bool __validate_minute(const __fields_storage& __f) {
+ return !__f.__has(__fields_set::__minutes) || __in_range(__f.__minutes_, 0, 59);
+}
+
+_LIBCPP_HIDE_FROM_ABI inline bool __validate_second(const __fields_storage& __f, int __max_second) {
+ return (!__f.__has(__fields_set::__seconds) || __in_range(__f.__seconds_, 0, __max_second)) && __f.__subseconds_ >= 0;
+}
+
+// Validate the time-of-day fields and combine their whole seconds only on success.
+_LIBCPP_HIDE_FROM_ABI inline bool
+__try_get_time_of_day(const __fields_storage& __f, seconds& __result, int __max_second = 59) {
+ int __hour{};
+ if (!__try_get_hour(__f, __hour) || !__validate_minute(__f) || !__validate_second(__f, __max_second))
+ return false;
+
+ __result = hours{__hour} + minutes{__f.__minutes_} + seconds{__f.__seconds_};
+ return true;
+}
+
+template <class _Duration, class _Unit>
+_LIBCPP_HIDE_FROM_ABI constexpr bool __can_represent() {
+ return treat_as_floating_point_v<typename _Duration::rep> ||
+ ratio_less_equal_v<typename _Duration::period, typename _Unit::period>;
+}
+
+// Reject fields whose units are finer than the target precision.
+template <class _Duration>
+_LIBCPP_HIDE_FROM_ABI bool __validate_time_precision(const __fields_storage& __f) {
+ if constexpr (!chrono::__can_represent<_Duration, days>())
+ return false;
+
+ if constexpr (!chrono::__can_represent<_Duration, hours>()) {
+ if (__f.__has_any(__fields_set::__hours | __fields_set::__hour12 | __fields_set::__am_pm))
+ return false;
+ }
+
+ if constexpr (!chrono::__can_represent<_Duration, minutes>()) {
+ if (__f.__has(__fields_set::__minutes))
+ return false;
+ }
+
+ if constexpr (!chrono::__can_represent<_Duration, seconds>()) {
+ if (__f.__has(__fields_set::__seconds))
+ return false;
+ }
+
+ return true;
+}
+
+// Convert whole seconds and a nonnegative attosecond fraction together.
+// Overflow in duration conversion and accumulation is not checked.
+template <class _Duration>
+_LIBCPP_HIDE_FROM_ABI _Duration __make_duration(int64_t __seconds, int64_t __subseconds) {
+ if constexpr (treat_as_floating_point_v<typename _Duration::rep>) {
+ constexpr auto __scale = chrono::__pow10(__fields_storage::__subsecond_precision);
+ const duration<long double> __value{
+ static_cast<long double>(__seconds) + static_cast<long double>(__subseconds) / __scale};
+ return chrono::duration_cast<_Duration>(__value);
+ } else {
+ using _HMS = hh_mm_ss<_Duration>;
+ using _Precision = typename _HMS::precision;
+ _Precision __value = chrono::duration_cast<_Precision>(seconds{__seconds});
+ __value += _Precision{static_cast<typename _Precision::rep>(
+ __subseconds / chrono::__pow10(__fields_storage::__subsecond_precision - _HMS::fractional_width))};
+ return chrono::duration_cast<_Duration>(__value);
+ }
+}
+
+// Builders validate parsed fields and convert them to the requested type.
+
+template <class _Rep, class _Period>
+_LIBCPP_HIDE_FROM_ABI bool __from_fields(const __fields_storage& __f, duration<_Rep, _Period>& __result) {
+ if (!chrono::__validate_time_precision<duration<_Rep, _Period>>(__f))
+ return false;
+
+ // Durations can represent only elapsed days and time-of-day fields.
+ // UTC offsets and time zone abbreviations are allowed but do not contribute to the duration.
+ constexpr auto __duration_components =
+ __fields_set::__day_of_year | __fields_set::__hours | __fields_set::__hour12 | __fields_set::__minutes |
+ __fields_set::__seconds;
+ if (!__f.__has_only(__duration_components | __fields_set::__am_pm))
+ return false;
+
+ // Require at least one numeric component; %p alone is insufficient.
+ if (!__f.__has_any(__duration_components))
+ return false;
+
+ seconds __time_of_day{};
+ // Use clock-time ranges for %H, %M, and %S; %j supplies any additional days.
+ if (__f.__day_of_year_ < 0 || !__try_get_time_of_day(__f, __time_of_day))
+ return false;
+
+ // Sum the components in seconds; when parsing a duration, %j denotes a day count, not a day of the year.
+ const seconds __whole_seconds = chrono::duration_cast<seconds>(days{__f.__day_of_year_}) + __time_of_day;
+
+ __result = chrono::__make_duration<duration<_Rep, _Period>>(__whole_seconds.count(), __f.__subseconds_);
+ return true;
+}
+
+template <class _Duration>
+_LIBCPP_HIDE_FROM_ABI bool __from_fields(const __fields_storage& __f, sys_time<_Duration>& __result) {
+ if (!chrono::__validate_time_precision<_Duration>(__f))
+ return false;
+
+ sys_days __date{};
+ if (!__try_get_date(__f, __date))
+ return false;
+
+ // sys_time does not represent leap seconds, so seconds must be in [0, 59].
+ seconds __time_of_day{};
+ if (!__try_get_time_of_day(__f, __time_of_day))
+ return false;
+
+ // %z gives the offset of the parsed time from UTC, so it is subtracted to
+ // arrive at the UTC time sys_time holds. It is zero when %z was not used.
+ // Combine whole seconds before converting to avoid overflowing conversion
+ // ratios from days or minutes to fine target precisions.
+ const seconds __whole_seconds = __date.time_since_epoch() + __time_of_day - minutes{__f.__utc_offset_};
+ using _Precision = common_type_t<_Duration, seconds>;
+ __result = sys_time<_Duration>{
+ chrono::floor<_Duration>(chrono::__make_duration<_Precision>(__whole_seconds.count(), __f.__subseconds_))};
+ return true;
+}
+
+// A parsed UTC offset is not applied to local_time.
+template <class _Duration>
+_LIBCPP_HIDE_FROM_ABI bool __from_fields(const __fields_storage& __f, local_time<_Duration>& __result) {
+ if (!chrono::__validate_time_precision<_Duration>(__f))
+ return false;
+
+ sys_days __date{};
+ if (!__try_get_date(__f, __date))
+ return false;
+
+ seconds __time_of_day{};
+ if (!__try_get_time_of_day(__f, __time_of_day))
+ return false;
+
+ // Combine whole seconds before converting to the target precision.
+ const seconds __whole_seconds = __date.time_since_epoch() + __time_of_day;
+ using _Precision = common_type_t<_Duration, seconds>;
+ __result = local_time<_Duration>{
+ chrono::floor<_Duration>(chrono::__make_duration<_Precision>(__whole_seconds.count(), __f.__subseconds_))};
+ return true;
+}
+
+template <class _Duration>
+_LIBCPP_HIDE_FROM_ABI bool __from_fields(const __fields_storage& __f, file_time<_Duration>& __result) {
+ sys_time<_Duration> __st{};
+ if (!chrono::__from_fields(__f, __st))
+ return false;
+
+ __result = file_clock::from_sys(__st);
+ return true;
+}
+
+# if _LIBCPP_HAS_EXPERIMENTAL_TZDB && _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
+template <class _Duration>
+_LIBCPP_HIDE_FROM_ABI bool __from_fields(const __fields_storage& __f, utc_time<_Duration>& __result) {
+ if (!chrono::__validate_time_precision<_Duration>(__f))
+ return false;
+
+ sys_days __date{};
+ if (!__try_get_date(__f, __date))
+ return false;
+
+ // utc_time can represent leap seconds, so the seconds field may be 60.
+ seconds __time_of_day{};
+ if (!__try_get_time_of_day(__f, __time_of_day, 60))
+ return false;
+
+ const sys_seconds __sys = __date + __time_of_day - minutes{__f.__utc_offset_};
+ utc_seconds __time;
+ if (__f.__seconds_ == 60) {
+ // Convert the preceding second, then advance into the possible leap second.
+ // This also handles offsets that put the leap second on a different local date.
+ __time = utc_clock::from_sys(__sys - seconds{1}) + seconds{1};
+ if (!chrono::get_leap_second_info(__time).is_leap_second)
+ return false;
+ } else {
+ // Reject the UTC second removed by a negative leap second.
+ for (const auto& __leap : chrono::get_tzdb().leap_seconds) {
+ if (__sys < __leap.date()) {
+ if (__leap.value() < seconds{0} && __sys >= __leap.date() + __leap.value())
+ return false;
+ break;
+ }
+ }
+ __time = utc_clock::from_sys(__sys);
+ }
+ using _Precision = common_type_t<_Duration, seconds>;
+ __result = utc_time<_Duration>{chrono::floor<_Duration>(
+ chrono::__make_duration<_Precision>(__time.time_since_epoch().count(), __f.__subseconds_))};
+ return true;
+}
+
+template <class _Duration>
+_LIBCPP_HIDE_FROM_ABI bool __from_fields(const __fields_storage& __f, tai_time<_Duration>& __result) {
+ if (!chrono::__validate_time_precision<_Duration>(__f))
+ return false;
+
+ sys_days __date{};
+ if (!__try_get_date(__f, __date))
+ return false;
+
+ seconds __time_of_day{};
+ if (!__try_get_time_of_day(__f, __time_of_day))
+ return false;
+
+ constexpr sys_days __tai_epoch{-days{4383}}; // 1958-01-01.
+ const seconds __whole_seconds = __date - __tai_epoch + __time_of_day - minutes{__f.__utc_offset_};
+ using _Precision = common_type_t<_Duration, seconds>;
+ __result = tai_time<_Duration>{
+ chrono::floor<_Duration>(chrono::__make_duration<_Precision>(__whole_seconds.count(), __f.__subseconds_))};
+ return true;
+}
+
+template <class _Duration>
+_LIBCPP_HIDE_FROM_ABI bool __from_fields(const __fields_storage& __f, gps_time<_Duration>& __result) {
+ if (!chrono::__validate_time_precision<_Duration>(__f))
+ return false;
+
+ sys_days __date{};
+ if (!__try_get_date(__f, __date))
+ return false;
+
+ seconds __time_of_day{};
+ if (!__try_get_time_of_day(__f, __time_of_day))
+ return false;
+
+ constexpr sys_days __gps_epoch{days{3657}}; // 1980-01-06.
+ const seconds __whole_seconds = __date - __gps_epoch + __time_of_day - minutes{__f.__utc_offset_};
+ using _Precision = common_type_t<_Duration, seconds>;
+ __result = gps_time<_Duration>{
+ chrono::floor<_Duration>(chrono::__make_duration<_Precision>(__whole_seconds.count(), __f.__subseconds_))};
+ return true;
+}
+# endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB && _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
+
+// Calendrical results reject fields they cannot represent. UTC offsets are
+// excluded from these checks; time zone abbreviations are stored separately.
+_LIBCPP_HIDE_FROM_ABI inline bool __from_fields(const __fields_storage& __f, day& __result) {
+ if (!__f.__has_exactly(__fields_set::__day))
+ return false;
+
+ if (!__in_range(__f.__day_, 1, 31))
+ return false;
+
+ __result = day{static_cast<unsigned>(__f.__day_)};
+ return true;
+}
+
+_LIBCPP_HIDE_FROM_ABI inline bool __from_fields(const __fields_storage& __f, month& __result) {
+ if (!__f.__has_exactly(__fields_set::__month))
+ return false;
+
+ if (!__in_range(__f.__month_, 1, 12))
+ return false;
+
+ __result = month{static_cast<unsigned>(__f.__month_)};
+ return true;
+}
+
+_LIBCPP_HIDE_FROM_ABI inline bool __from_fields(const __fields_storage& __f, year& __result) {
+ constexpr auto __year_fields = __fields_set::__year | __fields_set::__century | __fields_set::__year_of_century;
+ if (!__f.__has_only(__year_fields))
+ return false;
+
+ int __year{};
+ if (!__try_get_year(__f, __year))
+ return false;
+
+ __result = year{__year};
+ return true;
+}
+
+_LIBCPP_HIDE_FROM_ABI inline bool __from_fields(const __fields_storage& __f, weekday& __result) {
+ if (!__f.__has_exactly(__fields_set::__weekday))
+ return false;
+
+ if (!__in_range(__f.__weekday_, 0, 6))
+ return false;
+
+ __result = weekday{static_cast<unsigned>(__f.__weekday_)};
+ return true;
+}
+
+_LIBCPP_HIDE_FROM_ABI inline bool __from_fields(const __fields_storage& __f, month_day& __result) {
+ if (!__f.__has_only(__fields_set::__month | __fields_set::__day | __fields_set::__day_of_year))
+ return false;
+
+ int __month{};
+ int __day{};
+ if (__f.__has(__fields_set::__day_of_year)) {
+ // Without a year, only days through February 28 are unambiguous.
+ if (!__in_range(__f.__day_of_year_, 1, 59))
+ return false;
+ __month = __f.__day_of_year_ <= 31 ? 1 : 2;
+ __day = __f.__day_of_year_ <= 31 ? __f.__day_of_year_ : __f.__day_of_year_ - 31;
+ if ((__f.__has(__fields_set::__month) && __f.__month_ != __month) ||
+ (__f.__has(__fields_set::__day) && __f.__day_ != __day))
+ return false;
+ } else {
+ if (!__f.__has(__fields_set::__month | __fields_set::__day))
+ return false;
+ __month = __f.__month_;
+ __day = __f.__day_;
+ }
+
+ if (!__in_range(__month, 1, 12) || !__in_range(__day, 1, 31))
+ return false;
+
+ month_day __md{month{static_cast<unsigned>(__month)}, day{static_cast<unsigned>(__day)}};
+ if (!__md.ok())
+ return false;
+
+ __result = __md;
+ return true;
+}
+
+_LIBCPP_HIDE_FROM_ABI inline bool __from_fields(const __fields_storage& __f, year_month& __result) {
+ constexpr auto __year_fields = __fields_set::__year | __fields_set::__century | __fields_set::__year_of_century;
+ if (!__f.__has_exactly(__fields_set::__month, __year_fields))
+ return false;
+
+ int __year{};
+ if (!__try_get_year(__f, __year))
+ return false;
+
+ if (!__in_range(__f.__month_, 1, 12))
+ return false;
+
+ __result = year_month{year{__year}, month{static_cast<unsigned>(__f.__month_)}};
+ return true;
+}
+
+_LIBCPP_HIDE_FROM_ABI inline bool __from_fields(const __fields_storage& __f, year_month_day& __result) {
+ constexpr auto __date_fields =
+ __fields_set::__year | __fields_set::__century | __fields_set::__year_of_century | __fields_set::__month |
+ __fields_set::__day | __fields_set::__iso_year | __fields_set::__iso_year_of_century | __fields_set::__iso_week |
+ __fields_set::__weekday | __fields_set::__day_of_year | __fields_set::__week_sun | __fields_set::__week_mon;
+ if (!__f.__has_only(__date_fields))
+ return false;
+
+ // Resolve the date and check that all supplied date fields agree.
+ sys_days __date{};
+ if (!__try_get_date(__f, __date))
+ return false;
+
+ __result = year_month_day{__date};
+ return true;
+}
+
+template <class _Tp, class _CharT, class _Traits, class _Alloc>
+_LIBCPP_HIDE_FROM_ABI basic_istream<_CharT, _Traits>&
+__from_stream(basic_istream<_CharT, _Traits>& __is,
+ const _CharT* __fmt,
+ _Tp& __value,
+ basic_string<_CharT, _Traits, _Alloc>* __abbrev,
+ minutes* __offset) {
+ ios_base::iostate __state = ios_base::goodbit;
+ constexpr bool __noskipws = true;
+ typename basic_istream<_CharT, _Traits>::sentry __s{__is, __noskipws};
+
+ if (__s) {
+# if _LIBCPP_HAS_EXCEPTIONS
+ try {
+# endif
+ __fields_storage __f{};
+ basic_string<_CharT, _Traits> __parsed_abbrev;
+ constexpr auto __options = chrono::__get_parse_options<_Tp>();
+
+ // Parse the input according to the format and collect the fields.
+ __from_stream_parser<_CharT, _Traits> __parser{__is, __state};
+ __parser.__parse(__fmt, __f, __parsed_abbrev, __options);
+ if (!__parser.__fail()) {
+ if (__abbrev && !__parsed_abbrev.empty())
+ __abbrev->assign(__parsed_abbrev.data(), __parsed_abbrev.size());
+ if (__offset && __f.__has(__fields_set::__utc_offset))
+ *__offset = minutes{__f.__utc_offset_};
+
+ // Resolve and validate the fields needed to construct the requested result.
+ _Tp __result{};
+ if (!chrono::__from_fields(__f, __result)) {
+ __state |= ios_base::failbit;
+ } else {
+ __value = __result;
+ }
+ }
+# if _LIBCPP_HAS_EXCEPTIONS
+ } catch (...) {
+ __state |= ios_base::badbit;
+ __is.__setstate_nothrow(__state);
+ if (__is.exceptions() & ios_base::badbit)
+ throw;
+ }
+# endif
+ // Keep exceptions requested for failbit or eofbit outside the input handler.
+ __is.setstate(__state);
+ }
+
+ return __is;
+}
+
+template <class _CharT, class _Traits, class _Rep, class _Period, class _Alloc = allocator<_CharT>>
+_LIBCPP_HIDE_FROM_ABI 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) {
+ return chrono::__from_stream(__is, __fmt, __d, __abbrev, __offset);
+}
+
+template <class _CharT, class _Traits, class _Duration, class _Alloc = allocator<_CharT>>
+_LIBCPP_HIDE_FROM_ABI 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) {
+ return chrono::__from_stream(__is, __fmt, __tp, __abbrev, __offset);
+}
+
+template <class _CharT, class _Traits, class _Duration, class _Alloc = allocator<_CharT>>
+_LIBCPP_HIDE_FROM_ABI 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) {
+ return chrono::__from_stream(__is, __fmt, __tp, __abbrev, __offset);
+}
+
+template <class _CharT, class _Traits, class _Duration, class _Alloc = allocator<_CharT>>
+_LIBCPP_HIDE_FROM_ABI 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) {
+ return chrono::__from_stream(__is, __fmt, __tp, __abbrev, __offset);
+}
+
+# if _LIBCPP_HAS_EXPERIMENTAL_TZDB && _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
+template <class _CharT, class _Traits, class _Duration, class _Alloc = allocator<_CharT>>
+_LIBCPP_HIDE_FROM_ABI 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) {
+ return chrono::__from_stream(__is, __fmt, __tp, __abbrev, __offset);
+}
+
+template <class _CharT, class _Traits, class _Duration, class _Alloc = allocator<_CharT>>
+_LIBCPP_HIDE_FROM_ABI 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) {
+ return chrono::__from_stream(__is, __fmt, __tp, __abbrev, __offset);
+}
+
+template <class _CharT, class _Traits, class _Duration, class _Alloc = allocator<_CharT>>
+_LIBCPP_HIDE_FROM_ABI 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) {
+ return chrono::__from_stream(__is, __fmt, __tp, __abbrev, __offset);
+}
+# endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB && _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM
+
+template <class _CharT, class _Traits, class _Alloc = allocator<_CharT>>
+_LIBCPP_HIDE_FROM_ABI 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) {
+ return chrono::__from_stream(__is, __fmt, __d, __abbrev, __offset);
+}
+
+template <class _CharT, class _Traits, class _Alloc = allocator<_CharT>>
+_LIBCPP_HIDE_FROM_ABI 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) {
+ return chrono::__from_stream(__is, __fmt, __m, __abbrev, __offset);
+}
+
+template <class _CharT, class _Traits, class _Alloc = allocator<_CharT>>
+_LIBCPP_HIDE_FROM_ABI 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) {
+ return chrono::__from_stream(__is, __fmt, __y, __abbrev, __offset);
+}
+
+template <class _CharT, class _Traits, class _Alloc = allocator<_CharT>>
+_LIBCPP_HIDE_FROM_ABI 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) {
+ return chrono::__from_stream(__is, __fmt, __wd, __abbrev, __offset);
+}
+
+template <class _CharT, class _Traits, class _Alloc = allocator<_CharT>>
+_LIBCPP_HIDE_FROM_ABI 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) {
+ return chrono::__from_stream(__is, __fmt, __md, __abbrev, __offset);
+}
+
+template <class _CharT, class _Traits, class _Alloc = allocator<_CharT>>
+_LIBCPP_HIDE_FROM_ABI 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) {
+ return chrono::__from_stream(__is, __fmt, __ym, __abbrev, __offset);
+}
+
+template <class _CharT, class _Traits, class _Alloc = allocator<_CharT>>
+_LIBCPP_HIDE_FROM_ABI 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) {
+ return chrono::__from_stream(__is, __fmt, __ymd, __abbrev, __offset);
+}
+
+} // namespace chrono
+
+_LIBCPP_END_NAMESPACE_STD
+
+# endif
+
+_LIBCPP_POP_MACROS
+
+#endif
+
+#endif //_LIBCPP___CHRONO_FROM_STREAM_H
diff --git a/libcxx/include/__chrono/parse.h b/libcxx/include/__chrono/parse.h
new file mode 100644
index 00000000000000..55831d2c68a6f4
--- /dev/null
+++ b/libcxx/include/__chrono/parse.h
@@ -0,0 +1,226 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___CHRONO_PARSE_H
+#define _LIBCPP___CHRONO_PARSE_H
+
+#include <__config>
+
+#if _LIBCPP_HAS_LOCALIZATION
+
+# include <__chrono/duration.h>
+# include <__chrono/from_stream.h>
+# include <__fwd/istream.h>
+# include <__fwd/memory.h>
+# include <__fwd/string.h>
+# include <__memory/addressof.h>
+
+# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+# pragma GCC system_header
+# endif
+
+# if _LIBCPP_STD_VER >= 20
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+namespace chrono {
+
+namespace __parse_details {
+
+// Block ordinary lookup so from_stream is found only through ADL.
+void from_stream() = delete;
+
+// parse() stores the format and output references in a manipulator.
+// operator>> supplies the input stream and calls the matching from_stream overload.
+// Separate manipulator types preserve the four from_stream argument lists.
+// _Format preserves the value category of fmt (lvalue) or fmt.c_str() (prvalue).
+
+template <class _CharT, class _Traits, class _Parsable, class _Format>
+struct __parse_manip {
+ const _CharT* __fmt_;
+ _Parsable* __tp_;
+
+ _LIBCPP_HIDE_FROM_ABI __parse_manip(const _CharT* __fmt, _Parsable* __tp) : __fmt_(__fmt), __tp_(__tp) {}
+
+ __parse_manip(const __parse_manip&) = delete;
+ __parse_manip& operator=(const __parse_manip&) = delete;
+
+ _LIBCPP_HIDE_FROM_ABI friend basic_istream<_CharT, _Traits>&
+ operator>>(basic_istream<_CharT, _Traits>& __is, __parse_manip&& __manip) {
+ // NOLINTNEXTLINE(libcpp-robust-against-adl) [time.parse] requires ADL.
+ from_stream(__is, static_cast<_Format>(__manip.__fmt_), *__manip.__tp_);
+ return __is;
+ }
+};
+
+template <class _CharT, class _Traits, class _Parsable, class _Format, class _Alloc = allocator<_CharT>>
+struct __parse_manip_offset {
+ const _CharT* __fmt_;
+ _Parsable* __tp_;
+ minutes* __offset_;
+
+ _LIBCPP_HIDE_FROM_ABI __parse_manip_offset(const _CharT* __fmt, _Parsable* __tp, minutes* __offset)
+ : __fmt_(__fmt), __tp_(__tp), __offset_(__offset) {}
+
+ __parse_manip_offset(const __parse_manip_offset&) = delete;
+ __parse_manip_offset& operator=(const __parse_manip_offset&) = delete;
+
+ _LIBCPP_HIDE_FROM_ABI friend basic_istream<_CharT, _Traits>&
+ operator>>(basic_istream<_CharT, _Traits>& __is, __parse_manip_offset&& __manip) {
+ // NOLINTNEXTLINE(libcpp-robust-against-adl) [time.parse] requires ADL.
+ from_stream(__is,
+ static_cast<_Format>(__manip.__fmt_),
+ *__manip.__tp_,
+ static_cast<basic_string<_CharT, _Traits, _Alloc>*>(nullptr),
+ static_cast<minutes*>(__manip.__offset_));
+ return __is;
+ }
+};
+
+template <class _CharT, class _Traits, class _Alloc, class _Parsable, class _Format>
+struct __parse_manip_abbrev {
+ const _CharT* __fmt_;
+ _Parsable* __tp_;
+ basic_string<_CharT, _Traits, _Alloc>* __abbrev_;
+
+ _LIBCPP_HIDE_FROM_ABI
+ __parse_manip_abbrev(const _CharT* __fmt, _Parsable* __tp, basic_string<_CharT, _Traits, _Alloc>* __abbrev)
+ : __fmt_(__fmt), __tp_(__tp), __abbrev_(__abbrev) {}
+
+ __parse_manip_abbrev(const __parse_manip_abbrev&) = delete;
+ __parse_manip_abbrev& operator=(const __parse_manip_abbrev&) = delete;
+
+ _LIBCPP_HIDE_FROM_ABI friend basic_istream<_CharT, _Traits>&
+ operator>>(basic_istream<_CharT, _Traits>& __is, __parse_manip_abbrev&& __manip) {
+ // NOLINTNEXTLINE(libcpp-robust-against-adl) [time.parse] requires ADL.
+ from_stream(__is,
+ static_cast<_Format>(__manip.__fmt_),
+ *__manip.__tp_,
+ static_cast<basic_string<_CharT, _Traits, _Alloc>*>(__manip.__abbrev_));
+ return __is;
+ }
+};
+
+template <class _CharT, class _Traits, class _Alloc, class _Parsable, class _Format>
+struct __parse_manip_abbrev_offset {
+ const _CharT* __fmt_;
+ _Parsable* __tp_;
+ basic_string<_CharT, _Traits, _Alloc>* __abbrev_;
+ minutes* __offset_;
+
+ _LIBCPP_HIDE_FROM_ABI __parse_manip_abbrev_offset(
+ const _CharT* __fmt, _Parsable* __tp, basic_string<_CharT, _Traits, _Alloc>* __abbrev, minutes* __offset)
+ : __fmt_(__fmt), __tp_(__tp), __abbrev_(__abbrev), __offset_(__offset) {}
+
+ __parse_manip_abbrev_offset(const __parse_manip_abbrev_offset&) = delete;
+ __parse_manip_abbrev_offset& operator=(const __parse_manip_abbrev_offset&) = delete;
+
+ _LIBCPP_HIDE_FROM_ABI friend basic_istream<_CharT, _Traits>&
+ operator>>(basic_istream<_CharT, _Traits>& __is, __parse_manip_abbrev_offset&& __manip) {
+ // NOLINTNEXTLINE(libcpp-robust-against-adl) [time.parse] requires ADL.
+ from_stream(__is,
+ static_cast<_Format>(__manip.__fmt_),
+ *__manip.__tp_,
+ static_cast<basic_string<_CharT, _Traits, _Alloc>*>(__manip.__abbrev_),
+ static_cast<minutes*>(__manip.__offset_));
+ return __is;
+ }
+};
+
+// [time.parse]: a Parsable is anything from_stream can read, with the trailing
+// arguments the selected parse overload passes on. The call is unqualified, so
+// a user-defined type that provides its own from_stream is parsable as well.
+// _Format and _Args model the types and value categories of the format and trailing arguments.
+template <class _Parsable, class _CharT, class _Traits, class _Format, class... _Args>
+concept __parsable =
+ requires(basic_istream<_CharT, _Traits>& __is, const _CharT* __fmt, _Parsable& __tp, _Args... __args) {
+ // NOLINTNEXTLINE(libcpp-robust-against-adl) [time.parse] requires ADL.
+ from_stream(__is, static_cast<_Format>(__fmt), __tp, static_cast<_Args>(__args)...);
+ };
+
+} // namespace __parse_details
+
+template <class _CharT, class _Parsable>
+ requires __parse_details::__parsable<_Parsable, _CharT, char_traits<_CharT>, const _CharT*&>
+_LIBCPP_HIDE_FROM_ABI __parse_details::__parse_manip<_CharT, char_traits<_CharT>, _Parsable, const _CharT*&>
+parse(const _CharT* __fmt, _Parsable& __tp) {
+ return {__fmt, std::addressof(__tp)};
+}
+
+template <class _CharT, class _Traits, class _Alloc, class _Parsable>
+ requires __parse_details::__parsable<_Parsable, _CharT, _Traits, const _CharT*>
+_LIBCPP_HIDE_FROM_ABI __parse_details::__parse_manip<_CharT, _Traits, _Parsable, const _CharT*>
+parse(const basic_string<_CharT, _Traits, _Alloc>& __fmt, _Parsable& __tp) {
+ return {__fmt.c_str(), std::addressof(__tp)};
+}
+
+template <class _CharT, class _Traits, class _Alloc, class _Parsable>
+ requires __parse_details::
+ __parsable<_Parsable, _CharT, _Traits, const _CharT*&, basic_string<_CharT, _Traits, _Alloc>*>
+ _LIBCPP_HIDE_FROM_ABI __parse_details::__parse_manip_abbrev<_CharT, _Traits, _Alloc, _Parsable, const _CharT*&>
+ parse(const _CharT* __fmt, _Parsable& __tp, basic_string<_CharT, _Traits, _Alloc>& __abbrev) {
+ return {__fmt, std::addressof(__tp), std::addressof(__abbrev)};
+}
+
+template <class _CharT, class _Traits, class _Alloc, class _Parsable>
+ requires __parse_details::
+ __parsable<_Parsable, _CharT, _Traits, const _CharT*, basic_string<_CharT, _Traits, _Alloc>*>
+ _LIBCPP_HIDE_FROM_ABI __parse_details::__parse_manip_abbrev<_CharT, _Traits, _Alloc, _Parsable, const _CharT*>
+ parse(const basic_string<_CharT, _Traits, _Alloc>& __fmt,
+ _Parsable& __tp,
+ basic_string<_CharT, _Traits, _Alloc>& __abbrev) {
+ return {__fmt.c_str(), std::addressof(__tp), std::addressof(__abbrev)};
+}
+
+template <class _CharT, class _Parsable>
+ requires __parse_details::
+ __parsable<_Parsable, _CharT, char_traits<_CharT>, const _CharT*&, basic_string<_CharT>*, minutes*>
+ _LIBCPP_HIDE_FROM_ABI __parse_details::__parse_manip_offset<_CharT, char_traits<_CharT>, _Parsable, const _CharT*&>
+ parse(const _CharT* __fmt, _Parsable& __tp, minutes& __offset) {
+ return {__fmt, std::addressof(__tp), std::addressof(__offset)};
+}
+
+template <class _CharT, class _Traits, class _Alloc, class _Parsable>
+ requires __parse_details::
+ __parsable<_Parsable, _CharT, _Traits, const _CharT*, basic_string<_CharT, _Traits, _Alloc>*, minutes*>
+ _LIBCPP_HIDE_FROM_ABI __parse_details::__parse_manip_offset<_CharT, _Traits, _Parsable, const _CharT*, _Alloc>
+ parse(const basic_string<_CharT, _Traits, _Alloc>& __fmt, _Parsable& __tp, minutes& __offset) {
+ return {__fmt.c_str(), std::addressof(__tp), std::addressof(__offset)};
+}
+
+template <class _CharT, class _Traits, class _Alloc, class _Parsable>
+ requires __parse_details::
+ __parsable<_Parsable, _CharT, _Traits, const _CharT*&, basic_string<_CharT, _Traits, _Alloc>*, minutes*>
+ _LIBCPP_HIDE_FROM_ABI
+ __parse_details::__parse_manip_abbrev_offset<_CharT, _Traits, _Alloc, _Parsable, const _CharT*&>
+ parse(const _CharT* __fmt, _Parsable& __tp, basic_string<_CharT, _Traits, _Alloc>& __abbrev, minutes& __offset) {
+ return {__fmt, std::addressof(__tp), std::addressof(__abbrev), std::addressof(__offset)};
+}
+
+template <class _CharT, class _Traits, class _Alloc, class _Parsable>
+ requires __parse_details::
+ __parsable<_Parsable, _CharT, _Traits, const _CharT*, basic_string<_CharT, _Traits, _Alloc>*, minutes*>
+ _LIBCPP_HIDE_FROM_ABI
+ __parse_details::__parse_manip_abbrev_offset<_CharT, _Traits, _Alloc, _Parsable, const _CharT*>
+ parse(const basic_string<_CharT, _Traits, _Alloc>& __fmt,
+ _Parsable& __tp,
+ basic_string<_CharT, _Traits, _Alloc>& __abbrev,
+ minutes& __offset) {
+ return {__fmt.c_str(), std::addressof(__tp), std::addressof(__abbrev), std::addressof(__offset)};
+}
+
+} // namespace chrono
+
+_LIBCPP_END_NAMESPACE_STD
+
+# endif
+
+#endif
+
+#endif //_LIBCPP___CHRONO_PARSE_H
diff --git a/libcxx/include/__chrono/parser_data.h b/libcxx/include/__chrono/parser_data.h
new file mode 100644
index 00000000000000..fda046ca3752c6
--- /dev/null
+++ b/libcxx/include/__chrono/parser_data.h
@@ -0,0 +1,146 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___CHRONO_PARSER_DATA_H
+#define _LIBCPP___CHRONO_PARSER_DATA_H
+
+#include <__config>
+
+#if _LIBCPP_HAS_LOCALIZATION
+
+# include <cstdint>
+
+# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+# pragma GCC system_header
+# endif
+
+# if _LIBCPP_STD_VER >= 20
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+namespace chrono {
+
+// Fields supplied by the parsed format directives.
+enum class __fields_set : uint32_t {
+ __none = 0,
+ __year = 1 << 0,
+ __month = 1 << 1,
+ __day = 1 << 2,
+ __hours = 1 << 3,
+ __minutes = 1 << 4,
+ __seconds = 1 << 5,
+
+ __iso_year = 1 << 6,
+ __iso_week = 1 << 7,
+ __weekday = 1 << 8,
+
+ __century = 1 << 9,
+ __year_of_century = 1 << 10,
+
+ __hour12 = 1 << 11,
+ __am_pm = 1 << 12,
+
+ __day_of_year = 1 << 13,
+ __week_sun = 1 << 14,
+ __week_mon = 1 << 15,
+
+ __utc_offset = 1 << 16,
+
+ __iso_year_of_century = 1 << 17,
+};
+
+_LIBCPP_HIDE_FROM_ABI inline constexpr __fields_set operator|(__fields_set __lhs, __fields_set __rhs) {
+ return static_cast<__fields_set>(static_cast<uint32_t>(__lhs) | static_cast<uint32_t>(__rhs));
+}
+
+_LIBCPP_HIDE_FROM_ABI inline constexpr __fields_set operator&(__fields_set __lhs, __fields_set __rhs) {
+ return static_cast<__fields_set>(static_cast<uint32_t>(__lhs) & static_cast<uint32_t>(__rhs));
+}
+
+_LIBCPP_HIDE_FROM_ABI inline constexpr __fields_set operator~(__fields_set __fields) {
+ return static_cast<__fields_set>(~static_cast<uint32_t>(__fields));
+}
+
+_LIBCPP_HIDE_FROM_ABI inline constexpr __fields_set& operator|=(__fields_set& __lhs, __fields_set __rhs) {
+ return __lhs = __lhs | __rhs;
+}
+
+// Fields collected while parsing. Date/time construction and validation do not
+// modify these values or their presence flags.
+struct __fields_storage {
+ int __year_ = 0;
+ int __month_ = 0;
+ int __day_ = 0;
+ // Time-of-day fields.
+ int __hours_ = 0;
+ int __minutes_ = 0;
+ int __seconds_ = 0;
+ // Fractional seconds are stored as an integer number of attoseconds.
+ static constexpr unsigned __subsecond_precision = 18;
+ int64_t __subseconds_ = 0;
+
+ // __weekday_ uses [0, 6], with Sunday == 0.
+ int __iso_year_ = 0;
+ int __iso_year_of_century_ = 0;
+ int __iso_week_ = 0;
+ int __weekday_ = 0;
+
+ int __century_ = 0;
+ int __year_of_century_ = 0;
+
+ int __hour12_ = 0;
+ bool __is_pm_ = false;
+
+ // For durations, __day_of_year_ is a number of days and is not limited to [1, 366].
+ int __day_of_year_ = 0;
+ int __week_sun_ = 0;
+ int __week_mon_ = 0;
+
+ // Minutes east of UTC.
+ int __utc_offset_ = 0;
+
+ __fields_set __present_ = __fields_set::__none;
+
+ _LIBCPP_HIDE_FROM_ABI constexpr void __set(__fields_set __field_bit) { __present_ |= __field_bit; }
+
+ // Check whether all specified fields are present, allowing additional fields.
+ // __utc_offset is checked like any other field.
+ _LIBCPP_HIDE_FROM_ABI constexpr bool __has(__fields_set __parts) const { return (__present_ & __parts) == __parts; }
+
+ // Check whether at least one specified field is present, allowing additional fields.
+ // __utc_offset is checked like any other field.
+ _LIBCPP_HIDE_FROM_ABI constexpr bool __has_any(__fields_set __parts) const {
+ return (__present_ & __parts) != __fields_set::__none;
+ }
+
+ // Allow only these fields, without requiring all of them to be present.
+ // Ignore __utc_offset so callers do not have to explicitly allow it each time.
+ _LIBCPP_HIDE_FROM_ABI constexpr bool __has_only(__fields_set __allowed) const {
+ const auto __fields = __present_ & ~__fields_set::__utc_offset;
+ return (__fields & ~__allowed) == __fields_set::__none;
+ }
+
+ // Require every required field and allow only optional fields in addition.
+ // The two sets must be disjoint. Exclude the UTC offset from this check
+ // so callers do not have to explicitly allow __utc_offset each time.
+ _LIBCPP_HIDE_FROM_ABI constexpr bool
+ __has_exactly(__fields_set __required, __fields_set __optional = __fields_set::__none) const {
+ const auto __fields = __present_ & ~__fields_set::__utc_offset;
+ return (__fields & ~__optional) == __required;
+ }
+};
+
+} // namespace chrono
+
+_LIBCPP_END_NAMESPACE_STD
+
+# endif
+#endif
+
+#endif //_LIBCPP___CHRONO_PARSER_DATA_H
diff --git a/libcxx/include/chrono b/libcxx/include/chrono
index a7fd8428a652cc..cde8ec911d8c19 100644
--- a/libcxx/include/chrono
+++ b/libcxx/include/chrono
@@ -228,6 +228,12 @@ template<class charT, class traits, class Rep, class Period> // C++20
operator<<(basic_ostream<charT, traits>& os,
const duration<Rep, Period>& d);
+template<class charT, class traits, class Rep, class Period, class Alloc = allocator<charT>> // C++20
+ 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);
+
// time_point arithmetic (all constexpr in C++14)
template <class Clock, class Duration1, class Rep2, class Period2>
time_point<Clock, typename common_type<Duration1, duration<Rep2, Period2>>::type>
@@ -309,6 +315,12 @@ template<class charT, class traits> // C++20
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>& os, const sys_days& dp);
+template<class charT, class traits, class Duration, class Alloc = allocator<charT>> // C++20
+ 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);
+
// [time.clock.utc], class utc_clock
class utc_clock { // C++20
public:
@@ -336,6 +348,12 @@ template<class charT, class traits, class Duration> // C++20
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>& os, const utc_time<Duration>& t);
+template<class charT, class traits, class Duration, class Alloc = allocator<charT>> // C++20
+ 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);
+
struct leap_second_info { // C++20
bool is_leap_second;
seconds elapsed;
@@ -372,6 +390,12 @@ template<class charT, class traits, class Duration> // C++20
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>& os, const tai_time<Duration>& t);
+template<class charT, class traits, class Duration, class Alloc = allocator<charT>> // C++20
+ 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);
+
// [time.clock.gps], class gps_clock
class gps_clock { // C++20
public:
@@ -399,6 +423,12 @@ template<class charT, class traits, class Duration> // C++20
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>& os, const gps_time<Duration>& t);
+template<class charT, class traits, class Duration, class Alloc = allocator<charT>> // C++20
+ 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);
+
class file_clock // C++20
{
public:
@@ -424,6 +454,12 @@ template<class charT, class traits, class Duration> // C++20
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>& os, const file_time<Duration>& tp);
+template<class charT, class traits, class Duration, class Alloc = allocator<charT>> // C++20
+ 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);
+
class steady_clock
{
public:
@@ -449,6 +485,12 @@ template<class charT, class traits, class Duration> // C++20
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>& os, const local_time<Duration>& tp);
+template<class charT, class traits, class Duration, class Alloc = allocator<charT>> // C++20
+ 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);
+
// [time.cal.last] class last_spec // C++20
struct last_spec;
@@ -465,6 +507,12 @@ template<class charT, class traits>
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>& os, const day& d);
+template<class charT, class traits, class Alloc = allocator<charT>> // C++20
+ 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);
+
// [time.cal.month] class month // C++20
class month;
constexpr bool operator==(const month& x, const month& y) noexcept;
@@ -478,6 +526,12 @@ template<class charT, class traits>
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>& os, const month& m);
+template<class charT, class traits, class Alloc = allocator<charT>> // C++20
+ 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);
+
// [time.cal.year] class year // C++20
class year;
constexpr bool operator==(const year& x, const year& y) noexcept;
@@ -491,6 +545,12 @@ template<class charT, class traits>
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>& os, const year& y);
+template<class charT, class traits, class Alloc = allocator<charT>> // C++20
+ 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);
+
// [time.cal.wd] class weekday // C++20
class weekday;
@@ -503,6 +563,12 @@ template<class charT, class traits>
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>& os, const weekday& wd);
+template<class charT, class traits, class Alloc = allocator<charT>> // C++20
+ 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);
+
// [time.cal.wdidx] class weekday_indexed // C++20
class weekday_indexed;
@@ -531,6 +597,12 @@ template<class charT, class traits>
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>& os, const month_day& md);
+template<class charT, class traits, class Alloc = allocator<charT>> // C++20
+ 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);
+
// [time.cal.mdlast] class month_day_last // C++20
class month_day_last;
@@ -578,6 +650,12 @@ template<class charT, class traits>
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>& os, const year_month& ym);
+template<class charT, class traits, class Alloc = allocator<charT>> // C++20
+ 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);
+
// [time.cal.ymd] class year_month_day class // C++20
year_month_day;
@@ -595,6 +673,12 @@ template<class charT, class traits>
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>& os, const year_month_day& ymd);
+template<class charT, class traits, class Alloc = allocator<charT>> // C++20
+ 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);
+
// [time.cal.ymdlast] class year_month_day_last // C++20
class year_month_day_last;
@@ -996,6 +1080,31 @@ namespace std {
} // namespace std
namespace chrono {
+// [time.parse], parsing
+template<class charT, class Parsable> // C++20
+ unspecified parse(const charT* fmt, Parsable& tp);
+template<class charT, class traits, class Alloc, class Parsable> // C++20
+ unspecified parse(const basic_string<charT, traits, Alloc>& fmt, Parsable& tp);
+
+template<class charT, class traits, class Alloc, class Parsable> // C++20
+ unspecified parse(const charT* fmt, Parsable& tp,
+ basic_string<charT, traits, Alloc>& abbrev);
+template<class charT, class traits, class Alloc, class Parsable> // C++20
+ unspecified parse(const basic_string<charT, traits, Alloc>& fmt, Parsable& tp,
+ basic_string<charT, traits, Alloc>& abbrev);
+
+template<class charT, class Parsable> // C++20
+ unspecified parse(const charT* fmt, Parsable& tp, minutes& offset);
+template<class charT, class traits, class Alloc, class Parsable> // C++20
+ unspecified parse(const basic_string<charT, traits, Alloc>& fmt, Parsable& tp, minutes& offset);
+
+template<class charT, class traits, class Alloc, class Parsable> // C++20
+ unspecified parse(const charT* fmt, Parsable& tp,
+ basic_string<charT, traits, Alloc>& abbrev, minutes& offset);
+template<class charT, class traits, class Alloc, class Parsable> // C++20
+ unspecified parse(const basic_string<charT, traits, Alloc>& fmt, Parsable& tp,
+ basic_string<charT, traits, Alloc>& abbrev, minutes& offset);
+
// calendrical constants
inline constexpr last_spec last{}; // C++20
inline constexpr chrono::weekday Sunday{0}; // C++20
@@ -1119,7 +1228,10 @@ namespace std {
# if _LIBCPP_HAS_LOCALIZATION
# include <__chrono/formatter.h>
+# include <__chrono/from_stream.h>
# include <__chrono/ostream.h>
+# include <__chrono/parse.h>
+# include <__chrono/parser_data.h>
# include <__chrono/parser_std_format_spec.h>
# include <__chrono/statically_widen.h>
# endif
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 76a9df3820e3e4..6feec2bdca5014 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -974,6 +974,7 @@ module std {
module exception { header "__chrono/exception.h" }
module file_clock { header "__chrono/file_clock.h" }
module formatter { header "__chrono/formatter.h" }
+ module from_stream { header "__chrono/from_stream.h" }
module gps_clock {
header "__chrono/gps_clock.h"
export std.chrono.time_point
@@ -1001,6 +1002,8 @@ module std {
module month { header "__chrono/month.h" }
module monthday { header "__chrono/monthday.h" }
module ostream { header "__chrono/ostream.h" }
+ module parse { header "__chrono/parse.h" }
+ module parser_data { header "__chrono/parser_data.h" }
module parser_std_format_spec { header "__chrono/parser_std_format_spec.h" }
module statically_widen { header "__chrono/statically_widen.h" }
module steady_clock {
diff --git a/libcxx/modules/std/chrono.inc b/libcxx/modules/std/chrono.inc
index d771559d15107a..bf5f00b6b4c402 100644
--- a/libcxx/modules/std/chrono.inc
+++ b/libcxx/modules/std/chrono.inc
@@ -52,8 +52,8 @@ export namespace std {
// [time.duration.io], duration I/O
#if _LIBCPP_HAS_LOCALIZATION
using std::chrono::operator<<;
+ using std::chrono::from_stream;
#endif
- // using std::chrono::from_stream;
// convenience typedefs
using std::chrono::days;
@@ -256,7 +256,9 @@ export namespace std {
#endif
namespace chrono {
- // using std::chrono::parse;
+#if _LIBCPP_HAS_LOCALIZATION
+ using std::chrono::parse;
+#endif
// calendrical constants
using std::chrono::last;
diff --git a/libcxx/test/libcxx/time/from_stream.pass.cpp b/libcxx/test/libcxx/time/from_stream.pass.cpp
new file mode 100644
index 00000000000000..6584dce08f10fa
--- /dev/null
+++ b/libcxx/test/libcxx/time/from_stream.pass.cpp
@@ -0,0 +1,164 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+
+#include <cassert>
+#include <chrono>
+#include <cstdint>
+#include <limits>
+#include <locale>
+#include <sstream>
+#include <string>
+
+#include "make_string.h"
+#include "test_macros.h"
+
+#define STR(S) MAKE_STRING(CharT, S)
+
+template <class CharT>
+void test_read_digits() {
+ struct TestCase {
+ std::basic_string<CharT> input;
+ unsigned width;
+ std::uint64_t limit;
+ std::uint64_t value;
+ unsigned count;
+ bool overflow;
+ CharT next;
+ };
+ const TestCase cases[] = {
+ // Read ordinary digits, stopping at the width or a non-digit.
+ {STR("12345"), 2, 12, 12, 2, false, CharT('3')},
+ {STR("0012X"), 5, 12, 12, 4, false, CharT('X')},
+ // No digits are consumed when the width is zero or the input is not numeric.
+ {STR("12"), 0, 12, 0, 0, false, CharT('1')},
+ {STR("X"), 1, 12, 0, 0, false, CharT('X')},
+ // Stop as soon as the next digit would exceed the limit.
+ {STR("12345"), 5, 12, 12, 3, true, CharT('4')},
+ {STR("13X"), 3, 12, 1, 2, true, CharT('X')},
+ };
+ for (const auto& c : cases) {
+ std::basic_istringstream<CharT> stream(c.input);
+ std::ios_base::iostate state = std::ios_base::goodbit;
+ std::chrono::__from_stream_parser<CharT, std::char_traits<CharT>> parser{stream, state};
+ auto result = parser.__read_digits(c.width, c.limit);
+ assert(result.__value == c.value);
+ assert(result.__digits_read == c.count);
+ assert(result.__overflow == c.overflow);
+ assert(state == std::ios_base::goodbit); // Reporting failure is the caller's responsibility.
+ assert(stream.peek() == c.next);
+ }
+}
+
+void test_read_signed() {
+ struct TestCase {
+ std::string input;
+ int expected;
+ };
+ const TestCase cases[] = {
+ // Ordinary values, with and without an explicit sign.
+ {"123", 123},
+ {"+123", 123},
+ {"-123", -123},
+ // The negative limit has no positive counterpart in int.
+ {std::to_string(std::numeric_limits<int>::min()), std::numeric_limits<int>::min()},
+ };
+ for (const auto& c : cases) {
+ std::istringstream stream{c.input};
+ int result = -1;
+ std::ios_base::iostate state = std::ios_base::goodbit;
+ std::chrono::__from_stream_parser<char, std::char_traits<char>> parser{stream, state};
+ parser.__read_signed(static_cast<unsigned>(c.input.size()), result);
+ assert(!(state & (std::ios_base::failbit | std::ios_base::badbit)));
+ assert(result == c.expected);
+ }
+}
+
+void test_field_queries() {
+ using Fields = std::chrono::__fields_storage;
+ using Parts = std::chrono::__fields_set;
+ constexpr auto check = [] {
+ Fields fields;
+ assert(!fields.__has(Parts::__day));
+ assert(!fields.__has_any(Parts::__day | Parts::__month));
+ fields.__set(Parts::__utc_offset);
+ assert(fields.__has(Parts::__utc_offset));
+ assert(!fields.__has_exactly(Parts::__day));
+ fields.__set(Parts::__day);
+ // __has_only and __has_exactly ignore the UTC offset field.
+ assert(fields.__has_only(Parts::__day));
+ assert(fields.__has_exactly(Parts::__day));
+ assert(fields.__has(Parts::__day | Parts::__utc_offset));
+ assert(fields.__has_any(Parts::__day | Parts::__month));
+ assert(!fields.__has(Parts::__day | Parts::__month));
+ assert(fields.__has_only(Parts::__day | Parts::__month));
+ assert(fields.__has_exactly(Parts::__day, Parts::__month));
+ fields.__set(Parts::__month);
+ assert(!fields.__has_only(Parts::__day));
+ assert(!fields.__has_exactly(Parts::__day));
+ assert(fields.__has_exactly(Parts::__day, Parts::__month));
+ assert(fields.__has_exactly(Parts::__day | Parts::__month));
+ return true;
+ };
+ static_assert(check());
+ assert(check());
+}
+
+template <class CharT, class T>
+void check_repeated_field(const std::basic_string<CharT>& format,
+ const std::basic_string<CharT>& matching,
+ const std::basic_string<CharT>& conflicting,
+ T expected) {
+ {
+ std::basic_istringstream<CharT> stream(matching);
+ stream.imbue(std::locale::classic());
+ T result{};
+ std::chrono::from_stream(stream, format.c_str(), result);
+ assert(!stream.fail());
+ assert(result == expected);
+ }
+ {
+ std::basic_istringstream<CharT> stream(conflicting);
+ stream.imbue(std::locale::classic());
+ T result{};
+ std::chrono::from_stream(stream, format.c_str(), result);
+ assert(stream.fail());
+ }
+}
+
+template <class CharT>
+void test_repeated_fields() {
+ using namespace std::chrono;
+ // libc++ accepts equal repetitions and rejects conflicting values.
+ check_repeated_field(STR("%d %d"), STR("01 01"), STR("01 02"), day{1});
+
+ // Compound formats and locale formats share the same parsed fields.
+ check_repeated_field(STR("%F %x"), STR("2026-07-20 07/20/26"), STR("2026-07-20 07/21/26"), 2026y / July / 20);
+
+ // Compare fractional values, not their spelling; locale seconds have no fraction.
+ check_repeated_field(STR("%S %S"), STR("1.25 1.250"), STR("1.25 1.5"), 1250ms);
+ check_repeated_field(STR("%S %X"), STR("1.0 00:00:01"), STR("1.5 00:00:01"), 1000ms);
+
+ // Zone fields are checked even without output pointers.
+ check_repeated_field(STR("%d %z %Ez"), STR("01 +0100 +1:00"), STR("01 +0100 +2:00"), day{1});
+ check_repeated_field(STR("%d %Z %Z"), STR("01 UTC UTC"), STR("01 UTC GMT"), day{1});
+}
+
+int main(int, char**) {
+ test_read_signed();
+ test_field_queries();
+ test_read_digits<char>();
+ test_repeated_fields<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test_read_digits<wchar_t>();
+ test_repeated_fields<wchar_t>();
+#endif
+ return 0;
+}
diff --git a/libcxx/test/std/time/time.parse/from_stream.pass.cpp b/libcxx/test/std/time/time.parse/from_stream.pass.cpp
new file mode 100644
index 00000000000000..fa722f7093c7e5
--- /dev/null
+++ b/libcxx/test/std/time/time.parse/from_stream.pass.cpp
@@ -0,0 +1,463 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// XFAIL: availability-tzdb-missing && !libcpp-has-no-experimental-tzdb && !no-tzdb && !no-filesystem
+
+// <chrono>
+// from_stream overloads for calendar types, durations, and time points.
+
+#include <cassert>
+#include <chrono>
+#include <initializer_list>
+#include <limits>
+#include <locale>
+#include <ratio>
+#include <sstream>
+#include <string>
+
+#include "make_string.h"
+#include "test_macros.h"
+
+#define STR(S) MAKE_STRING(CharT, S)
+
+template <class CharT, class T>
+void check(const std::basic_string<CharT>& input, const std::basic_string<CharT>& format, T expected) {
+ std::basic_istringstream<CharT> stream(input);
+ stream.imbue(std::locale::classic());
+ T result{};
+ std::chrono::from_stream(stream, format.c_str(), result);
+ assert(!stream.fail());
+ assert(result == expected);
+}
+
+template <class T, class CharT>
+void check_failure(const std::basic_string<CharT>& input, const std::basic_string<CharT>& format) {
+ std::basic_istringstream<CharT> stream(input);
+ stream.imbue(std::locale::classic());
+ T result{};
+ std::chrono::from_stream(stream, format.c_str(), result);
+ assert(stream.fail());
+}
+
+template <class CharT>
+void test_calendar_types() {
+ using namespace std::chrono;
+ check(STR("15"), STR("%d"), day{15});
+ check(STR(" 15"), STR("%n%e"), day{15});
+ // Zone fields are consumed even when their output pointers are null.
+ check(STR("15 UTC +0130"), STR("%d %Z %z"), day{15});
+ check_failure<day>(STR("02-15"), STR("%m-%d"));
+ check_failure<day>(STR("15 00"), STR("%d %H"));
+ check_failure<day>(STR("32"), STR("%d"));
+
+ check(STR("02"), STR("%m"), February);
+ check(STR("Feb"), STR("%b"), February);
+ check_failure<month>(STR("13"), STR("%m"));
+
+ check(STR("2026"), STR("%Y"), 2026y);
+
+ check(STR("Mon"), STR("%a"), Monday);
+ check(STR("1"), STR("%u"), Monday);
+ check_failure<weekday>(STR("7"), STR("%w"));
+
+ check(STR("02-15"), STR("%m-%d"), February / 15);
+ check(STR("02-29"), STR("%m-%d"), February / 29);
+ check(STR("32"), STR("%j"), February / 1);
+ check_failure<month_day>(STR("60"), STR("%j"));
+ check_failure<month_day>(STR("02-30"), STR("%m-%d"));
+ check_failure<month_day>(STR("02"), STR("%m"));
+
+ check(STR("2026-02"), STR("%Y-%m"), 2026y / February);
+ check_failure<year_month>(STR("2026-13"), STR("%Y-%m"));
+
+ const year_month_day date = 2026y / February / 15;
+ check(STR("2026-02-15"), STR("%F"), date);
+ check(STR("2026 046"), STR("%Y %j"), date);
+ check(STR("2026-W07-7"), STR("%G-W%V-%u"), date);
+ check(STR("68 69-W01-1"), STR("%y %g-W%V-%u"), 2068y / December / 31);
+ check(STR("-32768-W53-6"), STR("%6G-W%V-%u"), (year::min)() / January / 1);
+ check(STR("2026 07 0"), STR("%Y %U %w"), date);
+ check(STR("2026 06 7"), STR("%Y %W %u"), date);
+ check(STR("2026-02-15 Sun"), STR("%F %a"), date);
+ check_failure<year_month_day>(STR("2026-02-15 Mon"), STR("%F %a"));
+ check_failure<year_month_day>(STR("2026-02-30"), STR("%F"));
+ check_failure<year_month_day>(STR("2026-02"), STR("%Y-%m"));
+ check_failure<year_month_day>(STR("2021-W53-5"), STR("%G-W%V-%u"));
+}
+
+template <class CharT>
+void test_format() {
+ using namespace std::chrono;
+ const sys_seconds date = sys_days{2026y / July / 20};
+ const sys_seconds date_time = date + 13h + 45min + 30s;
+
+ // Individual and compound directives.
+ check(STR("2026-07-20 13:45:30"), STR("%Y-%m-%d %H:%M:%S"), date_time);
+ check(STR("2026-07-7"), STR("%Y-%m-%e"), sys_seconds{sys_days{2026y / July / 7}});
+ check(STR("2026-07-20 13:45:30"), STR("%F %T"), date_time);
+ check(STR("2026-07-20 13:45"), STR("%F %R"), date + 13h + 45min);
+ check(STR("07/20/26"), STR("%D"), date);
+ check(STR("30"), STR("%S"), 30s);
+
+ // Named fields and locale-specific date/time formats in the classic locale.
+ check(STR("Monday"), STR("%A"), Monday);
+ check(STR("July"), STR("%B"), July);
+ check(STR("Jul"), STR("%h"), July);
+ check(STR("Mon Jul 20 13:45:30 2026"), STR("%c"), date_time);
+ check_failure<sys_seconds>(STR("Tue Jul 20 13:45:30 2026"), STR("%c"));
+ check(STR("07/20/26"), STR("%x"), date);
+
+ // A width on %F applies only to %Y.
+ check(STR("002026-07-20"), STR("%6F"), date);
+ check(STR("26-W30-1"), STR("%g-W%V-%u"), date);
+
+ // Literals and whitespace, including a match of zero whitespace characters.
+ check(STR("2026-07-20%"), STR("%F%%"), date);
+ check(STR("2026\t07\t20"), STR("%Y%t%m%t%d"), date);
+ check(STR(" 2026-07-20"), STR(" %F"), date);
+ check(STR("2026-07-20"), STR(" %Y-%m-%d"), date);
+
+ check_failure<sys_seconds>(STR("2026/07/20"), STR("%Y-%m-%d"));
+ check_failure<sys_seconds>(STR("13:45:30"), STR("%H:%M:%S"));
+ check_failure<sys_seconds>(STR("2026-07-xx"), STR("%Y-%m-%d"));
+ check_failure<sys_seconds>(STR("2026-07- 7"), STR("%Y-%m-%e"));
+}
+
+template <class CharT>
+void test_width_overflow() {
+ using namespace std::chrono;
+ std::basic_ostringstream<CharT> format;
+ format << CharT('%') << std::numeric_limits<unsigned>::max() << STR("0S");
+
+ // Reject an overflowing format width before reading the input.
+ std::basic_istringstream<CharT> stream{STR("1.25!")};
+ milliseconds result{};
+ from_stream(stream, format.str().c_str(), result);
+ assert(stream.fail());
+ stream.clear();
+ assert(stream.peek() == CharT('1'));
+}
+
+template <class CharT>
+void test_date_consistency() {
+ using namespace std::chrono;
+ const year_month_day feb1 = 2026y / February / 1;
+ const year_month_day jan1 = 2021y / January / 1;
+
+ // An ordinal date must agree with the supplied month.
+ check(STR("2026 032 02"), STR("%Y %j %m"), feb1);
+ check_failure<year_month_day>(STR("2026 032 03"), STR("%Y %j %m"));
+
+ // An ISO week date must agree with the calendar year, even across a year boundary.
+ check(STR("2020-W53-5 2021"), STR("%G-W%V-%u %Y"), jan1);
+ check_failure<year_month_day>(STR("2020-W53-5 2020"), STR("%G-W%V-%u %Y"));
+ check(STR("1926-W01-1 26"), STR("%G-W%V-%u %g"), 1926y / January / 4);
+}
+
+template <class CharT>
+void test_modifier_E() {
+ using namespace std::chrono;
+ const sys_seconds date = sys_days{2026y / July / 20};
+
+ check(STR("Mon Jul 20 13:45:30 2026"), STR("%Ec"), date + 13h + 45min + 30s);
+ check(STR("07/20/26"), STR("%Ex"), date);
+
+ // Numeric years in the classic locale.
+ for (const auto& format : {STR("%EC %Ey-%m-%d"), STR("%EC %EY-%m-%d")}) {
+ std::basic_istringstream<CharT> stream(format == STR("%EC %Ey-%m-%d") ? STR("20 26-07-20") : STR("20 2026-07-20"));
+ stream.imbue(std::locale::classic());
+ sys_seconds result{};
+ from_stream(stream, format.c_str(), result);
+ assert(!stream.fail());
+ assert(result == date);
+ }
+
+ // Parse a century and a UTC offset.
+ {
+ std::basic_istringstream<CharT> stream(STR("20 26-07-20 +4:30"));
+ stream.imbue(std::locale::classic());
+ sys_seconds result{};
+ from_stream(stream, STR("%EC %y-%m-%d %Ez").c_str(), result);
+ assert(!stream.fail());
+ assert(result == date - 4h - 30min);
+ }
+}
+
+template <class CharT>
+void test_modifier_O() {
+ using namespace std::chrono;
+ const sys_seconds date = sys_days{2026y / July / 20};
+
+ check(STR("2026-07-20"), STR("%Y-%m-%Oe"), date);
+
+ // Parse numeric fields, including twelve o'clock with PM.
+ {
+ std::basic_istringstream<CharT> stream(STR("26-07-20 13:45 1!"));
+ stream.imbue(std::locale::classic());
+ sys_seconds result{};
+ from_stream(stream, STR("%Oy-%Om-%Od %OH:%OM %Ow").c_str(), result);
+ assert(!stream.fail());
+ assert(result == date + 13h + 45min);
+ assert(stream.peek() == CharT('!'));
+ }
+ {
+ std::basic_istringstream<CharT> stream(STR("2026-07-20 12 PM"));
+ stream.imbue(std::locale::classic());
+ sys_seconds result{};
+ from_stream(stream, STR("%F %OI %p").c_str(), result);
+ assert(!stream.fail());
+ assert(result == date + 12h);
+ }
+
+ // %OS, %OU and %OW parse numerically; %Oz uses the offset parser.
+ {
+ std::basic_istringstream<CharT> stream(STR("2026-07-20 13:45:30.125 +4:30"));
+ stream.imbue(std::locale::classic());
+ sys_time<milliseconds> result{};
+ from_stream(stream, STR("%F %H:%M:%OS %Oz").c_str(), result);
+ assert(!stream.fail());
+ assert(result == date + 13h + 45min + 30s + 125ms - 4h - 30min);
+ }
+ for (const auto& format : {STR("%Y %OU %w"), STR("%Y %OW %w")}) {
+ std::basic_istringstream<CharT> stream(STR("2026 29 1"));
+ stream.imbue(std::locale::classic());
+ sys_seconds result{};
+ from_stream(stream, format.c_str(), result);
+ assert(!stream.fail());
+ assert(result == date);
+ }
+}
+
+template <class CharT>
+struct comma_numpunct : std::numpunct<CharT> {
+ CharT do_decimal_point() const override { return CharT(','); }
+};
+
+template <class CharT>
+void test_fractional_seconds() {
+ using namespace std::chrono;
+ check(STR("1.25"), STR("%S"), 1250ms);
+ check(STR(".5"), STR("%S"), 500ms);
+ check(STR("1."), STR("%S"), 1000ms);
+ check(STR(".5!"), STR("%2S!"), 500ms);
+ // Floating-point representations allow fractions even with a whole-second period.
+ check(STR("12.125s"), STR("%Ss"), duration<double>{12.125});
+ // An explicit width controls consumption independently of the target precision.
+ check(STR("1.2345!"), STR("%6S!"), 1234ms);
+
+ // An integer seconds target leaves the decimal point unread, regardless of width.
+ for (const auto& format : {STR("%S"), STR("%6S")}) {
+ std::basic_istringstream<CharT> stream(STR("1.25"));
+ stream.imbue(std::locale::classic());
+ duration<int> result{};
+ from_stream(stream, format.c_str(), result);
+ assert(!stream.fail());
+ assert(result == 1s);
+ assert(stream.peek() == CharT('.'));
+ }
+ {
+ std::basic_istringstream<CharT> stream(STR("1,25!"));
+ stream.imbue(std::locale(std::locale::classic(), new comma_numpunct<CharT>));
+ milliseconds result{};
+ from_stream(stream, STR("%S").c_str(), result);
+ assert(!stream.fail());
+ assert(result == 1250ms);
+ assert(stream.peek() == CharT('!'));
+ }
+ {
+ // The width includes the decimal point and limits the fractional digits.
+ std::basic_istringstream<CharT> stream(STR("1.234"));
+ stream.imbue(std::locale::classic());
+ milliseconds result{};
+ from_stream(stream, STR("%3S").c_str(), result);
+ assert(!stream.fail());
+ assert(result == 1200ms);
+ assert(stream.peek() == CharT('3'));
+ }
+}
+
+template <class CharT>
+void test_time_points() {
+ using namespace std::chrono;
+ const sys_seconds expected = sys_days{2026y / July / 20} + 13h + 45min + 30s;
+ auto test = [&](auto value) { check(STR("20 26-07-20 01:45:30 PM"), STR("%C %y-%m-%d %I:%M:%S %p"), value); };
+ test(expected);
+ test(local_seconds{expected.time_since_epoch()});
+ test(file_clock::from_sys(expected));
+
+ check(STR("2026-07-20"), STR("%F"), sys_days{2026y / July / 20});
+ // A days target rejects time-of-day fields even when the UTC offset would cancel them.
+ check_failure<sys_days>(STR("2026-07-20 04:00 +0400"), STR("%F %R %z"));
+ check(STR("1969-12-31 23:59:59.5"), STR("%F %T"), sys_time<milliseconds>{-500ms});
+ check(STR("1970-01-01 00:00:12.125!"), STR("%F %T!"), sys_time<duration<double>>{duration<double>{12.125}});
+ check(STR("1970-01-01 00:00:01"), STR("%F %T"), sys_time<duration<long long, std::atto>>{1s});
+
+#if !defined(TEST_HAS_NO_EXPERIMENTAL_TZDB) && !defined(TEST_HAS_NO_TIME_ZONE_DATABASE) && \
+ !defined(TEST_HAS_NO_FILESYSTEM)
+ // Combine the date and time of day before rounding to a unit that does not divide a day.
+ using SevenSeconds = duration<long long, std::ratio<7>>;
+ check(STR("1958-01-02 00:02"), STR("%F %R"), tai_time<SevenSeconds>{SevenSeconds{12360}});
+#endif
+}
+
+#if !defined(TEST_HAS_NO_EXPERIMENTAL_TZDB) && !defined(TEST_HAS_NO_TIME_ZONE_DATABASE) && \
+ !defined(TEST_HAS_NO_FILESYSTEM)
+template <class CharT>
+void test_utc_leap_seconds() {
+ using namespace std::chrono;
+ const utc_seconds leap = utc_clock::from_sys(sys_days{2017y / January / 1}) - 1s;
+ check(STR("2016-12-31 23:59:60"), STR("%F %T"), leap);
+ check(STR("2017-01-01 00:59:60.5 +0100"), STR("%F %T %z"), leap + 500ms);
+ check_failure<utc_seconds>(STR("2026-07-20 12:00:60"), STR("%F %T"));
+}
+#endif
+
+template <class CharT>
+void test_year_fields() {
+ using namespace std::chrono;
+ check(STR("+123"), STR("%4Y"), year{123});
+ check(STR("-123"), STR("%4Y"), year{-123});
+ check(STR("0"), STR("%Y"), year{0});
+
+ // Two-digit years use a default century unless %C or %Y supplies one.
+ check(STR("68"), STR("%y"), year{2068});
+ check(STR("69"), STR("%y"), year{1969});
+ check(STR("20 26"), STR("%C %y"), 2026y);
+ check(STR("-20 76"), STR("%3C %y"), year{-1976});
+ check(STR("2026 20"), STR("%Y %C"), 2026y);
+ check(STR("1926 26"), STR("%Y %y"), 1926y);
+ check(STR("1926-07-20 26"), STR("%F %y"), 1926y / July / 20);
+
+ // A sign consumes one character of the field's width.
+ {
+ std::basic_istringstream<CharT> stream(STR("+123"));
+ stream.imbue(std::locale::classic());
+ year result{};
+ from_stream(stream, STR("%3Y").c_str(), result);
+ assert(!stream.fail());
+ assert(result == year{12});
+ assert(stream.peek() == CharT('3'));
+ }
+
+ check_failure<year>(STR("100"), STR("%3y"));
+ check_failure<year>(STR("+1"), STR("%1Y"));
+ check_failure<year>(STR("2026 25"), STR("%Y %y"));
+ check_failure<year>(STR("20"), STR("%C"));
+}
+
+template <class CharT>
+void test_duration() {
+ using namespace std::chrono;
+ // Parse time-of-day and day-count components.
+ for (const auto& format : {STR("%T"), STR("%X"), STR("%EX")})
+ check(STR("01:30:00"), format, 5400s);
+ check(STR("01:30:00 AM"), STR("%r"), 5400s);
+ check(STR("PM 01:30"), STR("%p %I:%M"), 810min);
+ check(STR("2"), STR("%j"), days{2});
+ check(STR("2 01:30"), STR("%j %R"), 48h + 90min);
+ check(STR("23:59:59"), STR("%T"), 23h + 59min + 59s);
+
+ check_failure<seconds>(STR("15 01"), STR("%d %H"));
+ check_failure<hours>(STR("01:30"), STR("%R"));
+ check_failure<minutes>(STR("PM"), STR("%p"));
+ check_failure<minutes>(STR("60"), STR("%M"));
+ check_failure<seconds>(STR("60"), STR("%S"));
+}
+
+template <class CharT>
+void test_hours() {
+ using namespace std::chrono;
+ // %H can disambiguate %I without %p, in either input order.
+ check(STR("13 01 PM"), STR("%H %I %p"), 13h);
+ check(STR("13 01"), STR("%H %I"), 13h);
+ check(STR("01 13"), STR("%I %H"), 13h);
+ check(STR("00 12"), STR("%H %I"), 0h);
+ check(STR("12 12"), STR("%H %I"), 12h);
+
+ check_failure<minutes>(STR("14 01"), STR("%H %I"));
+ check_failure<minutes>(STR("13 AM"), STR("%H %p"));
+ check_failure<hours>(STR("24"), STR("%H"));
+ check_failure<minutes>(STR("13 PM"), STR("%I %p"));
+}
+
+template <class CharT>
+void test_duration_conversion() {
+ using namespace std::chrono;
+
+ check(STR("42"), STR("%S"), duration<unsigned>{42});
+ check(STR("1.25"), STR("%S"), duration<double, std::milli>{1250});
+ // A 1.5-second integer tick is coarser than the seconds field.
+ check_failure<duration<int, std::ratio<3, 2>>>(STR("1.5"), STR("%S"));
+}
+
+template <class CharT>
+void test_offsets() {
+ using namespace std::chrono;
+ const sys_seconds date = sys_days{2026y / July / 20};
+ check(STR("2026-07-20 04"), STR("%F %z"), date - 4h);
+ check(STR("2026-07-20 0430"), STR("%F %z"), date - 4h - 30min);
+ check(STR("2026-07-20 -0130"), STR("%F %z"), date + 90min);
+ // Offset minutes are not clock-minute fields restricted to 0-59.
+ check(STR("2026-07-20 +0160"), STR("%F %z"), date - 120min);
+
+ check(STR("2026-07-20 4"), STR("%F %Ez"), date - 4h);
+ check(STR("2026-07-20 +4:30"), STR("%F %Ez"), date - 4h - 30min);
+ check(STR("2026-07-20 +4:30"), STR("%F %Oz"), date - 4h - 30min);
+ check(STR("2026-07-20 -4:30"), STR("%F %Ez"), date + 4h + 30min);
+
+ // Basic hours and any supplied minutes require two digits.
+ check_failure<sys_seconds>(STR("2026-07-20 019"), STR("%F %z"));
+ check_failure<sys_seconds>(STR("2026-07-20 4:9"), STR("%F %Ez"));
+ check_failure<sys_seconds>(STR("2026-07-20 4"), STR("%F %z"));
+}
+
+template <class CharT>
+void test_abbrev() {
+ using namespace std::chrono;
+ std::basic_istringstream<CharT> stream(STR("2026-07-20 a_1/+B-2!"));
+ stream.imbue(std::locale::classic());
+ sys_days result{};
+ std::basic_string<CharT> abbrev = STR("original");
+ from_stream(stream, STR("%F %Z").c_str(), result, &abbrev);
+ assert(!stream.fail());
+ assert(result == sys_days{2026y / July / 20});
+ assert(abbrev == STR("a_1/+B-2"));
+ assert(stream.peek() == CharT('!'));
+}
+
+template <class CharT>
+void test() {
+ test_calendar_types<CharT>();
+ test_format<CharT>();
+ test_width_overflow<CharT>();
+ test_date_consistency<CharT>();
+ test_modifier_E<CharT>();
+ test_modifier_O<CharT>();
+ test_fractional_seconds<CharT>();
+ test_year_fields<CharT>();
+ test_duration<CharT>();
+ test_hours<CharT>();
+ test_duration_conversion<CharT>();
+ test_time_points<CharT>();
+#if !defined(TEST_HAS_NO_EXPERIMENTAL_TZDB) && !defined(TEST_HAS_NO_TIME_ZONE_DATABASE) && \
+ !defined(TEST_HAS_NO_FILESYSTEM)
+ test_utc_leap_seconds<CharT>();
+#endif
+ test_offsets<CharT>();
+ test_abbrev<CharT>();
+}
+
+int main(int, char**) {
+ test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test<wchar_t>();
+#endif
+ return 0;
+}
diff --git a/libcxx/test/std/time/time.parse/parse.pass.cpp b/libcxx/test/std/time/time.parse/parse.pass.cpp
new file mode 100644
index 00000000000000..e1f894f3184149
--- /dev/null
+++ b/libcxx/test/std/time/time.parse/parse.pass.cpp
@@ -0,0 +1,176 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+
+// <chrono>
+// parse: string and pointer formats, optional outputs, constraints, and ADL.
+
+#include <cassert>
+#include <chrono>
+#include <concepts>
+#include <locale>
+#include <sstream>
+#include <string>
+#include <utility>
+
+#include "make_string.h"
+#include "test_macros.h"
+
+#define STR(S) MAKE_STRING(CharT, S)
+
+template <class... Args>
+concept CanParse = requires(Args&&... args) { std::chrono::parse(std::forward<Args>(args)...); };
+
+namespace custom {
+template <int Arity>
+struct value {
+ int count = 0;
+};
+
+template <class CharT, class Traits>
+std::basic_istream<CharT, Traits>& from_stream(std::basic_istream<CharT, Traits>& is, const CharT* fmt, value<3>& out) {
+ assert(fmt[0] == CharT('#'));
+ return is >> out.count;
+}
+
+template <class CharT, class Traits, class Alloc>
+std::basic_istream<CharT, Traits>&
+from_stream(std::basic_istream<CharT, Traits>& is,
+ const CharT* fmt,
+ value<4>& out,
+ std::basic_string<CharT, Traits, Alloc>* abbrev) {
+ assert(fmt[0] == CharT('#'));
+ assert(abbrev != nullptr);
+ *abbrev = STR("custom");
+ return is >> out.count;
+}
+
+template <class CharT, class Traits, class Alloc>
+std::basic_istream<CharT, Traits>&
+from_stream(std::basic_istream<CharT, Traits>& is,
+ const CharT* fmt,
+ value<5>& out,
+ std::basic_string<CharT, Traits, Alloc>* abbrev,
+ std::chrono::minutes* offset) {
+ assert(fmt[0] == CharT('#'));
+ assert(offset != nullptr);
+ if (abbrev)
+ *abbrev = STR("custom");
+ *offset = std::chrono::minutes{90};
+ return is >> out.count;
+}
+
+struct not_parsable {};
+} // namespace custom
+
+template <class CharT, class Format, int Arity>
+void check_constraints() {
+ using String = std::basic_string<CharT>;
+ using Value = custom::value<Arity>;
+ using Minutes = std::chrono::minutes;
+ static_assert(CanParse<const Format&, Value&> == (Arity == 3));
+ static_assert(CanParse<const Format&, Value&, String&> == (Arity == 4));
+ static_assert(CanParse<const Format&, Value&, Minutes&> == (Arity == 5));
+ static_assert(CanParse<const Format&, Value&, String&, Minutes&> == (Arity == 5));
+}
+
+template <class CharT, class Format>
+void test_constraints() {
+ check_constraints<CharT, Format, 3>();
+ check_constraints<CharT, Format, 4>();
+ check_constraints<CharT, Format, 5>();
+ static_assert(!CanParse<const Format&, custom::not_parsable&>);
+ static_assert(!CanParse<const Format&, const custom::value<3>&>);
+ static_assert(!CanParse<const Format&, custom::value<3>>);
+}
+
+template <class CharT, class Format>
+void test_adl() {
+ using String = std::basic_string<CharT>;
+ const auto format_storage = STR("#");
+ const Format fmt{format_storage.c_str()};
+ {
+ std::basic_istringstream<CharT> is(STR("7"));
+ custom::value<3> result{};
+ auto& returned = is >> std::chrono::parse(fmt, result);
+ assert(&returned == &is);
+ assert(!is.fail());
+ assert(result.count == 7);
+ }
+ {
+ std::basic_istringstream<CharT> is(STR("7"));
+ custom::value<5> result{};
+ String abbrev;
+ std::chrono::minutes offset{};
+ is >> std::chrono::parse(fmt, result, abbrev, offset);
+ assert(!is.fail());
+ assert(result.count == 7);
+ assert(abbrev == STR("custom"));
+ assert(offset == std::chrono::minutes{90});
+ }
+}
+
+template <class CharT, class Format>
+void test_chrono_overloads() {
+ using namespace std::chrono;
+ const sys_seconds date = sys_days{2026y / July / 20};
+ const auto format_storage = STR("%F %Z %z");
+ const Format fmt{format_storage.c_str()};
+ // Test both format types, with and without optional outputs.
+ {
+ std::basic_istringstream<CharT> is(STR("2026-07-20 UTC +0130!"));
+ is.imbue(std::locale::classic());
+ sys_seconds result{};
+ static_assert(std::same_as<decltype(is >> parse(fmt, result)), std::basic_istream<CharT>&>);
+ auto& returned = is >> parse(fmt, result);
+ assert(&returned == &is);
+ assert(!is.fail());
+ assert(result == date - 90min);
+ assert(is.peek() == CharT('!'));
+ }
+ {
+ std::basic_istringstream<CharT> is(STR("2026-07-20 UTC +0130!"));
+ is.imbue(std::locale::classic());
+ sys_seconds result{};
+ std::basic_string<CharT> abbrev;
+ minutes offset{};
+ is >> parse(fmt, result, abbrev, offset);
+ assert(!is.fail());
+ assert(result == date - 90min);
+ assert(is.peek() == CharT('!'));
+ assert(abbrev == STR("UTC"));
+ assert(offset == 90min);
+ }
+
+ // A parsing failure is visible on the original stream.
+ std::basic_istringstream<CharT> is(STR("invalid"));
+ is.imbue(std::locale::classic());
+ sys_seconds result{};
+ is >> parse(fmt, result);
+ assert(is.fail());
+}
+
+template <class CharT>
+void test() {
+ test_constraints<CharT, const CharT*>();
+ test_constraints<CharT, std::basic_string<CharT>>();
+ test_adl<CharT, const CharT*>();
+ test_adl<CharT, std::basic_string<CharT>>();
+ test_chrono_overloads<CharT, const CharT*>();
+ test_chrono_overloads<CharT, std::basic_string<CharT>>();
+}
+
+int main(int, char**) {
+ test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test<wchar_t>();
+#endif
+ return 0;
+}
>From 7794b3a67600abd388f5c815a3007554d77304d5 Mon Sep 17 00:00:00 2001
From: Emmett <emmettzhang2020 at outlook.com>
Date: Thu, 1 Oct 2026 23:09:35 +0800
Subject: [PATCH 2/5] fix some review comments and CI failure
---
libcxx/docs/Status/Cxx20Issues.csv | 14 +++++++-------
libcxx/docs/Status/Cxx23Issues.csv | 4 ++--
libcxx/docs/Status/Cxx26Issues.csv | 4 ++--
libcxx/include/__chrono/from_stream.h | 4 ++++
libcxx/include/__chrono/parser_data.h | 14 ++++++++++----
libcxx/test/libcxx/time/from_stream.pass.cpp | 2 +-
6 files changed, 26 insertions(+), 16 deletions(-)
diff --git a/libcxx/docs/Status/Cxx20Issues.csv b/libcxx/docs/Status/Cxx20Issues.csv
index e4ff0f28b5d33c..d00cbd31ad8780 100644
--- a/libcxx/docs/Status/Cxx20Issues.csv
+++ b/libcxx/docs/Status/Cxx20Issues.csv
@@ -164,16 +164,16 @@
"`LWG3231 <https://wg21.link/LWG3231>`__","``year_month_day_last::day``\ specification does not cover ``!ok()``\ values","2019-11 (Belfast)","|Nothing To Do|","","`#103862 <https://github.com/llvm/llvm-project/issues/103862>`__",""
"`LWG3225 <https://wg21.link/LWG3225>`__","``zoned_time``\ converting constructor shall not be ``noexcept``\ ","2019-11 (Belfast)","|Complete|","19","`#103863 <https://github.com/llvm/llvm-project/issues/103863>`__",""
"`LWG3190 <https://wg21.link/LWG3190>`__","``std::allocator::allocate``\ sometimes returns too little storage","2019-11 (Belfast)","|Complete|","14","`#103864 <https://github.com/llvm/llvm-project/issues/103864>`__",""
-"`LWG3218 <https://wg21.link/LWG3218>`__","Modifier for ``%d``\ parse flag does not match POSIX and ``format``\ specification","2019-11 (Belfast)","","","`#100258 <https://github.com/llvm/llvm-project/issues/100258>`__",""
+"`LWG3218 <https://wg21.link/LWG3218>`__","Modifier for ``%d``\ parse flag does not match POSIX and ``format``\ specification","2019-11 (Belfast)","|Partial|","","`#100258 <https://github.com/llvm/llvm-project/issues/100258>`__","``%Od`` is accepted, but parsing locale-specific alternative digits is not implemented."
"`LWG3224 <https://wg21.link/LWG3224>`__","``zoned_time``\ constructor from ``TimeZonePtr``\ does not specify initialization of ``tp_``\ ","2019-11 (Belfast)","|Complete|","19","`#103865 <https://github.com/llvm/llvm-project/issues/103865>`__",""
"`LWG3230 <https://wg21.link/LWG3230>`__","Format specifier ``%y/%Y``\ is missing locale alternative versions","2019-11 (Belfast)","|Complete|","16","`#103868 <https://github.com/llvm/llvm-project/issues/103868>`__",""
"`LWG3232 <https://wg21.link/LWG3232>`__","Inconsistency in ``zoned_time``\ deduction guides","2019-11 (Belfast)","|Complete|","19","`#103869 <https://github.com/llvm/llvm-project/issues/103869>`__",""
"`LWG3222 <https://wg21.link/LWG3222>`__","P0574R1 introduced preconditions on non-existent parameters","2019-11 (Belfast)","","","`#100259 <https://github.com/llvm/llvm-project/issues/100259>`__",""
"`LWG3221 <https://wg21.link/LWG3221>`__","Result of ``year_month``\ arithmetic with ``months``\ is ambiguous","2019-11 (Belfast)","|Complete|","8","`#103870 <https://github.com/llvm/llvm-project/issues/103870>`__",""
-"`LWG3235 <https://wg21.link/LWG3235>`__","``parse``\ manipulator without abbreviation is not callable","2019-11 (Belfast)","","","`#100262 <https://github.com/llvm/llvm-project/issues/100262>`__",""
+"`LWG3235 <https://wg21.link/LWG3235>`__","``parse``\ manipulator without abbreviation is not callable","2019-11 (Belfast)","|Complete|","24","`#100262 <https://github.com/llvm/llvm-project/issues/100262>`__",""
"`LWG3246 <https://wg21.link/LWG3246>`__","LWG3246: What are the constraints on the template parameter of ``basic_format_arg``?","2019-11 (Belfast)","|Nothing To Do|","","`#100263 <https://github.com/llvm/llvm-project/issues/100263>`__",""
"`LWG3253 <https://wg21.link/LWG3253>`__","``basic_syncbuf::basic_syncbuf()``\ should not be explicit","2019-11 (Belfast)","|Complete|","20","`#100264 <https://github.com/llvm/llvm-project/issues/100264>`__",""
-"`LWG3245 <https://wg21.link/LWG3245>`__","Unnecessary restriction on ``'%p'``\ parse specifier","2019-11 (Belfast)","","","`#100265 <https://github.com/llvm/llvm-project/issues/100265>`__",""
+"`LWG3245 <https://wg21.link/LWG3245>`__","Unnecessary restriction on ``'%p'``\ parse specifier","2019-11 (Belfast)","|Complete|","24","`#100265 <https://github.com/llvm/llvm-project/issues/100265>`__",""
"`LWG3244 <https://wg21.link/LWG3244>`__","Constraints for ``Source``\ in |sect|\ [fs.path.req] insufficiently constrainty","2019-11 (Belfast)","","","`#100266 <https://github.com/llvm/llvm-project/issues/100266>`__",""
"`LWG3241 <https://wg21.link/LWG3241>`__","``chrono-spec``\ grammar ambiguity in |sect|\ [time.format]","2019-11 (Belfast)","|Complete|","16","`#103871 <https://github.com/llvm/llvm-project/issues/103871>`__",""
"`LWG3257 <https://wg21.link/LWG3257>`__","Missing feature testing macro update from P0858","2019-11 (Belfast)","|Complete|","12","`#103873 <https://github.com/llvm/llvm-project/issues/103873>`__",""
@@ -181,7 +181,7 @@
"`LWG3273 <https://wg21.link/LWG3273>`__","Specify ``weekday_indexed``\ to range of ``[0, 7]``\ ","2019-11 (Belfast)","|Complete|","16","`#103875 <https://github.com/llvm/llvm-project/issues/103875>`__",""
"`LWG3070 <https://wg21.link/LWG3070>`__","``path::lexically_relative``\ causes surprising results if a filename can also be a *root-name*","2019-11 (Belfast)","","","`#100267 <https://github.com/llvm/llvm-project/issues/100267>`__",""
"`LWG3266 <https://wg21.link/LWG3266>`__","``to_chars(bool)``\ should be deleted","2019-11 (Belfast)","|Complete|","14","`#103876 <https://github.com/llvm/llvm-project/issues/103876>`__",""
-"`LWG3272 <https://wg21.link/LWG3272>`__","``%I%p``\ should parse/format ``duration``\ since midnight","2019-11 (Belfast)","","","`#100268 <https://github.com/llvm/llvm-project/issues/100268>`__",""
+"`LWG3272 <https://wg21.link/LWG3272>`__","``%I%p``\ should parse/format ``duration``\ since midnight","2019-11 (Belfast)","|Complete|","24","`#100268 <https://github.com/llvm/llvm-project/issues/100268>`__",""
"`LWG3259 <https://wg21.link/LWG3259>`__","The definition of *constexpr iterators* should be adjusted","2019-11 (Belfast)","|Nothing To Do|","","`#103877 <https://github.com/llvm/llvm-project/issues/103877>`__",""
"`LWG3103 <https://wg21.link/LWG3103>`__","Errors in taking subview of ``span``\ should be ill-formed where possible","2019-11 (Belfast)","|Complete|","11","`#103878 <https://github.com/llvm/llvm-project/issues/103878>`__",""
"`LWG3274 <https://wg21.link/LWG3274>`__","Missing feature test macro for ``<span>``\ ","2019-11 (Belfast)","|Complete|","11","`#103880 <https://github.com/llvm/llvm-project/issues/103880>`__",""
@@ -209,14 +209,14 @@
"`LWG3248 <https://wg21.link/LWG3248>`__","``std::format``\ ``#b``\ , ``#B``\ , ``#o``\ , ``#x``\ , and ``#X``\ presentation types misformat negative numbers","2020-02 (Prague)","|Complete|","14","`#103907 <https://github.com/llvm/llvm-project/issues/103907>`__",""
"`LWG3250 <https://wg21.link/LWG3250>`__","``std::format``\ : ``#``\ (alternate form) for NaN and inf","2020-02 (Prague)","|Complete|","14","`#103908 <https://github.com/llvm/llvm-project/issues/103908>`__",""
"`LWG3251 <https://wg21.link/LWG3251>`__","Are ``std::format``\ alignment specifiers applied to string arguments?","2020-02 (Prague)","|Complete|","14","`#103909 <https://github.com/llvm/llvm-project/issues/103909>`__",""
-"`LWG3252 <https://wg21.link/LWG3252>`__","Parse locale's aware modifiers for commands are not consistent with POSIX spec","2020-02 (Prague)","","","`#100271 <https://github.com/llvm/llvm-project/issues/100271>`__",""
+"`LWG3252 <https://wg21.link/LWG3252>`__","Parse locale's aware modifiers for commands are not consistent with POSIX spec","2020-02 (Prague)","|Partial|","","`#100271 <https://github.com/llvm/llvm-project/issues/100271>`__","The permitted modifiers are implemented, but parsing locale-specific alternative digits for ``%OI``, ``%OU``, and ``%OW`` is not implemented."
"`LWG3254 <https://wg21.link/LWG3254>`__","Strike ``stop_token``\ 's ``operator!=``\ ","2020-02 (Prague)","|Complete|","17","`#103910 <https://github.com/llvm/llvm-project/issues/103910>`__",""
"`LWG3255 <https://wg21.link/LWG3255>`__","``span``\ 's ``array``\ constructor is too strict","2020-02 (Prague)","|Complete|","","`#103911 <https://github.com/llvm/llvm-project/issues/103911>`__",""
"`LWG3260 <https://wg21.link/LWG3260>`__","``year_month*``\ arithmetic rejects durations convertible to years","2020-02 (Prague)","","","`#100274 <https://github.com/llvm/llvm-project/issues/100274>`__",""
"`LWG3262 <https://wg21.link/LWG3262>`__","Formatting of negative durations is not specified","2020-02 (Prague)","|Complete|","16","`#103912 <https://github.com/llvm/llvm-project/issues/103912>`__",""
"`LWG3264 <https://wg21.link/LWG3264>`__","``sized_range``\ and ``ranges::size``\ redundantly use ``disable_sized_range``\ ","2020-02 (Prague)","|Complete|","15","`#103913 <https://github.com/llvm/llvm-project/issues/103913>`__",""
-"`LWG3269 <https://wg21.link/LWG3269>`__","Parse manipulators do not specify the result of the extraction from stream","2020-02 (Prague)","","","`#100275 <https://github.com/llvm/llvm-project/issues/100275>`__",""
-"`LWG3270 <https://wg21.link/LWG3270>`__","Parsing and formatting ``%j``\ with ``duration``\ s","2020-02 (Prague)","|Partial|","","`#100418 <https://github.com/llvm/llvm-project/issues/100418>`__",""
+"`LWG3269 <https://wg21.link/LWG3269>`__","Parse manipulators do not specify the result of the extraction from stream","2020-02 (Prague)","|Complete|","24","`#100275 <https://github.com/llvm/llvm-project/issues/100275>`__",""
+"`LWG3270 <https://wg21.link/LWG3270>`__","Parsing and formatting ``%j``\ with ``duration``\ s","2020-02 (Prague)","|Complete|","24","`#100418 <https://github.com/llvm/llvm-project/issues/100418>`__",""
"`LWG3280 <https://wg21.link/LWG3280>`__","View converting constructors can cause constraint recursion and are unneeded","2020-02 (Prague)","|Complete|","15","`#103914 <https://github.com/llvm/llvm-project/issues/103914>`__",""
"`LWG3281 <https://wg21.link/LWG3281>`__","Conversion from ``*pair-like*``\ types to ``subrange``\ is a silent semantic promotion","2020-02 (Prague)","|Complete|","13","`#103915 <https://github.com/llvm/llvm-project/issues/103915>`__",""
"`LWG3282 <https://wg21.link/LWG3282>`__","``subrange``\ converting constructor should disallow derived to base conversions","2020-02 (Prague)","|Complete|","15","`#103916 <https://github.com/llvm/llvm-project/issues/103916>`__",""
diff --git a/libcxx/docs/Status/Cxx23Issues.csv b/libcxx/docs/Status/Cxx23Issues.csv
index 58ea67732f808f..bc3d09ea6c378f 100644
--- a/libcxx/docs/Status/Cxx23Issues.csv
+++ b/libcxx/docs/Status/Cxx23Issues.csv
@@ -82,7 +82,7 @@
"`LWG3530 <https://wg21.link/LWG3530>`__","``BUILTIN-PTR-MEOW`` should not opt the type out of syntactic checks","2021-06 (Virtual)","","","`#104341 <https://github.com/llvm/llvm-project/issues/104341>`__",""
"`LWG3532 <https://wg21.link/LWG3532>`__","``split_view<V, P>::inner-iterator<true>::operator++(int)`` should depend on ``Base``","2021-06 (Virtual)","","","`#104342 <https://github.com/llvm/llvm-project/issues/104342>`__",""
"`LWG3533 <https://wg21.link/LWG3533>`__","Make ``base() const &`` consistent across iterator wrappers that supports ``input_iterators``","2021-06 (Virtual)","|Complete|","14","`#104343 <https://github.com/llvm/llvm-project/issues/104343>`__",""
-"`LWG3536 <https://wg21.link/LWG3536>`__","Should ``chrono::from_stream()`` assign zero to duration for failure?","2021-06 (Virtual)","","","`#104344 <https://github.com/llvm/llvm-project/issues/104344>`__",""
+"`LWG3536 <https://wg21.link/LWG3536>`__","Should ``chrono::from_stream()`` assign zero to duration for failure?","2021-06 (Virtual)","|Complete|","24","`#104344 <https://github.com/llvm/llvm-project/issues/104344>`__",""
"`LWG3539 <https://wg21.link/LWG3539>`__","``format_to`` must not copy models of ``output_iterator<const charT&>``","2021-06 (Virtual)","|Complete|","14","`#104345 <https://github.com/llvm/llvm-project/issues/104345>`__",""
"`LWG3540 <https://wg21.link/LWG3540>`__","§[format.arg] There should be no const in ``basic_format_arg(const T* p)``","2021-06 (Virtual)","|Complete|","14","`#104348 <https://github.com/llvm/llvm-project/issues/104348>`__",""
"`LWG3541 <https://wg21.link/LWG3541>`__","``indirectly_readable_traits`` should be SFINAE-friendly for all types","2021-06 (Virtual)","|Complete|","14","`#104349 <https://github.com/llvm/llvm-project/issues/104349>`__",""
@@ -113,7 +113,7 @@
"`LWG3480 <https://wg21.link/LWG3480>`__","``directory_iterator`` and ``recursive_directory_iterator`` are not C++20 ranges","2021-10 (Virtual)","|Complete|","14","`#104374 <https://github.com/llvm/llvm-project/issues/104374>`__",""
"`LWG3498 <https://wg21.link/LWG3498>`__","Inconsistent ``noexcept``-specifiers for ``basic_syncbuf``","2021-10 (Virtual)","|Complete|","18","`#104375 <https://github.com/llvm/llvm-project/issues/104375>`__",""
"`LWG3535 <https://wg21.link/LWG3535>`__","``join_view::iterator::iterator_category`` and ``::iterator_concept`` lie","2021-10 (Virtual)","|Complete|","15","`#104376 <https://github.com/llvm/llvm-project/issues/104376>`__",""
-"`LWG3554 <https://wg21.link/LWG3554>`__","``chrono::parse`` needs ``const charT*`` and ``basic_string_view<charT>`` overloads","2021-10 (Virtual)","","","`#104377 <https://github.com/llvm/llvm-project/issues/104377>`__",""
+"`LWG3554 <https://wg21.link/LWG3554>`__","``chrono::parse`` needs ``const charT*`` and ``basic_string_view<charT>`` overloads","2021-10 (Virtual)","|Complete|","24","`#104377 <https://github.com/llvm/llvm-project/issues/104377>`__",""
"`LWG3557 <https://wg21.link/LWG3557>`__","The ``static_cast`` expression in ``convertible_to`` has the wrong operand","2021-10 (Virtual)","|Complete|","14","`#104378 <https://github.com/llvm/llvm-project/issues/104378>`__",""
"`LWG3559 <https://wg21.link/LWG3559>`__","Semantic requirements of ``sized_range`` is circular","2021-10 (Virtual)","|Nothing To Do|","","`#104379 <https://github.com/llvm/llvm-project/issues/104379>`__",""
"`LWG3560 <https://wg21.link/LWG3560>`__","``ranges::equal`` and ``ranges::is_permutation`` should short-circuit for ``sized_ranges``","2021-10 (Virtual)","","","`#104380 <https://github.com/llvm/llvm-project/issues/104380>`__",""
diff --git a/libcxx/docs/Status/Cxx26Issues.csv b/libcxx/docs/Status/Cxx26Issues.csv
index 8666436cc81e9d..c362c37124fcdd 100644
--- a/libcxx/docs/Status/Cxx26Issues.csv
+++ b/libcxx/docs/Status/Cxx26Issues.csv
@@ -112,7 +112,7 @@
"`LWG4170 <https://wg21.link/LWG4170>`__","``contiguous_iterator`` should require ``to_address(I{})``","2024-11 (Wrocław)","","","`#118368 <https://github.com/llvm/llvm-project/issues/118368>`__",""
"","","","","","",""
"`LWG3578 <https://wg21.link/LWG3578>`__","Iterator SCARYness in the context of associative container merging","2025-02 (Hagenberg)","","","`#127859 <https://github.com/llvm/llvm-project/issues/127859>`__",""
-"`LWG3956 <https://wg21.link/LWG3956>`__","``chrono::parse`` uses ``from_stream`` as a customization point","2025-02 (Hagenberg)","","","`#127860 <https://github.com/llvm/llvm-project/issues/127860>`__",""
+"`LWG3956 <https://wg21.link/LWG3956>`__","``chrono::parse`` uses ``from_stream`` as a customization point","2025-02 (Hagenberg)","|Complete|","24","`#127860 <https://github.com/llvm/llvm-project/issues/127860>`__",""
"`LWG4172 <https://wg21.link/LWG4172>`__","``unique_lock`` self-move-assignment is broken","2025-02 (Hagenberg)","|Complete|","22","`#127861 <https://github.com/llvm/llvm-project/issues/127861>`__",""
"`LWG4175 <https://wg21.link/LWG4175>`__","``get_env()`` specified in terms of ``as_const()`` but this doesn't work with rvalue senders","2025-02 (Hagenberg)","","","`#127862 <https://github.com/llvm/llvm-project/issues/127862>`__",""
"`LWG4179 <https://wg21.link/LWG4179>`__","Wrong range in ``[alg.search]``","2025-02 (Hagenberg)","","","`#127863 <https://github.com/llvm/llvm-project/issues/127863>`__",""
@@ -267,7 +267,7 @@
"`LWG3662 <https://wg21.link/LWG3662>`__","``basic_string::append/assign(NTBS, pos, n)`` suboptimal","2026-03 (Croydon)","|Complete|","23","`#189809 <https://github.com/llvm/llvm-project/issues/189809>`__",""
"`LWG3777 <https://wg21.link/LWG3777>`__","Common ``cartesian_product_view`` produces an invalid range if the first range is input and one of the ranges is empty","2026-03 (Croydon)","","","`#189810 <https://github.com/llvm/llvm-project/issues/189810>`__",""
"`LWG3797 <https://wg21.link/LWG3797>`__","``elements_view`` insufficiently constrained","2026-03 (Croydon)","","","`#189811 <https://github.com/llvm/llvm-project/issues/189811>`__",""
-"`LWG3831 <https://wg21.link/LWG3831>`__","Two-digit formatting of negative ``year`` is ambiguous","2026-03 (Croydon)","","","`#189812 <https://github.com/llvm/llvm-project/issues/189812>`__",""
+"`LWG3831 <https://wg21.link/LWG3831>`__","Two-digit formatting of negative ``year`` is ambiguous","2026-03 (Croydon)","|Complete|","24","`#189812 <https://github.com/llvm/llvm-project/issues/189812>`__",""
"`LWG3891 <https://wg21.link/LWG3891>`__","LWG3870 breaks ``std::expected<cv T, E>``","2026-03 (Croydon)","","","`#189813 <https://github.com/llvm/llvm-project/issues/189813>`__",""
"`LWG4026 <https://wg21.link/LWG4026>`__","Assignment operators of ``std::expected`` should propagate triviality","2026-03 (Croydon)","","","`#189814 <https://github.com/llvm/llvm-project/issues/189814>`__",""
"`LWG4090 <https://wg21.link/LWG4090>`__","Underspecified use of locale facets for locale-dependent ``std::format``","2026-03 (Croydon)","","","`#189815 <https://github.com/llvm/llvm-project/issues/189815>`__",""
diff --git a/libcxx/include/__chrono/from_stream.h b/libcxx/include/__chrono/from_stream.h
index bb5d5af9d1696b..8e2921b1908125 100644
--- a/libcxx/include/__chrono/from_stream.h
+++ b/libcxx/include/__chrono/from_stream.h
@@ -21,6 +21,7 @@
# include <__chrono/file_clock.h>
# include <__chrono/gps_clock.h>
# include <__chrono/hh_mm_ss.h>
+# include <__chrono/leap_second.h>
# include <__chrono/month.h>
# include <__chrono/monthday.h>
# include <__chrono/parser_data.h>
@@ -458,6 +459,9 @@ class __from_stream_parser {
}
// Parses '__fmt' into '__f', setting failbit on a mismatch.
+ // Keep the handling of all format specifiers together in a single switch.
+ // This makes the function longer and increases its cognitive complexity.
+ // NOLINTNEXTLINE(readability-function-size, readability-function-cognitive-complexity)
_LIBCPP_HIDE_FROM_ABI void __parse(
const _CharT* __fmt, __fields_storage& __f, basic_string<_CharT, _Traits>& __abbrev, __parse_options __options) {
const auto& __ctype = std::use_facet<ctype<_CharT> >(__is_.getloc());
diff --git a/libcxx/include/__chrono/parser_data.h b/libcxx/include/__chrono/parser_data.h
index fda046ca3752c6..9af9839614d33e 100644
--- a/libcxx/include/__chrono/parser_data.h
+++ b/libcxx/include/__chrono/parser_data.h
@@ -26,6 +26,8 @@ _LIBCPP_BEGIN_NAMESPACE_STD
namespace chrono {
+namespace __parser {
+
// Fields supplied by the parsed format directives.
enum class __fields_set : uint32_t {
__none = 0,
@@ -55,22 +57,26 @@ enum class __fields_set : uint32_t {
__iso_year_of_century = 1 << 17,
};
-_LIBCPP_HIDE_FROM_ABI inline constexpr __fields_set operator|(__fields_set __lhs, __fields_set __rhs) {
+_LIBCPP_HIDE_FROM_ABI constexpr __fields_set operator|(__fields_set __lhs, __fields_set __rhs) {
return static_cast<__fields_set>(static_cast<uint32_t>(__lhs) | static_cast<uint32_t>(__rhs));
}
-_LIBCPP_HIDE_FROM_ABI inline constexpr __fields_set operator&(__fields_set __lhs, __fields_set __rhs) {
+_LIBCPP_HIDE_FROM_ABI constexpr __fields_set operator&(__fields_set __lhs, __fields_set __rhs) {
return static_cast<__fields_set>(static_cast<uint32_t>(__lhs) & static_cast<uint32_t>(__rhs));
}
-_LIBCPP_HIDE_FROM_ABI inline constexpr __fields_set operator~(__fields_set __fields) {
+_LIBCPP_HIDE_FROM_ABI constexpr __fields_set operator~(__fields_set __fields) {
return static_cast<__fields_set>(~static_cast<uint32_t>(__fields));
}
-_LIBCPP_HIDE_FROM_ABI inline constexpr __fields_set& operator|=(__fields_set& __lhs, __fields_set __rhs) {
+_LIBCPP_HIDE_FROM_ABI constexpr __fields_set& operator|=(__fields_set& __lhs, __fields_set __rhs) {
return __lhs = __lhs | __rhs;
}
+} // namespace __parser
+
+using __parser::__fields_set;
+
// Fields collected while parsing. Date/time construction and validation do not
// modify these values or their presence flags.
struct __fields_storage {
diff --git a/libcxx/test/libcxx/time/from_stream.pass.cpp b/libcxx/test/libcxx/time/from_stream.pass.cpp
index 6584dce08f10fa..9d7a58046c5a09 100644
--- a/libcxx/test/libcxx/time/from_stream.pass.cpp
+++ b/libcxx/test/libcxx/time/from_stream.pass.cpp
@@ -53,7 +53,7 @@ void test_read_digits() {
assert(result.__digits_read == c.count);
assert(result.__overflow == c.overflow);
assert(state == std::ios_base::goodbit); // Reporting failure is the caller's responsibility.
- assert(stream.peek() == c.next);
+ assert(stream.peek() == std::char_traits<CharT>::to_int_type(c.next));
}
}
>From 50f2c4620aadbf19c64d26dc45ff272e48b602a2 Mon Sep 17 00:00:00 2001
From: Emmett <emmettzhang2020 at outlook.com>
Date: Fri, 2 Oct 2026 00:52:50 +0800
Subject: [PATCH 3/5] try to fix CI failure, remove recursive pattern with
explicit sequential operation
---
libcxx/include/__chrono/from_stream.h | 46 +++++++++++++++++++--------
1 file changed, 33 insertions(+), 13 deletions(-)
diff --git a/libcxx/include/__chrono/from_stream.h b/libcxx/include/__chrono/from_stream.h
index 8e2921b1908125..28cf3c6d3f4e0c 100644
--- a/libcxx/include/__chrono/from_stream.h
+++ b/libcxx/include/__chrono/from_stream.h
@@ -25,7 +25,6 @@
# include <__chrono/month.h>
# include <__chrono/monthday.h>
# include <__chrono/parser_data.h>
-# include <__chrono/statically_widen.h>
# include <__chrono/system_clock.h>
# include <__chrono/tai_clock.h>
# include <__chrono/time_point.h>
@@ -478,9 +477,10 @@ class __from_stream_parser {
_CharT __c{};
if (!__peek(__c) || !_Traits::eq(__c, __expected)) {
__state_ |= ios_base::failbit;
- return;
+ return false;
}
__consume();
+ return !__fail();
};
// Accept repeated fields only when their values agree.
@@ -623,15 +623,28 @@ class __from_stream_parser {
break;
case 'D':
- __parse(_LIBCPP_STATICALLY_WIDEN(_CharT, "%m/%d/%y"), __f, __abbrev, __options);
+ __read_unsigned(2, __value);
+ if (__fail() || !__assign(__f.__month_, __value, __fields_set::__month) || !__match(_CharT('/')))
+ break;
+ __read_unsigned(2, __value);
+ if (__fail() || !__assign(__f.__day_, __value, __fields_set::__day) || !__match(_CharT('/')))
+ break;
+ __read_unsigned(2, __value);
+ if (!__fail())
+ __assign(__f.__year_of_century_, __value, __fields_set::__year_of_century);
break;
case 'F':
// A width on %F applies only to %Y.
__read_signed(__has_width ? __width : 4, __value);
- if (!__fail() && __assign(__f.__year_, __value, __fields_set::__year)) {
- __parse(_LIBCPP_STATICALLY_WIDEN(_CharT, "-%m-%d"), __f, __abbrev, __options);
- }
+ if (__fail() || !__assign(__f.__year_, __value, __fields_set::__year) || !__match(_CharT('-')))
+ break;
+ __read_unsigned(2, __value);
+ if (__fail() || !__assign(__f.__month_, __value, __fields_set::__month) || !__match(_CharT('-')))
+ break;
+ __read_unsigned(2, __value);
+ if (!__fail())
+ __assign(__f.__day_, __value, __fields_set::__day);
break;
case 'g':
@@ -710,13 +723,20 @@ class __from_stream_parser {
break;
}
- case 'R':
- __parse(_LIBCPP_STATICALLY_WIDEN(_CharT, "%H:%M"), __f, __abbrev, __options);
- break;
-
- case 'T':
- __parse(_LIBCPP_STATICALLY_WIDEN(_CharT, "%H:%M:%S"), __f, __abbrev, __options);
- break;
+ case 'R': // %H:%M
+ case 'T': // %H:%M:%S
+ __read_unsigned(2, __value);
+ if (__fail() || !__assign(__f.__hours_, __value, __fields_set::__hours) || !__match(_CharT(':')))
+ break;
+ __read_unsigned(2, __value);
+ if (__fail() || !__assign(__f.__minutes_, __value, __fields_set::__minutes))
+ break;
+ if (__spec == 'R')
+ break;
+ if (!__match(_CharT(':')))
+ break;
+ // %T has no width or modifier; parse its seconds as an ordinary %S.
+ [[fallthrough]];
case 'S': {
// The target type determines whether fractions are allowed; the width only
>From 700303e15bf069204d460aec45c13de4ab172fc2 Mon Sep 17 00:00:00 2001
From: Emmett <emmettzhang2020 at outlook.com>
Date: Fri, 2 Oct 2026 10:00:58 +0800
Subject: [PATCH 4/5] rename __in_range -> __is_in_range to avoid name
conflicting on Windows
---
libcxx/include/__chrono/from_stream.h | 45 ++++++++++++++-------------
1 file changed, 23 insertions(+), 22 deletions(-)
diff --git a/libcxx/include/__chrono/from_stream.h b/libcxx/include/__chrono/from_stream.h
index 28cf3c6d3f4e0c..ddd3ae3280243f 100644
--- a/libcxx/include/__chrono/from_stream.h
+++ b/libcxx/include/__chrono/from_stream.h
@@ -90,7 +90,7 @@ _LIBCPP_HIDE_FROM_ABI constexpr __parse_options __get_parse_options() {
}
// Check an inclusive range without narrowing parsed int or int64_t fields.
-_LIBCPP_HIDE_FROM_ABI constexpr bool __in_range(int64_t __value, int64_t __lo, int64_t __hi) {
+_LIBCPP_HIDE_FROM_ABI constexpr bool __is_in_range(int64_t __value, int64_t __lo, int64_t __hi) {
return __lo <= __value && __value <= __hi;
}
@@ -650,7 +650,7 @@ class __from_stream_parser {
case 'g':
__read_unsigned(__has_width ? __width : 2, __value);
if (!__fail()) {
- if (!__in_range(__value, 0, 99))
+ if (!__is_in_range(__value, 0, 99))
__state_ |= ios_base::failbit;
else
__assign(__f.__iso_year_of_century_, __value, __fields_set::__iso_year_of_century);
@@ -754,7 +754,7 @@ class __from_stream_parser {
int __weekday = 0;
__read_unsigned(__has_width ? __width : 1, __weekday);
if (!__fail()) {
- if (!__in_range(__weekday, 1, 7))
+ if (!__is_in_range(__weekday, 1, 7))
__state_ |= ios_base::failbit;
else
__assign(__f.__weekday_, __weekday % 7, __fields_set::__weekday);
@@ -886,7 +886,7 @@ _LIBCPP_HIDE_FROM_ABI inline bool __try_get_year(const __fields_storage& __f, in
int64_t __year{};
if (__f.__has(__fields_set::__year_of_century)) {
- if (!__in_range(__f.__year_of_century_, 0, 99))
+ if (!__is_in_range(__f.__year_of_century_, 0, 99))
return false;
// Use an explicit century or full year before falling back to the default century.
@@ -913,7 +913,7 @@ _LIBCPP_HIDE_FROM_ABI inline bool __try_get_year(const __fields_storage& __f, in
return false;
}
- if (!__in_range(__year, static_cast<int>((year::min)()), static_cast<int>((year::max)())))
+ if (!__is_in_range(__year, static_cast<int>((year::min)()), static_cast<int>((year::max)())))
return false;
__result = static_cast<int>(__year);
@@ -925,7 +925,7 @@ _LIBCPP_HIDE_FROM_ABI inline bool __try_get_year(const __fields_storage& __f, in
_LIBCPP_HIDE_FROM_ABI inline bool __iso_week_to_sys_days(int64_t __g, int __v, weekday __wd, sys_days& __result) {
const int __min_year = static_cast<int>((year::min)());
const int __max_year = static_cast<int>((year::max)());
- if (!__in_range(__g, __min_year - 1, __max_year + 1) || !__in_range(__v, 1, 53))
+ if (!__is_in_range(__g, __min_year - 1, __max_year + 1) || !__is_in_range(__v, 1, 53))
return false;
const days __year_length{__g % 4 == 0 && (__g % 100 != 0 || __g % 400 == 0) ? 366 : 365};
@@ -969,7 +969,7 @@ _LIBCPP_HIDE_FROM_ABI inline bool __iso_week_to_sys_days(int64_t __g, int __v, w
// The caller must supply a valid year.
_LIBCPP_HIDE_FROM_ABI inline bool
__week_to_sys_days(int __year, int __week, weekday __first, weekday __wd, sys_days& __result) {
- if (!__in_range(__week, 0, 53))
+ if (!__is_in_range(__week, 0, 53))
return false;
// Compute days from 1970-01-01 to __year/__week/__wd in four parts:
@@ -993,7 +993,7 @@ _LIBCPP_HIDE_FROM_ABI inline bool __make_date(const __fields_storage& __f, year_
if (int __year{}; __try_get_year(__f, __year)) {
// Calendar date: %Y %m %d, also supplied by formats such as %F, %D, or %x.
if (__f.__has(__fields_set::__month | __fields_set::__day)) {
- if (!__in_range(__f.__month_, 1, 12) || !__in_range(__f.__day_, 1, 31))
+ if (!__is_in_range(__f.__month_, 1, 12) || !__is_in_range(__f.__day_, 1, 31))
return false;
const year_month_day __ymd{
@@ -1006,7 +1006,7 @@ _LIBCPP_HIDE_FROM_ABI inline bool __make_date(const __fields_storage& __f, year_
// Ordinal date: %Y %j.
if (__f.__has(__fields_set::__day_of_year)) {
- if (!__in_range(__f.__day_of_year_, 1, year{__year}.is_leap() ? 366 : 365))
+ if (!__is_in_range(__f.__day_of_year_, 1, year{__year}.is_leap() ? 366 : 365))
return false;
__result = year_month_day{sys_days{year{__year} / January / 1} + days{__f.__day_of_year_ - 1}};
@@ -1015,7 +1015,7 @@ _LIBCPP_HIDE_FROM_ABI inline bool __make_date(const __fields_storage& __f, year_
// Week date: %Y with %U or %W and a weekday (%a/%A/%u/%w).
if (__f.__has(__fields_set::__weekday) && __f.__has_any(__fields_set::__week_sun | __fields_set::__week_mon)) {
- if (!__in_range(__f.__weekday_, 0, 6))
+ if (!__is_in_range(__f.__weekday_, 0, 6))
return false;
const bool __use_sunday = __f.__has(__fields_set::__week_sun);
sys_days __date{};
@@ -1050,7 +1050,7 @@ _LIBCPP_HIDE_FROM_ABI inline bool __make_date(const __fields_storage& __f, year_
__iso_year = __f.__iso_year_;
}
sys_days __date{};
- if (!__in_range(__f.__weekday_, 0, 6) ||
+ if (!__is_in_range(__f.__weekday_, 0, 6) ||
!__iso_week_to_sys_days(__iso_year, __f.__iso_week_, weekday{static_cast<unsigned>(__f.__weekday_)}, __date))
return false;
@@ -1077,7 +1077,7 @@ _LIBCPP_HIDE_FROM_ABI inline bool __validate_date(const __fields_storage& __f, c
// Without %C or %Y, %y denotes a year in [1969, 2068], not just matching last digits.
if (__f.__has(__fields_set::__year_of_century) && !__f.__has_any(__fields_set::__century | __fields_set::__year) &&
- !__in_range(__year, 1969, 2068))
+ !__is_in_range(__year, 1969, 2068))
return false;
// Month (%m/%b/%B/%h), day (%d/%e), and weekday (%a/%A/%u/%w).
@@ -1154,7 +1154,7 @@ _LIBCPP_HIDE_FROM_ABI inline bool __try_get_hour(const __fields_storage& __f, in
if (__f.__has(__fields_set::__hours)) {
// %H determines the hour; check agreement with %I and %p if supplied.
__hour = __f.__hours_;
- if (!__in_range(__hour, 0, 23))
+ if (!__is_in_range(__hour, 0, 23))
return false;
if (__f.__has(__fields_set::__hour12)) {
const int __hour12 = static_cast<int>(chrono::make12(hours{__hour}).count());
@@ -1165,7 +1165,7 @@ _LIBCPP_HIDE_FROM_ABI inline bool __try_get_hour(const __fields_storage& __f, in
return false;
} else if (__f.__has(__fields_set::__hour12)) {
// Without %H, %I requires %p to distinguish AM from PM.
- if (!__in_range(__f.__hour12_, 1, 12) || !__f.__has(__fields_set::__am_pm))
+ if (!__is_in_range(__f.__hour12_, 1, 12) || !__f.__has(__fields_set::__am_pm))
return false;
__hour = static_cast<int>(chrono::make24(hours{__f.__hour12_}, __f.__is_pm_).count());
} else {
@@ -1178,11 +1178,12 @@ _LIBCPP_HIDE_FROM_ABI inline bool __try_get_hour(const __fields_storage& __f, in
}
_LIBCPP_HIDE_FROM_ABI inline bool __validate_minute(const __fields_storage& __f) {
- return !__f.__has(__fields_set::__minutes) || __in_range(__f.__minutes_, 0, 59);
+ return !__f.__has(__fields_set::__minutes) || __is_in_range(__f.__minutes_, 0, 59);
}
_LIBCPP_HIDE_FROM_ABI inline bool __validate_second(const __fields_storage& __f, int __max_second) {
- return (!__f.__has(__fields_set::__seconds) || __in_range(__f.__seconds_, 0, __max_second)) && __f.__subseconds_ >= 0;
+ return (!__f.__has(__fields_set::__seconds) || __is_in_range(__f.__seconds_, 0, __max_second)) &&
+ __f.__subseconds_ >= 0;
}
// Validate the time-of-day fields and combine their whole seconds only on success.
@@ -1422,7 +1423,7 @@ _LIBCPP_HIDE_FROM_ABI inline bool __from_fields(const __fields_storage& __f, day
if (!__f.__has_exactly(__fields_set::__day))
return false;
- if (!__in_range(__f.__day_, 1, 31))
+ if (!__is_in_range(__f.__day_, 1, 31))
return false;
__result = day{static_cast<unsigned>(__f.__day_)};
@@ -1433,7 +1434,7 @@ _LIBCPP_HIDE_FROM_ABI inline bool __from_fields(const __fields_storage& __f, mon
if (!__f.__has_exactly(__fields_set::__month))
return false;
- if (!__in_range(__f.__month_, 1, 12))
+ if (!__is_in_range(__f.__month_, 1, 12))
return false;
__result = month{static_cast<unsigned>(__f.__month_)};
@@ -1457,7 +1458,7 @@ _LIBCPP_HIDE_FROM_ABI inline bool __from_fields(const __fields_storage& __f, wee
if (!__f.__has_exactly(__fields_set::__weekday))
return false;
- if (!__in_range(__f.__weekday_, 0, 6))
+ if (!__is_in_range(__f.__weekday_, 0, 6))
return false;
__result = weekday{static_cast<unsigned>(__f.__weekday_)};
@@ -1472,7 +1473,7 @@ _LIBCPP_HIDE_FROM_ABI inline bool __from_fields(const __fields_storage& __f, mon
int __day{};
if (__f.__has(__fields_set::__day_of_year)) {
// Without a year, only days through February 28 are unambiguous.
- if (!__in_range(__f.__day_of_year_, 1, 59))
+ if (!__is_in_range(__f.__day_of_year_, 1, 59))
return false;
__month = __f.__day_of_year_ <= 31 ? 1 : 2;
__day = __f.__day_of_year_ <= 31 ? __f.__day_of_year_ : __f.__day_of_year_ - 31;
@@ -1486,7 +1487,7 @@ _LIBCPP_HIDE_FROM_ABI inline bool __from_fields(const __fields_storage& __f, mon
__day = __f.__day_;
}
- if (!__in_range(__month, 1, 12) || !__in_range(__day, 1, 31))
+ if (!__is_in_range(__month, 1, 12) || !__is_in_range(__day, 1, 31))
return false;
month_day __md{month{static_cast<unsigned>(__month)}, day{static_cast<unsigned>(__day)}};
@@ -1506,7 +1507,7 @@ _LIBCPP_HIDE_FROM_ABI inline bool __from_fields(const __fields_storage& __f, yea
if (!__try_get_year(__f, __year))
return false;
- if (!__in_range(__f.__month_, 1, 12))
+ if (!__is_in_range(__f.__month_, 1, 12))
return false;
__result = year_month{year{__year}, month{static_cast<unsigned>(__f.__month_)}};
>From 1b38c37472f5b4a595d2f3a04400c4d5fa8107c3 Mon Sep 17 00:00:00 2001
From: Emmett <emmettzhang2020 at outlook.com>
Date: Tue, 20 Oct 2026 11:47:43 +0800
Subject: [PATCH 5/5] Refine test cases
---
libcxx/test/libcxx/time/from_stream.pass.cpp | 211 ++++---
.../std/time/time.parse/from_stream.pass.cpp | 568 ++++++++++++------
.../test/std/time/time.parse/parse.pass.cpp | 63 +-
3 files changed, 576 insertions(+), 266 deletions(-)
diff --git a/libcxx/test/libcxx/time/from_stream.pass.cpp b/libcxx/test/libcxx/time/from_stream.pass.cpp
index 9d7a58046c5a09..254fa7e0d4153c 100644
--- a/libcxx/test/libcxx/time/from_stream.pass.cpp
+++ b/libcxx/test/libcxx/time/from_stream.pass.cpp
@@ -6,7 +6,7 @@
//
//===----------------------------------------------------------------------===//
-// UNSUPPORTED: c++03, c++11, c++14, c++17
+// REQUIRES: std-at-least-c++20
// UNSUPPORTED: no-localization
#include <cassert>
@@ -24,6 +24,8 @@
template <class CharT>
void test_read_digits() {
+ using Traits = std::char_traits<CharT>;
+ constexpr auto max_value = std::numeric_limits<std::uint64_t>::max();
struct TestCase {
std::basic_string<CharT> input;
unsigned width;
@@ -31,18 +33,37 @@ void test_read_digits() {
std::uint64_t value;
unsigned count;
bool overflow;
- CharT next;
+ typename Traits::int_type next;
+ std::ios_base::iostate state = std::ios_base::goodbit;
};
const TestCase cases[] = {
- // Read ordinary digits, stopping at the width or a non-digit.
- {STR("12345"), 2, 12, 12, 2, false, CharT('3')},
- {STR("0012X"), 5, 12, 12, 4, false, CharT('X')},
- // No digits are consumed when the width is zero or the input is not numeric.
+ // No digits consumed.
+ // A zero width consumes nothing.
{STR("12"), 0, 12, 0, 0, false, CharT('1')},
+ // An initial non-digit consumes nothing.
{STR("X"), 1, 12, 0, 0, false, CharT('X')},
- // Stop as soon as the next digit would exceed the limit.
+ // Empty input reports EOF without consuming any digits.
+ {STR(""), 1, 12, 0, 0, false, Traits::eof(), std::ios_base::eofbit},
+
+ // Successful reads.
+ // Stop at the width, leaving '3' unread.
+ {STR("12345"), 2, 12, 12, 2, false, CharT('3')},
+ // Accept leading zeros and stop before the non-digit.
+ {STR("0012X"), 5, 12, 12, 4, false, CharT('X')},
+ // Reaching EOF before the width preserves the parsed value and reports EOF.
+ {STR("12"), 3, 12, 12, 2, false, Traits::eof(), std::ios_base::eofbit},
+
+ // Overflow.
+ // After reaching the limit, consume '3' and report overflow; retain 12.
{STR("12345"), 5, 12, 12, 3, true, CharT('4')},
+ // Exceed the limit without reaching it first; consume '3' and retain 1.
{STR("13X"), 3, 12, 1, 2, true, CharT('X')},
+
+ // uint64_t boundaries.
+ // Accept the largest uint64_t value without overflow.
+ {STR("18446744073709551615X"), 21, max_value, max_value, 20, false, CharT('X')},
+ // One above the largest uint64_t value overflows; retain the prefix before the final digit.
+ {STR("18446744073709551616X"), 21, max_value, max_value / 10, 20, true, CharT('X')},
};
for (const auto& c : cases) {
std::basic_istringstream<CharT> stream(c.input);
@@ -52,112 +73,150 @@ void test_read_digits() {
assert(result.__value == c.value);
assert(result.__digits_read == c.count);
assert(result.__overflow == c.overflow);
- assert(state == std::ios_base::goodbit); // Reporting failure is the caller's responsibility.
- assert(stream.peek() == std::char_traits<CharT>::to_int_type(c.next));
+ assert(state == c.state);
+ assert(stream.peek() == c.next);
}
}
+template <class CharT>
void test_read_signed() {
+ using Traits = std::char_traits<CharT>;
+ constexpr int sentinel = 42;
+ constexpr int min_value = std::numeric_limits<int>::min();
+ constexpr int max_value = std::numeric_limits<int>::max();
+ const auto make_input = [](std::int64_t value) {
+ const auto input = std::to_string(value);
+ return std::basic_string<CharT>(input.begin(), input.end());
+ };
struct TestCase {
- std::string input;
+ std::basic_string<CharT> input;
int expected;
+ bool expected_fail = false;
};
const TestCase cases[] = {
- // Ordinary values, with and without an explicit sign.
- {"123", 123},
- {"+123", 123},
- {"-123", -123},
+ // Ordinary values.
+ // Accept digits without a sign.
+ {STR("123"), 123},
+ // Accept an explicit positive sign.
+ {STR("+123"), 123},
+ // Apply a negative sign.
+ {STR("-123"), -123},
+
+ // Integer boundaries.
+ // Accept the largest positive int.
+ {make_input(max_value), max_value},
// The negative limit has no positive counterpart in int.
- {std::to_string(std::numeric_limits<int>::min()), std::numeric_limits<int>::min()},
+ {make_input(min_value), min_value},
+ // Reject a value above INT_MAX and preserve the output.
+ {make_input(static_cast<std::int64_t>(max_value) + 1), sentinel, true},
+ // Reject a value below INT_MIN and preserve the output.
+ {make_input(static_cast<std::int64_t>(min_value) - 1), sentinel, true},
+
+ // Missing digits preserve the output.
+ // A negative sign alone is not an integer.
+ {STR("-"), sentinel, true},
};
for (const auto& c : cases) {
- std::istringstream stream{c.input};
- int result = -1;
+ std::basic_istringstream<CharT> stream{c.input};
+ int result = sentinel;
std::ios_base::iostate state = std::ios_base::goodbit;
- std::chrono::__from_stream_parser<char, std::char_traits<char>> parser{stream, state};
- parser.__read_signed(static_cast<unsigned>(c.input.size()), result);
- assert(!(state & (std::ios_base::failbit | std::ios_base::badbit)));
+ std::chrono::__from_stream_parser<CharT, Traits> parser{stream, state};
+ parser.__read_signed(std::numeric_limits<unsigned>::max(), result);
+ assert(bool(state & std::ios_base::failbit) == c.expected_fail);
+ assert(!(state & std::ios_base::badbit));
assert(result == c.expected);
+ assert(stream.peek() == Traits::eof());
}
}
-void test_field_queries() {
- using Fields = std::chrono::__fields_storage;
- using Parts = std::chrono::__fields_set;
- constexpr auto check = [] {
- Fields fields;
- assert(!fields.__has(Parts::__day));
- assert(!fields.__has_any(Parts::__day | Parts::__month));
- fields.__set(Parts::__utc_offset);
- assert(fields.__has(Parts::__utc_offset));
- assert(!fields.__has_exactly(Parts::__day));
- fields.__set(Parts::__day);
- // __has_only and __has_exactly ignore the UTC offset field.
- assert(fields.__has_only(Parts::__day));
- assert(fields.__has_exactly(Parts::__day));
- assert(fields.__has(Parts::__day | Parts::__utc_offset));
- assert(fields.__has_any(Parts::__day | Parts::__month));
- assert(!fields.__has(Parts::__day | Parts::__month));
- assert(fields.__has_only(Parts::__day | Parts::__month));
- assert(fields.__has_exactly(Parts::__day, Parts::__month));
- fields.__set(Parts::__month);
- assert(!fields.__has_only(Parts::__day));
- assert(!fields.__has_exactly(Parts::__day));
- assert(fields.__has_exactly(Parts::__day, Parts::__month));
- assert(fields.__has_exactly(Parts::__day | Parts::__month));
- return true;
- };
- static_assert(check());
- assert(check());
+template <class CharT>
+void test_width_overflow() {
+ std::basic_ostringstream<CharT> format;
+ format.imbue(std::locale::classic());
+ // Append a zero to UINT_MAX to make the format width overflow unsigned.
+ format << CharT('%') << std::numeric_limits<unsigned>::max() << STR("0S");
+
+ // libc++ stores the width in unsigned and rejects overflow before reading input.
+ std::basic_istringstream<CharT> stream{STR("1.25!")};
+ std::chrono::milliseconds result{};
+ std::chrono::from_stream(stream, format.str().c_str(), result);
+ assert(stream.fail());
+ // Clear the failure state and verify that no input was consumed.
+ stream.clear();
+ assert(stream.peek() == CharT('1'));
}
template <class CharT, class T>
-void check_repeated_field(const std::basic_string<CharT>& format,
- const std::basic_string<CharT>& matching,
- const std::basic_string<CharT>& conflicting,
- T expected) {
- {
- std::basic_istringstream<CharT> stream(matching);
- stream.imbue(std::locale::classic());
- T result{};
- std::chrono::from_stream(stream, format.c_str(), result);
- assert(!stream.fail());
- assert(result == expected);
- }
- {
- std::basic_istringstream<CharT> stream(conflicting);
- stream.imbue(std::locale::classic());
- T result{};
- std::chrono::from_stream(stream, format.c_str(), result);
- assert(stream.fail());
- }
+void check(const std::basic_string<CharT>& input, const std::basic_string<CharT>& format, T expected) {
+ std::basic_istringstream<CharT> stream(input);
+ stream.imbue(std::locale::classic());
+ T result{};
+ std::chrono::from_stream(stream, format.c_str(), result);
+ assert(!stream.fail());
+ assert(result == expected);
+}
+
+template <class T, class CharT>
+void check_failure(const std::basic_string<CharT>& input, const std::basic_string<CharT>& format) {
+ std::basic_istringstream<CharT> stream(input);
+ stream.imbue(std::locale::classic());
+ T result{};
+ std::chrono::from_stream(stream, format.c_str(), result);
+ assert(stream.fail());
+}
+
+template <class CharT>
+void test_whitespace() {
+ // libc++ does not implicitly skip whitespace before %e.
+ check_failure<std::chrono::year_month_day>(STR("2026-07- 7"), STR("%Y-%m-%e"));
}
template <class CharT>
void test_repeated_fields() {
using namespace std::chrono;
// libc++ accepts equal repetitions and rejects conflicting values.
- check_repeated_field(STR("%d %d"), STR("01 01"), STR("01 02"), day{1});
+ check(STR("01 01"), STR("%d %d"), day{1});
+ check_failure<day>(STR("01 02"), STR("%d %d"));
// Compound formats and locale formats share the same parsed fields.
- check_repeated_field(STR("%F %x"), STR("2026-07-20 07/20/26"), STR("2026-07-20 07/21/26"), 2026y / July / 20);
+ check(STR("2026-07-20 07/20/26"), STR("%F %x"), 2026y / July / 20);
+ check_failure<year_month_day>(STR("2026-07-20 07/21/26"), STR("%F %x"));
+
+ // Reject conflicting calendar, ordinal, and ISO week date information.
+ check_failure<year_month_day>(STR("2026-02-15 Mon"), STR("%F %a"));
+ check_failure<year_month_day>(STR("2026 032 03"), STR("%Y %j %m"));
+ check_failure<year_month_day>(STR("2020-W53-5 2020"), STR("%G-W%V-%u %Y"));
+
+ // Different representations of the year or hour must agree.
+ check_failure<year>(STR("2026 25"), STR("%Y %y"));
+ check_failure<minutes>(STR("14 01"), STR("%H %I"));
+
+ // Repeated seconds must agree, including their fractional parts.
+ check(STR("1.25 1.25"), STR("%S %S"), 1250ms);
+ check_failure<milliseconds>(STR("1.25 1.5"), STR("%S %S"));
- // Compare fractional values, not their spelling; locale seconds have no fraction.
- check_repeated_field(STR("%S %S"), STR("1.25 1.250"), STR("1.25 1.5"), 1250ms);
- check_repeated_field(STR("%S %X"), STR("1.0 00:00:01"), STR("1.5 00:00:01"), 1000ms);
+ // Locale seconds have no fraction.
+ check(STR("1.0 00:00:01"), STR("%S %X"), 1000ms);
+ check_failure<milliseconds>(STR("1.5 00:00:01"), STR("%S %X"));
// Zone fields are checked even without output pointers.
- check_repeated_field(STR("%d %z %Ez"), STR("01 +0100 +1:00"), STR("01 +0100 +2:00"), day{1});
- check_repeated_field(STR("%d %Z %Z"), STR("01 UTC UTC"), STR("01 UTC GMT"), day{1});
+ check(STR("01 +0100 +1:00"), STR("%d %z %Ez"), day{1});
+ check_failure<day>(STR("01 +0100 +2:00"), STR("%d %z %Ez"));
+ check(STR("01 UTC UTC"), STR("%d %Z %Z"), day{1});
+ check_failure<day>(STR("01 UTC GMT"), STR("%d %Z %Z"));
}
int main(int, char**) {
- test_read_signed();
- test_field_queries();
test_read_digits<char>();
+ test_read_signed<char>();
+ test_width_overflow<char>();
+ test_whitespace<char>();
test_repeated_fields<char>();
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
test_read_digits<wchar_t>();
+ test_read_signed<wchar_t>();
+ test_width_overflow<wchar_t>();
+ test_whitespace<wchar_t>();
test_repeated_fields<wchar_t>();
#endif
return 0;
diff --git a/libcxx/test/std/time/time.parse/from_stream.pass.cpp b/libcxx/test/std/time/time.parse/from_stream.pass.cpp
index fa722f7093c7e5..676a37ee55cfb6 100644
--- a/libcxx/test/std/time/time.parse/from_stream.pass.cpp
+++ b/libcxx/test/std/time/time.parse/from_stream.pass.cpp
@@ -6,17 +6,101 @@
//
//===----------------------------------------------------------------------===//
-// UNSUPPORTED: c++03, c++11, c++14, c++17
+// REQUIRES: std-at-least-c++20
// UNSUPPORTED: no-localization
// XFAIL: availability-tzdb-missing && !libcpp-has-no-experimental-tzdb && !no-tzdb && !no-filesystem
// <chrono>
-// from_stream overloads for calendar types, durations, and time points.
+//
+// Tested from_stream overloads (C++20):
+//
+// template<class charT, class traits, class Rep, class Period, class Alloc = allocator<charT>>
+// 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);
+//
+// template<class charT, class traits, class Duration, class 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);
+//
+// template<class charT, class traits, class Duration, class Alloc = allocator<charT>>
+// 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);
+//
+// template<class charT, class traits, class Duration, class Alloc = allocator<charT>>
+// 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);
+//
+// template<class charT, class traits, class Duration, class Alloc = allocator<charT>>
+// 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);
+//
+// template<class charT, class traits, class Duration, class Alloc = allocator<charT>>
+// 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);
+//
+// template<class charT, class traits, class Duration, class 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);
+//
+// template<class charT, class traits, class Alloc = allocator<charT>>
+// 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);
+//
+// template<class charT, class traits, class Alloc = allocator<charT>>
+// 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);
+//
+// template<class charT, class traits, class Alloc = allocator<charT>>
+// 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);
+//
+// template<class charT, class traits, class Alloc = allocator<charT>>
+// 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);
+//
+// template<class charT, class traits, class Alloc = allocator<charT>>
+// 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);
+//
+// template<class charT, class traits, class Alloc = allocator<charT>>
+// 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);
+//
+// template<class charT, class traits, class Alloc = allocator<charT>>
+// 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);
#include <cassert>
#include <chrono>
#include <initializer_list>
-#include <limits>
#include <locale>
#include <ratio>
#include <sstream>
@@ -25,6 +109,8 @@
#include "make_string.h"
#include "test_macros.h"
+using namespace std::chrono;
+
#define STR(S) MAKE_STRING(CharT, S)
template <class CharT, class T>
@@ -38,201 +124,236 @@ void check(const std::basic_string<CharT>& input, const std::basic_string<CharT>
}
template <class T, class CharT>
-void check_failure(const std::basic_string<CharT>& input, const std::basic_string<CharT>& format) {
+void check_failure(const std::basic_string<CharT>& input, const std::basic_string<CharT>& format, T initial = T{}) {
std::basic_istringstream<CharT> stream(input);
stream.imbue(std::locale::classic());
- T result{};
+ T result = initial;
std::chrono::from_stream(stream, format.c_str(), result);
assert(stream.fail());
+ assert(result == initial);
}
+// Test parsing day, month, year, weekday, month_day, year_month, and year_month_day.
+// Cover numeric and named calendar fields, invalid values, missing fields,
+// and fields that the target type cannot represent.
template <class CharT>
void test_calendar_types() {
- using namespace std::chrono;
+ // Parse a numeric day of the month.
check(STR("15"), STR("%d"), day{15});
- check(STR(" 15"), STR("%n%e"), day{15});
- // Zone fields are consumed even when their output pointers are null.
- check(STR("15 UTC +0130"), STR("%d %Z %z"), day{15});
+ // Parse the day with %e, which is equivalent to %d.
+ check(STR("15"), STR("%e"), day{15});
+ // A day cannot represent a month field.
check_failure<day>(STR("02-15"), STR("%m-%d"));
+ // A day cannot represent an hour field, even when its value is zero.
check_failure<day>(STR("15 00"), STR("%d %H"));
- check_failure<day>(STR("32"), STR("%d"));
+ // Day 0 is below the valid range.
+ check_failure<day>(STR("0"), STR("%d"));
+ // Day 32 is outside the valid range; preserve the original day on failure.
+ check_failure<day>(STR("32"), STR("%d"), day{7});
+ // Parse a numeric month.
check(STR("02"), STR("%m"), February);
+ // Parse an abbreviated month name in the classic locale.
check(STR("Feb"), STR("%b"), February);
+ // Parse a full month name.
+ check(STR("July"), STR("%B"), July);
+ // %B also accepts an abbreviated month name, ignoring case.
+ check(STR("jUl"), STR("%B"), July);
+ // %h is equivalent to %b.
+ check(STR("Jul"), STR("%h"), July);
+ // Month 13 is outside the valid range.
check_failure<month>(STR("13"), STR("%m"));
+ // Parse a calendar year.
check(STR("2026"), STR("%Y"), 2026y);
+ // Parse an abbreviated weekday name in the classic locale.
check(STR("Mon"), STR("%a"), Monday);
+ // %a also accepts a full weekday name, ignoring case.
+ check(STR("mOnDaY"), STR("%a"), Monday);
+ // Parse a full weekday name.
+ check(STR("Monday"), STR("%A"), Monday);
+ // ISO weekday numbering assigns 1 to Monday.
check(STR("1"), STR("%u"), Monday);
+ // Unlike %u, %w only accepts weekday numbers from 0 to 6.
check_failure<weekday>(STR("7"), STR("%w"));
+ // Parse a month and day without a year.
check(STR("02-15"), STR("%m-%d"), February / 15);
+ // February 29 is valid for month_day because no year is specified.
check(STR("02-29"), STR("%m-%d"), February / 29);
- check(STR("32"), STR("%j"), February / 1);
- check_failure<month_day>(STR("60"), STR("%j"));
+ // February 30 is invalid in every year.
check_failure<month_day>(STR("02-30"), STR("%m-%d"));
+ // A month alone does not supply the required day.
check_failure<month_day>(STR("02"), STR("%m"));
+ // Parse a year and month without a day.
check(STR("2026-02"), STR("%Y-%m"), 2026y / February);
+ // Reject an invalid month in a year_month result.
check_failure<year_month>(STR("2026-13"), STR("%Y-%m"));
- const year_month_day date = 2026y / February / 15;
- check(STR("2026-02-15"), STR("%F"), date);
- check(STR("2026 046"), STR("%Y %j"), date);
- check(STR("2026-W07-7"), STR("%G-W%V-%u"), date);
- check(STR("68 69-W01-1"), STR("%y %g-W%V-%u"), 2068y / December / 31);
- check(STR("-32768-W53-6"), STR("%6G-W%V-%u"), (year::min)() / January / 1);
- check(STR("2026 07 0"), STR("%Y %U %w"), date);
- check(STR("2026 06 7"), STR("%Y %W %u"), date);
- check(STR("2026-02-15 Sun"), STR("%F %a"), date);
- check_failure<year_month_day>(STR("2026-02-15 Mon"), STR("%F %a"));
+ // Parse a complete calendar date with %F.
+ check(STR("2026-02-15"), STR("%F"), 2026y / February / 15);
+ // %e accepts a single-digit day without padding.
+ check(STR("2026-07-7"), STR("%Y-%m-%e"), 2026y / July / 7);
+ // Reject an invalid day in a complete calendar date.
check_failure<year_month_day>(STR("2026-02-30"), STR("%F"));
+ // February 29 is valid in a leap year.
+ check(STR("2024-02-29"), STR("%F"), 2024y / February / 29);
+ // February 29 is invalid in a non-leap year.
+ check_failure<year_month_day>(STR("2023-02-29"), STR("%F"));
+ // A year and month do not supply the required day.
check_failure<year_month_day>(STR("2026-02"), STR("%Y-%m"));
- check_failure<year_month_day>(STR("2021-W53-5"), STR("%G-W%V-%u"));
}
+// Test compound date/time directives against their component fields.
template <class CharT>
-void test_format() {
- using namespace std::chrono;
- const sys_seconds date = sys_days{2026y / July / 20};
+void test_compound_specifiers() {
+ const sys_days date = sys_days{2026y / July / 20};
const sys_seconds date_time = date + 13h + 45min + 30s;
- // Individual and compound directives.
+ // Parse the explicit fields represented by %F and %T.
check(STR("2026-07-20 13:45:30"), STR("%Y-%m-%d %H:%M:%S"), date_time);
- check(STR("2026-07-7"), STR("%Y-%m-%e"), sys_seconds{sys_days{2026y / July / 7}});
+ // %F and %T combine the date and time fields.
check(STR("2026-07-20 13:45:30"), STR("%F %T"), date_time);
- check(STR("2026-07-20 13:45"), STR("%F %R"), date + 13h + 45min);
- check(STR("07/20/26"), STR("%D"), date);
- check(STR("30"), STR("%S"), 30s);
+ // %R supplies hours and minutes.
+ check(STR("2026-07-20 13:45"), STR("%F %R"), sys_time<minutes>{date} + 13h + 45min);
+ // %T combines hours, minutes, and seconds into a duration.
+ check(STR("01:30:00"), STR("%T"), 5400s);
+ // %D combines the month, day, and two-digit year.
+ check(STR("07/20/26"), STR("%D"), year_month_day{date});
+}
- // Named fields and locale-specific date/time formats in the classic locale.
- check(STR("Monday"), STR("%A"), Monday);
- check(STR("July"), STR("%B"), July);
- check(STR("Jul"), STR("%h"), July);
+// Test the classic locale's date, time, and date-and-time representations.
+template <class CharT>
+void test_locale_formats() {
+ const year_month_day date = 2026y / July / 20;
+ const sys_seconds date_time = sys_days{date} + 13h + 45min + 30s;
+
+ // Locale-specific date/time formats in the classic locale.
check(STR("Mon Jul 20 13:45:30 2026"), STR("%c"), date_time);
- check_failure<sys_seconds>(STR("Tue Jul 20 13:45:30 2026"), STR("%c"));
+ // %x parses the classic locale's month/day/two-digit-year representation.
check(STR("07/20/26"), STR("%x"), date);
+ // %X parses the classic locale's hours, minutes, and seconds.
+ check(STR("01:30:00"), STR("%X"), 5400s);
+ // Parse the classic locale's twelve-hour time with an AM designator.
+ check(STR("01:30:00 AM"), STR("%r"), 5400s);
+}
- // A width on %F applies only to %Y.
- check(STR("002026-07-20"), STR("%6F"), date);
- check(STR("26-W30-1"), STR("%g-W%V-%u"), date);
+// Test literal matching and whitespace consumption in the input.
+template <class CharT>
+void test_literals_and_whitespace() {
+ const year_month_day date = 2026y / July / 20;
- // Literals and whitespace, including a match of zero whitespace characters.
+ // %% matches a literal percent sign.
check(STR("2026-07-20%"), STR("%F%%"), date);
+ // %t matches a tab between numeric fields.
check(STR("2026\t07\t20"), STR("%Y%t%m%t%d"), date);
+ // %t can match no whitespace before a literal.
+ check(STR("X15"), STR("%tX%d"), day{15});
+ // A format space matches multiple input spaces.
check(STR(" 2026-07-20"), STR(" %F"), date);
+ // A format space can also match no whitespace.
check(STR("2026-07-20"), STR(" %Y-%m-%d"), date);
+ // %n consumes one whitespace character before the day.
+ check(STR(" 15"), STR("%n%e"), day{15});
+ // %n fails when no whitespace precedes the literal.
+ check_failure<day>(STR("X15"), STR("%nX%d"));
- check_failure<sys_seconds>(STR("2026/07/20"), STR("%Y-%m-%d"));
- check_failure<sys_seconds>(STR("13:45:30"), STR("%H:%M:%S"));
- check_failure<sys_seconds>(STR("2026-07-xx"), STR("%Y-%m-%d"));
- check_failure<sys_seconds>(STR("2026-07- 7"), STR("%Y-%m-%e"));
-}
-
-template <class CharT>
-void test_width_overflow() {
- using namespace std::chrono;
- std::basic_ostringstream<CharT> format;
- format << CharT('%') << std::numeric_limits<unsigned>::max() << STR("0S");
-
- // Reject an overflowing format width before reading the input.
- std::basic_istringstream<CharT> stream{STR("1.25!")};
- milliseconds result{};
- from_stream(stream, format.str().c_str(), result);
- assert(stream.fail());
- stream.clear();
- assert(stream.peek() == CharT('1'));
+ // Literal separators must match.
+ check_failure<year_month_day>(STR("2026/07/20"), STR("%Y-%m-%d"));
}
+// Test day-of-year and week-based dates, year boundaries, and matching date fields.
template <class CharT>
-void test_date_consistency() {
- using namespace std::chrono;
+void test_date_resolution() {
+ const year_month_day date = 2026y / February / 15;
const year_month_day feb1 = 2026y / February / 1;
const year_month_day jan1 = 2021y / January / 1;
- // An ordinal date must agree with the supplied month.
+ // Day-of-year 32 is February 1 in both leap and non-leap years.
+ check(STR("32"), STR("%j"), February / 1);
+ // Day-of-year 60 needs a year to distinguish February 29 from March 1.
+ check_failure<month_day>(STR("60"), STR("%j"));
+ // Convert a year and day-of-year to a calendar date.
+ check(STR("2026 046"), STR("%Y %j"), date);
+ // Parse an ordinal date with a matching month.
check(STR("2026 032 02"), STR("%Y %j %m"), feb1);
- check_failure<year_month_day>(STR("2026 032 03"), STR("%Y %j %m"));
- // An ISO week date must agree with the calendar year, even across a year boundary.
+ // Convert an ISO week year, week number, and weekday to a calendar date.
+ check(STR("2026-W07-7"), STR("%G-W%V-%u"), date);
+ // A calendar year supplies context for a two-digit ISO week year.
+ check(STR("2026 26-W30-1"), STR("%Y %g-W%V-%u"), 2026y / July / 20);
+ // Calendar year 2068 ends in ISO week year 2069, despite %y's century cutoff at 69.
+ check(STR("68 69-W01-1"), STR("%y %g-W%V-%u"), 2068y / December / 31);
+ // The earliest supported calendar year begins in the preceding ISO week year.
+ check(STR("-32768-W53-6"), STR("%6G-W%V-%u"), (year::min)() / January / 1);
+ // ISO week years and calendar years can differ across a year boundary.
check(STR("2020-W53-5 2021"), STR("%G-W%V-%u %Y"), jan1);
- check_failure<year_month_day>(STR("2020-W53-5 2020"), STR("%G-W%V-%u %Y"));
+ // Match a full ISO week year with its last two digits.
check(STR("1926-W01-1 26"), STR("%G-W%V-%u %g"), 1926y / January / 4);
+ // ISO week year 2021 has no week 53.
+ check_failure<year_month_day>(STR("2021-W53-5"), STR("%G-W%V-%u"));
+
+ // Parse a Sunday-based week number with Sunday numbered 0.
+ check(STR("2026 07 0"), STR("%Y %U %w"), date);
+ // Parse a Monday-based week number with ISO Sunday numbered 7.
+ check(STR("2026 06 7"), STR("%Y %W %u"), date);
+ // Accept a date accompanied by its matching weekday.
+ check(STR("2026-02-15 Sun"), STR("%F %a"), date);
}
+// Test E-modified fields in the classic locale, including UTC offsets.
template <class CharT>
void test_modifier_E() {
- using namespace std::chrono;
- const sys_seconds date = sys_days{2026y / July / 20};
+ const year_month_day date = 2026y / July / 20;
+ const sys_seconds date_time = sys_days{date} + 13h + 45min + 30s;
- check(STR("Mon Jul 20 13:45:30 2026"), STR("%Ec"), date + 13h + 45min + 30s);
+ // %Ec parses the same date-and-time representation as %c in the classic locale.
+ check(STR("Mon Jul 20 13:45:30 2026"), STR("%Ec"), date_time);
+ // %Ex parses the same date representation as %x in the classic locale.
check(STR("07/20/26"), STR("%Ex"), date);
+ // %EX parses the same time representation as %X in the classic locale.
+ check(STR("01:30:00"), STR("%EX"), 5400s);
// Numeric years in the classic locale.
- for (const auto& format : {STR("%EC %Ey-%m-%d"), STR("%EC %EY-%m-%d")}) {
- std::basic_istringstream<CharT> stream(format == STR("%EC %Ey-%m-%d") ? STR("20 26-07-20") : STR("20 2026-07-20"));
- stream.imbue(std::locale::classic());
- sys_seconds result{};
- from_stream(stream, format.c_str(), result);
- assert(!stream.fail());
- assert(result == date);
- }
+ check(STR("20 26-07-20"), STR("%EC %Ey-%m-%d"), date);
+ // Match a century with a full year using E-modified fields.
+ check(STR("20 2026-07-20"), STR("%EC %EY-%m-%d"), date);
// Parse a century and a UTC offset.
- {
- std::basic_istringstream<CharT> stream(STR("20 26-07-20 +4:30"));
- stream.imbue(std::locale::classic());
- sys_seconds result{};
- from_stream(stream, STR("%EC %y-%m-%d %Ez").c_str(), result);
- assert(!stream.fail());
- assert(result == date - 4h - 30min);
- }
+ check(STR("20 26-07-20 13:45:30 +4:30"), STR("%EC %y-%m-%d %T %Ez"), date_time - 4h - 30min);
}
+// Test O-modified numeric fields in the classic locale.
template <class CharT>
void test_modifier_O() {
- using namespace std::chrono;
- const sys_seconds date = sys_days{2026y / July / 20};
+ const sys_days date = sys_days{2026y / July / 20};
- check(STR("2026-07-20"), STR("%Y-%m-%Oe"), date);
+ // %Oe accepts ordinary decimal day digits in the classic locale.
+ check(STR("2026-07-20"), STR("%Y-%m-%Oe"), year_month_day{date});
- // Parse numeric fields, including twelve o'clock with PM.
+ // Parse numeric fields and leave the trailing character unread.
{
std::basic_istringstream<CharT> stream(STR("26-07-20 13:45 1!"));
stream.imbue(std::locale::classic());
- sys_seconds result{};
+ sys_time<minutes> result{};
from_stream(stream, STR("%Oy-%Om-%Od %OH:%OM %Ow").c_str(), result);
assert(!stream.fail());
assert(result == date + 13h + 45min);
assert(stream.peek() == CharT('!'));
}
- {
- std::basic_istringstream<CharT> stream(STR("2026-07-20 12 PM"));
- stream.imbue(std::locale::classic());
- sys_seconds result{};
- from_stream(stream, STR("%F %OI %p").c_str(), result);
- assert(!stream.fail());
- assert(result == date + 12h);
- }
+ // Twelve o'clock with PM denotes noon.
+ check(STR("2026-07-20 12 PM"), STR("%F %OI %p"), sys_time<hours>{date} + 12h);
- // %OS, %OU and %OW parse numerically; %Oz uses the offset parser.
- {
- std::basic_istringstream<CharT> stream(STR("2026-07-20 13:45:30.125 +4:30"));
- stream.imbue(std::locale::classic());
- sys_time<milliseconds> result{};
- from_stream(stream, STR("%F %H:%M:%OS %Oz").c_str(), result);
- assert(!stream.fail());
- assert(result == date + 13h + 45min + 30s + 125ms - 4h - 30min);
- }
- for (const auto& format : {STR("%Y %OU %w"), STR("%Y %OW %w")}) {
- std::basic_istringstream<CharT> stream(STR("2026 29 1"));
- stream.imbue(std::locale::classic());
- sys_seconds result{};
- from_stream(stream, format.c_str(), result);
- assert(!stream.fail());
- assert(result == date);
- }
+ // Parse fractional seconds with a colon-separated UTC offset.
+ check(STR("2026-07-20 13:45:30.125 +4:30"),
+ STR("%F %H:%M:%OS %Oz"),
+ date + 13h + 45min + 30s + 125ms - 4h - 30min);
+
+ // This date has week number 29 with either Sunday or Monday as the first day.
+ for (const auto& format : {STR("%Y %OU %w"), STR("%Y %OW %w")})
+ check(STR("2026 29 1"), format, year_month_day{date});
}
template <class CharT>
@@ -240,28 +361,29 @@ struct comma_numpunct : std::numpunct<CharT> {
CharT do_decimal_point() const override { return CharT(','); }
};
+// Test fractional-second syntax, decimal punctuation, and target precision.
template <class CharT>
void test_fractional_seconds() {
- using namespace std::chrono;
+ // Convert fractional seconds to an integral millisecond count.
check(STR("1.25"), STR("%S"), 1250ms);
+ // Accept a fraction without digits before the decimal point.
check(STR(".5"), STR("%S"), 500ms);
+ // Accept a decimal point without following fractional digits.
check(STR("1."), STR("%S"), 1000ms);
- check(STR(".5!"), STR("%2S!"), 500ms);
// Floating-point representations allow fractions even with a whole-second period.
check(STR("12.125s"), STR("%Ss"), duration<double>{12.125});
- // An explicit width controls consumption independently of the target precision.
- check(STR("1.2345!"), STR("%6S!"), 1234ms);
- // An integer seconds target leaves the decimal point unread, regardless of width.
- for (const auto& format : {STR("%S"), STR("%6S")}) {
+ // With the default width, an integer seconds target leaves the decimal point unread.
+ {
std::basic_istringstream<CharT> stream(STR("1.25"));
stream.imbue(std::locale::classic());
duration<int> result{};
- from_stream(stream, format.c_str(), result);
+ from_stream(stream, STR("%S").c_str(), result);
assert(!stream.fail());
assert(result == 1s);
assert(stream.peek() == CharT('.'));
}
+ // Use the locale's comma decimal point and leave the trailing character unread.
{
std::basic_istringstream<CharT> stream(STR("1,25!"));
stream.imbue(std::locale(std::locale::classic(), new comma_numpunct<CharT>));
@@ -271,6 +393,31 @@ void test_fractional_seconds() {
assert(result == 1250ms);
assert(stream.peek() == CharT('!'));
}
+}
+
+// Test field widths, including signs, decimal points, and unread input.
+template <class CharT>
+void test_field_widths() {
+ // A width on %F applies only to %Y.
+ check(STR("002026-07-20"), STR("%6F"), 2026y / July / 20);
+
+ // A sign consumes one character of the field's width.
+ for (bool negative : {false, true}) {
+ std::basic_istringstream<CharT> stream(negative ? STR("-123") : STR("+123"));
+ stream.imbue(std::locale::classic());
+ year result{};
+ from_stream(stream, STR("%3Y").c_str(), result);
+ assert(!stream.fail());
+ assert(result == year{negative ? -12 : 12});
+ assert(stream.peek() == CharT('3'));
+ }
+
+ // A width consumed entirely by the sign leaves no year digits.
+ check_failure<year>(STR("+1"), STR("%1Y"));
+
+ // Consume six characters and match the trailing literal without losing precision.
+ check(STR("1.2345!"), STR("%6S!"), 1234500us);
+
{
// The width includes the decimal point and limits the fractional digits.
std::basic_istringstream<CharT> stream(STR("1.234"));
@@ -283,20 +430,49 @@ void test_fractional_seconds() {
}
}
-template <class CharT>
+// Test system, local, and file clock targets and their required date/time fields.
+template <class CharT, class FileClock = file_clock>
void test_time_points() {
- using namespace std::chrono;
const sys_seconds expected = sys_days{2026y / July / 20} + 13h + 45min + 30s;
auto test = [&](auto value) { check(STR("20 26-07-20 01:45:30 PM"), STR("%C %y-%m-%d %I:%M:%S %p"), value); };
+ // Combine century, year, and twelve-hour time into a system-clock time point.
test(expected);
+ // Parse the same calendar and time fields into a local time point.
test(local_seconds{expected.time_since_epoch()});
- test(file_clock::from_sys(expected));
+ // Convert the parsed date and time to the file clock's epoch.
+ if constexpr (requires { FileClock::from_sys(expected); }) {
+ test(FileClock::from_sys(expected));
+ }
+#if !defined(TEST_HAS_NO_EXPERIMENTAL_TZDB) && !defined(TEST_HAS_NO_TIME_ZONE_DATABASE) && \
+ !defined(TEST_HAS_NO_FILESYSTEM)
+ else {
+ // Implementations may provide UTC conversions instead of system-clock conversions.
+ test(FileClock::from_utc(utc_clock::from_sys(expected)));
+ }
+
+ // The GPS epoch is 1980-01-06 00:00:00.
+ check(STR("1980-01-06 00:00:00"), STR("%F %T"), gps_seconds{0s});
+#endif
+ // A time of day alone cannot determine a time point.
+ check_failure<sys_seconds>(STR("13:45:30"), STR("%H:%M:%S"));
+ // Reject a nonnumeric day and preserve the original time point.
+ check_failure<sys_seconds>(STR("2026-07-xx"), STR("%Y-%m-%d"), expected);
+
+ // Parse a date into a system-clock time point with day precision.
check(STR("2026-07-20"), STR("%F"), sys_days{2026y / July / 20});
// A days target rejects time-of-day fields even when the UTC offset would cancel them.
check_failure<sys_days>(STR("2026-07-20 04:00 +0400"), STR("%F %R %z"));
+}
+
+// Test time-point precision, representation, and conversion relative to the clock epoch.
+template <class CharT>
+void test_time_point_conversion() {
+ // Preserve fractional seconds for a time point just before the Unix epoch.
check(STR("1969-12-31 23:59:59.5"), STR("%F %T"), sys_time<milliseconds>{-500ms});
+ // Preserve fractional seconds in a floating-point time point and match the trailing literal.
check(STR("1970-01-01 00:00:12.125!"), STR("%F %T!"), sys_time<duration<double>>{duration<double>{12.125}});
+ // Convert one second after the Unix epoch to an attosecond count.
check(STR("1970-01-01 00:00:01"), STR("%F %T"), sys_time<duration<long long, std::atto>>{1s});
#if !defined(TEST_HAS_NO_EXPERIMENTAL_TZDB) && !defined(TEST_HAS_NO_TIME_ZONE_DATABASE) && \
@@ -309,118 +485,145 @@ void test_time_points() {
#if !defined(TEST_HAS_NO_EXPERIMENTAL_TZDB) && !defined(TEST_HAS_NO_TIME_ZONE_DATABASE) && \
!defined(TEST_HAS_NO_FILESYSTEM)
+// Test valid and invalid UTC leap seconds, including fractions and offsets.
template <class CharT>
void test_utc_leap_seconds() {
- using namespace std::chrono;
const utc_seconds leap = utc_clock::from_sys(sys_days{2017y / January / 1}) - 1s;
+ // Accept the leap second at the end of 2016.
check(STR("2016-12-31 23:59:60"), STR("%F %T"), leap);
+ // Apply the UTC offset and preserve the fraction within a leap second.
check(STR("2017-01-01 00:59:60.5 +0100"), STR("%F %T %z"), leap + 500ms);
+ // Reject second 60 at a time when no leap second occurs.
check_failure<utc_seconds>(STR("2026-07-20 12:00:60"), STR("%F %T"));
}
#endif
+// Test signed years, century selection, and agreement between year fields.
template <class CharT>
-void test_year_fields() {
- using namespace std::chrono;
+void test_year_resolution() {
+ // Accept an explicit positive sign within the year field's width.
check(STR("+123"), STR("%4Y"), year{123});
+ // Accept a negative year with room for its sign and three digits.
check(STR("-123"), STR("%4Y"), year{-123});
+ // Year zero is a valid calendar year.
check(STR("0"), STR("%Y"), year{0});
// Two-digit years use a default century unless %C or %Y supplies one.
check(STR("68"), STR("%y"), year{2068});
+ // The default century changes from 2000 to 1900 at two-digit year 69.
check(STR("69"), STR("%y"), year{1969});
+ // Combine an explicit century with a two-digit year.
check(STR("20 26"), STR("%C %y"), 2026y);
+ // Combine a negative century with a two-digit year.
check(STR("-20 76"), STR("%3C %y"), year{-1976});
+ // Accept a full year followed by its matching century.
check(STR("2026 20"), STR("%Y %C"), 2026y);
+ // A full year supplies the century for a matching two-digit year.
check(STR("1926 26"), STR("%Y %y"), 1926y);
+ // The year in %F supplies the century for a following two-digit year.
check(STR("1926-07-20 26"), STR("%F %y"), 1926y / July / 20);
- // A sign consumes one character of the field's width.
- {
- std::basic_istringstream<CharT> stream(STR("+123"));
- stream.imbue(std::locale::classic());
- year result{};
- from_stream(stream, STR("%3Y").c_str(), result);
- assert(!stream.fail());
- assert(result == year{12});
- assert(stream.peek() == CharT('3'));
- }
-
+ // Increasing the width does not make 100 a valid two-digit year.
check_failure<year>(STR("100"), STR("%3y"));
- check_failure<year>(STR("+1"), STR("%1Y"));
- check_failure<year>(STR("2026 25"), STR("%Y %y"));
+ // A century alone cannot determine a complete year.
check_failure<year>(STR("20"), STR("%C"));
}
+// Test duration components, their ranges, and rejection of calendar fields.
+// Parse time-of-day and day-count components.
template <class CharT>
void test_duration() {
- using namespace std::chrono;
- // Parse time-of-day and day-count components.
- for (const auto& format : {STR("%T"), STR("%X"), STR("%EX")})
- check(STR("01:30:00"), format, 5400s);
- check(STR("01:30:00 AM"), STR("%r"), 5400s);
- check(STR("PM 01:30"), STR("%p %I:%M"), 810min);
+ // Parse a standalone seconds field into a duration.
+ check(STR("30"), STR("%S"), 30s);
+
+ // Interpret %j as a day count when parsing a duration.
check(STR("2"), STR("%j"), days{2});
+ // Add hours and minutes to a multi-day duration.
check(STR("2 01:30"), STR("%j %R"), 48h + 90min);
+ // Accept the largest ordinary hour, minute, and second fields.
check(STR("23:59:59"), STR("%T"), 23h + 59min + 59s);
- check_failure<seconds>(STR("15 01"), STR("%d %H"));
- check_failure<hours>(STR("01:30"), STR("%R"));
- check_failure<minutes>(STR("PM"), STR("%p"));
- check_failure<minutes>(STR("60"), STR("%M"));
- check_failure<seconds>(STR("60"), STR("%S"));
+ // Reject a calendar day-of-month field and preserve the original duration.
+ check_failure<seconds>(STR("15 01"), STR("%d %H"), 42s);
}
+// Test twelve-hour and twenty-four-hour fields, AM/PM, and hour ranges.
template <class CharT>
-void test_hours() {
- using namespace std::chrono;
- // %H can disambiguate %I without %p, in either input order.
+void test_hour_fields() {
+ // Twelve o'clock with AM denotes midnight without a twenty-four-hour field.
+ check(STR("12 AM"), STR("%I %p"), 0h);
+ // Apply a PM designator that precedes the twelve-hour time.
+ check(STR("PM 01:30"), STR("%p %I:%M"), 810min);
+
+ // Accept matching twenty-four-hour, twelve-hour, and PM fields.
check(STR("13 01 PM"), STR("%H %I %p"), 13h);
+ // A preceding twenty-four-hour field resolves the twelve-hour field as PM.
check(STR("13 01"), STR("%H %I"), 13h);
+ // A following twenty-four-hour field also resolves the twelve-hour field as PM.
check(STR("01 13"), STR("%I %H"), 13h);
+ // Hour 00 resolves twelve o'clock as midnight.
check(STR("00 12"), STR("%H %I"), 0h);
+ // Hour 12 resolves twelve o'clock as noon.
check(STR("12 12"), STR("%H %I"), 12h);
- check_failure<minutes>(STR("14 01"), STR("%H %I"));
- check_failure<minutes>(STR("13 AM"), STR("%H %p"));
- check_failure<hours>(STR("24"), STR("%H"));
+ // An AM/PM designator alone cannot determine a duration.
+ check_failure<minutes>(STR("PM"), STR("%p"));
+ // Hour 13 is outside the twelve-hour range even with a PM designator.
check_failure<minutes>(STR("13 PM"), STR("%I %p"));
}
+// Test duration representations and whether the target period can represent the parsed value.
template <class CharT>
void test_duration_conversion() {
- using namespace std::chrono;
-
+ // Store a positive seconds count in an unsigned duration representation.
check(STR("42"), STR("%S"), duration<unsigned>{42});
+ // Scale fractional seconds to a floating-point millisecond count.
check(STR("1.25"), STR("%S"), duration<double, std::milli>{1250});
- // A 1.5-second integer tick is coarser than the seconds field.
- check_failure<duration<int, std::ratio<3, 2>>>(STR("1.5"), STR("%S"));
+ // An integral hours target cannot represent the additional thirty minutes.
+ check_failure<hours>(STR("01:30"), STR("%R"));
}
+// Test UTC offset syntax and its effect on the parsed time point.
template <class CharT>
void test_offsets() {
- using namespace std::chrono;
- const sys_seconds date = sys_days{2026y / July / 20};
- check(STR("2026-07-20 04"), STR("%F %z"), date - 4h);
- check(STR("2026-07-20 0430"), STR("%F %z"), date - 4h - 30min);
- check(STR("2026-07-20 -0130"), STR("%F %z"), date + 90min);
- // Offset minutes are not clock-minute fields restricted to 0-59.
- check(STR("2026-07-20 +0160"), STR("%F %z"), date - 120min);
+ // Consume the offset without storing it when the output pointer is null.
+ check(STR("15 +0130"), STR("%d %z"), day{15});
- check(STR("2026-07-20 4"), STR("%F %Ez"), date - 4h);
- check(STR("2026-07-20 +4:30"), STR("%F %Ez"), date - 4h - 30min);
- check(STR("2026-07-20 +4:30"), STR("%F %Oz"), date - 4h - 30min);
- check(STR("2026-07-20 -4:30"), STR("%F %Ez"), date + 4h + 30min);
+ const sys_seconds date = sys_days{2026y / July / 20};
+ // An unsigned two-digit offset supplies positive hours without minutes.
+ check(STR("2026-07-20 00:00:00 04"), STR("%F %T %z"), date - 4h);
+ // A four-digit offset supplies positive hours and minutes.
+ check(STR("2026-07-20 00:00:00 0430"), STR("%F %T %z"), date - 4h - 30min);
+ // Subtracting a negative UTC offset moves the result forward.
+ check(STR("2026-07-20 00:00:00 -0130"), STR("%F %T %z"), date + 90min);
+
+ // %Ez accepts a single-digit hour without minutes.
+ check(STR("2026-07-20 00:00:00 4"), STR("%F %T %Ez"), date - 4h);
+ // %Ez accepts an explicit positive sign and colon-separated minutes.
+ check(STR("2026-07-20 00:00:00 +4:30"), STR("%F %T %Ez"), date - 4h - 30min);
+ // %Oz accepts the same colon-separated offset syntax as %Ez.
+ check(STR("2026-07-20 00:00:00 +4:30"), STR("%F %T %Oz"), date - 4h - 30min);
+ // %Ez also accepts a negative colon-separated offset.
+ check(STR("2026-07-20 00:00:00 -4:30"), STR("%F %T %Ez"), date + 4h + 30min);
+
+ // A parsed UTC offset does not shift a local time point.
+ check(STR("2026-07-20 00:00:00 +0130"), STR("%F %T %z"), local_seconds{date.time_since_epoch()});
// Basic hours and any supplied minutes require two digits.
- check_failure<sys_seconds>(STR("2026-07-20 019"), STR("%F %z"));
- check_failure<sys_seconds>(STR("2026-07-20 4:9"), STR("%F %Ez"));
- check_failure<sys_seconds>(STR("2026-07-20 4"), STR("%F %z"));
+ check_failure<sys_seconds>(STR("2026-07-20 00:00:00 019!"), STR("%F %T %z!"));
+ // Modified offsets still require two minute digits after the colon.
+ check_failure<sys_seconds>(STR("2026-07-20 00:00:00 4:9!"), STR("%F %T %Ez!"));
+ // An unmodified offset requires two hour digits.
+ check_failure<sys_seconds>(STR("2026-07-20 00:00:00 4"), STR("%F %T %z"));
}
+// Test consuming and storing time-zone abbreviations.
template <class CharT>
void test_abbrev() {
- using namespace std::chrono;
+ // Consume the abbreviation without storing it when the output pointer is null.
+ check(STR("15 UTC"), STR("%d %Z"), day{15});
+
+ // Store letters, digits, and permitted punctuation in the abbreviation, stopping before '!'.
std::basic_istringstream<CharT> stream(STR("2026-07-20 a_1/+B-2!"));
stream.imbue(std::locale::classic());
sys_days result{};
@@ -435,17 +638,20 @@ void test_abbrev() {
template <class CharT>
void test() {
test_calendar_types<CharT>();
- test_format<CharT>();
- test_width_overflow<CharT>();
- test_date_consistency<CharT>();
+ test_compound_specifiers<CharT>();
+ test_locale_formats<CharT>();
+ test_literals_and_whitespace<CharT>();
+ test_date_resolution<CharT>();
test_modifier_E<CharT>();
test_modifier_O<CharT>();
test_fractional_seconds<CharT>();
- test_year_fields<CharT>();
+ test_field_widths<CharT>();
+ test_year_resolution<CharT>();
test_duration<CharT>();
- test_hours<CharT>();
+ test_hour_fields<CharT>();
test_duration_conversion<CharT>();
test_time_points<CharT>();
+ test_time_point_conversion<CharT>();
#if !defined(TEST_HAS_NO_EXPERIMENTAL_TZDB) && !defined(TEST_HAS_NO_TIME_ZONE_DATABASE) && \
!defined(TEST_HAS_NO_FILESYSTEM)
test_utc_leap_seconds<CharT>();
diff --git a/libcxx/test/std/time/time.parse/parse.pass.cpp b/libcxx/test/std/time/time.parse/parse.pass.cpp
index e1f894f3184149..33ba6931273d04 100644
--- a/libcxx/test/std/time/time.parse/parse.pass.cpp
+++ b/libcxx/test/std/time/time.parse/parse.pass.cpp
@@ -6,7 +6,7 @@
//
//===----------------------------------------------------------------------===//
-// UNSUPPORTED: c++03, c++11, c++14, c++17
+// REQUIRES: std-at-least-c++20
// UNSUPPORTED: no-localization
// <chrono>
@@ -31,13 +31,15 @@ concept CanParse = requires(Args&&... args) { std::chrono::parse(std::forward<Ar
namespace custom {
template <int Arity>
struct value {
- int count = 0;
+ int count = 0;
+ bool abbrev_was_null = false;
};
+// parse must return the input stream even when the ADL function returns void.
template <class CharT, class Traits>
-std::basic_istream<CharT, Traits>& from_stream(std::basic_istream<CharT, Traits>& is, const CharT* fmt, value<3>& out) {
+void from_stream(std::basic_istream<CharT, Traits>& is, const CharT* fmt, value<3>& out) {
assert(fmt[0] == CharT('#'));
- return is >> out.count;
+ is >> out.count;
}
template <class CharT, class Traits, class Alloc>
@@ -61,6 +63,7 @@ from_stream(std::basic_istream<CharT, Traits>& is,
std::chrono::minutes* offset) {
assert(fmt[0] == CharT('#'));
assert(offset != nullptr);
+ out.abbrev_was_null = abbrev == nullptr;
if (abbrev)
*abbrev = STR("custom");
*offset = std::chrono::minutes{90};
@@ -75,57 +78,98 @@ void check_constraints() {
using String = std::basic_string<CharT>;
using Value = custom::value<Arity>;
using Minutes = std::chrono::minutes;
+ // Without optional outputs, parse calls a three-argument from_stream.
static_assert(CanParse<const Format&, Value&> == (Arity == 3));
+ // An abbreviation output requires a four-argument from_stream.
static_assert(CanParse<const Format&, Value&, String&> == (Arity == 4));
+ // An offset output requires five arguments, including a null abbreviation pointer.
static_assert(CanParse<const Format&, Value&, Minutes&> == (Arity == 5));
+ // Both optional outputs require a five-argument from_stream.
static_assert(CanParse<const Format&, Value&, String&, Minutes&> == (Arity == 5));
}
+// Test overload participation for each ADL signature and invalid result arguments.
template <class CharT, class Format>
void test_constraints() {
check_constraints<CharT, Format, 3>();
check_constraints<CharT, Format, 4>();
check_constraints<CharT, Format, 5>();
+ // Reject a type without a suitable from_stream.
static_assert(!CanParse<const Format&, custom::not_parsable&>);
+ // This custom parser requires a mutable result.
static_assert(!CanParse<const Format&, const custom::value<3>&>);
+ // The result must be an lvalue.
static_assert(!CanParse<const Format&, custom::value<3>>);
}
+// Test ADL dispatch and output forwarding for all four parse argument lists.
template <class CharT, class Format>
void test_adl() {
using String = std::basic_string<CharT>;
const auto format_storage = STR("#");
const Format fmt{format_storage.c_str()};
+ // Call the void-returning ADL parser and return the original input stream.
{
std::basic_istringstream<CharT> is(STR("7"));
custom::value<3> result{};
auto& returned = is >> std::chrono::parse(fmt, result);
+ static_assert(std::same_as<decltype(returned), std::basic_istream<CharT>&>);
+ assert(&returned == &is);
+ assert(!is.fail());
+ assert(result.count == 7);
+ }
+ // Forward only the abbreviation output to the four-argument ADL parser.
+ {
+ std::basic_istringstream<CharT> is(STR("7"));
+ custom::value<4> result{};
+ String abbrev;
+ auto& returned = is >> std::chrono::parse(fmt, result, abbrev);
+ static_assert(std::same_as<decltype(returned), std::basic_istream<CharT>&>);
assert(&returned == &is);
assert(!is.fail());
assert(result.count == 7);
+ assert(abbrev == STR("custom"));
}
+ // Forward the offset output with a null abbreviation pointer.
+ {
+ std::basic_istringstream<CharT> is(STR("7"));
+ custom::value<5> result{};
+ std::chrono::minutes offset{};
+ auto& returned = is >> std::chrono::parse(fmt, result, offset);
+ static_assert(std::same_as<decltype(returned), std::basic_istream<CharT>&>);
+ assert(&returned == &is);
+ assert(!is.fail());
+ assert(result.count == 7);
+ assert(result.abbrev_was_null);
+ assert(offset == std::chrono::minutes{90});
+ }
+ // Forward both optional outputs to the five-argument ADL parser.
{
std::basic_istringstream<CharT> is(STR("7"));
custom::value<5> result{};
String abbrev;
std::chrono::minutes offset{};
- is >> std::chrono::parse(fmt, result, abbrev, offset);
+ auto& returned = is >> std::chrono::parse(fmt, result, abbrev, offset);
+ static_assert(std::same_as<decltype(returned), std::basic_istream<CharT>&>);
+ assert(&returned == &is);
assert(!is.fail());
assert(result.count == 7);
+ assert(!result.abbrev_was_null);
assert(abbrev == STR("custom"));
assert(offset == std::chrono::minutes{90});
}
}
+// Test delegation to chrono parsing, optional outputs, and failure propagation.
template <class CharT, class Format>
void test_chrono_overloads() {
using namespace std::chrono;
const sys_seconds date = sys_days{2026y / July / 20};
- const auto format_storage = STR("%F %Z %z");
+ const auto format_storage = STR("%F %T %Z %z");
const Format fmt{format_storage.c_str()};
- // Test both format types, with and without optional outputs.
+ // Apply the parsed UTC offset without requesting the optional outputs.
{
- std::basic_istringstream<CharT> is(STR("2026-07-20 UTC +0130!"));
+ std::basic_istringstream<CharT> is(STR("2026-07-20 00:00:00 UTC +0130!"));
is.imbue(std::locale::classic());
sys_seconds result{};
static_assert(std::same_as<decltype(is >> parse(fmt, result)), std::basic_istream<CharT>&>);
@@ -135,8 +179,9 @@ void test_chrono_overloads() {
assert(result == date - 90min);
assert(is.peek() == CharT('!'));
}
+ // Store both optional outputs and leave the trailing character unread.
{
- std::basic_istringstream<CharT> is(STR("2026-07-20 UTC +0130!"));
+ std::basic_istringstream<CharT> is(STR("2026-07-20 00:00:00 UTC +0130!"));
is.imbue(std::locale::classic());
sys_seconds result{};
std::basic_string<CharT> abbrev;
More information about the libcxx-commits
mailing list