From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: by sourceware.org (Postfix, from userid 2181) id 7D5DA385B51D; Thu, 22 Dec 2022 23:34:58 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 7D5DA385B51D DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gcc.gnu.org; s=default; t=1671752098; bh=EgDLVAvJBuJnCze/GsaiT8QOXSVnr+b8xViDDC3AqtM=; h=From:To:Subject:Date:From; b=ox2dtZTxI7uwDyNfQ02mnJvgKdUS5qhK6AHnwgkZm10bznJaBASDmFIM+Zp56Y5Wp rqN1d6MgqyxA0ShQo4fO04/xcC58IhFhkiPy7l/V34FZvvJec7VjfcJYxPRsi5A9ug McZnGLnjQWfXJ+uSUnNhNDuK8HwYTpL90yKHxWhU= MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Type: text/plain; charset="utf-8" From: Jonathan Wakely To: gcc-cvs@gcc.gnu.org, libstdc++-cvs@gcc.gnu.org Subject: [gcc r13-4856] libstdc++: Add std::format support to X-Act-Checkin: gcc X-Git-Author: Jonathan Wakely X-Git-Refname: refs/heads/master X-Git-Oldrev: 9247402a29d73dc09830a48d39a27768d40e46e0 X-Git-Newrev: f99b94865fa629cc2cc937128a812b6a23038446 Message-Id: <20221222233458.7D5DA385B51D@sourceware.org> Date: Thu, 22 Dec 2022 23:34:58 +0000 (GMT) List-Id: https://gcc.gnu.org/g:f99b94865fa629cc2cc937128a812b6a23038446 commit r13-4856-gf99b94865fa629cc2cc937128a812b6a23038446 Author: Jonathan Wakely Date: Thu Dec 22 01:29:22 2022 +0000 libstdc++: Add std::format support to This adds the operator<< overloads and std::formatter specializations required by C++20 so that types can be written to ostreams and printed with std::format. libstdc++-v3/ChangeLog: * include/Makefile.am: Add new header. * include/Makefile.in: Regenerate. * include/std/chrono (operator<<): Move to new header. (nonexistent_local_time::_M_make_what_str): Define correctly. (ambiguous_local_time::_M_make_what_str): Likewise. * include/bits/chrono_io.h: New file. * src/c++20/tzdb.cc (operator<<(ostream&, const Rule&)): Use new ostream output for month and weekday types. * testsuite/20_util/duration/io.cc: Test std::format support. * testsuite/std/time/exceptions.cc: Check what() strings. * testsuite/std/time/syn_c++20.cc: Uncomment local_time_format. * testsuite/std/time/time_zone/get_info_local.cc: Enable check for formatted output of local_info objects. * testsuite/std/time/clock/file/io.cc: New test. * testsuite/std/time/clock/gps/io.cc: New test. * testsuite/std/time/clock/system/io.cc: New test. * testsuite/std/time/clock/tai/io.cc: New test. * testsuite/std/time/clock/utc/io.cc: New test. * testsuite/std/time/day/io.cc: New test. * testsuite/std/time/format.cc: New test. * testsuite/std/time/hh_mm_ss/io.cc: New test. * testsuite/std/time/month/io.cc: New test. * testsuite/std/time/weekday/io.cc: New test. * testsuite/std/time/year/io.cc: New test. * testsuite/std/time/year_month_day/io.cc: New test. Diff: --- libstdc++-v3/include/Makefile.am | 1 + libstdc++-v3/include/Makefile.in | 1 + libstdc++-v3/include/bits/chrono_io.h | 2469 ++++++++++++++++++++ libstdc++-v3/include/std/chrono | 164 +- libstdc++-v3/src/c++20/tzdb.cc | 12 +- libstdc++-v3/testsuite/20_util/duration/io.cc | 48 + libstdc++-v3/testsuite/std/time/clock/file/io.cc | 23 + libstdc++-v3/testsuite/std/time/clock/gps/io.cc | 24 + libstdc++-v3/testsuite/std/time/clock/system/io.cc | 72 + libstdc++-v3/testsuite/std/time/clock/tai/io.cc | 24 + libstdc++-v3/testsuite/std/time/clock/utc/io.cc | 120 + libstdc++-v3/testsuite/std/time/day/io.cc | 75 + libstdc++-v3/testsuite/std/time/exceptions.cc | 4 +- libstdc++-v3/testsuite/std/time/format.cc | 117 + libstdc++-v3/testsuite/std/time/hh_mm_ss/io.cc | 46 + libstdc++-v3/testsuite/std/time/month/io.cc | 98 + libstdc++-v3/testsuite/std/time/syn_c++20.cc | 3 +- .../testsuite/std/time/time_zone/get_info_local.cc | 2 - libstdc++-v3/testsuite/std/time/weekday/io.cc | 101 + libstdc++-v3/testsuite/std/time/year/io.cc | 89 + .../testsuite/std/time/year_month_day/io.cc | 121 + 21 files changed, 3465 insertions(+), 149 deletions(-) diff --git a/libstdc++-v3/include/Makefile.am b/libstdc++-v3/include/Makefile.am index 92b5450fc14..e91f4ddd4de 100644 --- a/libstdc++-v3/include/Makefile.am +++ b/libstdc++-v3/include/Makefile.am @@ -175,6 +175,7 @@ bits_headers = \ ${bits_srcdir}/char_traits.h \ ${bits_srcdir}/charconv.h \ ${bits_srcdir}/chrono.h \ + ${bits_srcdir}/chrono_io.h \ ${bits_srcdir}/codecvt.h \ ${bits_srcdir}/cow_string.h \ ${bits_srcdir}/deque.tcc \ diff --git a/libstdc++-v3/include/Makefile.in b/libstdc++-v3/include/Makefile.in index 5d00f90a423..06589d53856 100644 --- a/libstdc++-v3/include/Makefile.in +++ b/libstdc++-v3/include/Makefile.in @@ -528,6 +528,7 @@ bits_freestanding = \ @GLIBCXX_HOSTED_TRUE@ ${bits_srcdir}/char_traits.h \ @GLIBCXX_HOSTED_TRUE@ ${bits_srcdir}/charconv.h \ @GLIBCXX_HOSTED_TRUE@ ${bits_srcdir}/chrono.h \ +@GLIBCXX_HOSTED_TRUE@ ${bits_srcdir}/chrono_io.h \ @GLIBCXX_HOSTED_TRUE@ ${bits_srcdir}/codecvt.h \ @GLIBCXX_HOSTED_TRUE@ ${bits_srcdir}/cow_string.h \ @GLIBCXX_HOSTED_TRUE@ ${bits_srcdir}/deque.tcc \ diff --git a/libstdc++-v3/include/bits/chrono_io.h b/libstdc++-v3/include/bits/chrono_io.h new file mode 100644 index 00000000000..4e53cd4aa2e --- /dev/null +++ b/libstdc++-v3/include/bits/chrono_io.h @@ -0,0 +1,2469 @@ +// Formatting -*- C++ -*- + +// Copyright The GNU Toolchain Authors. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// Under Section 7 of GPL version 3, you are granted additional +// permissions described in the GCC Runtime Library Exception, version +// 3.1, as published by the Free Software Foundation. + +// You should have received a copy of the GNU General Public License and +// a copy of the GCC Runtime Library Exception along with this program; +// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see +// . + +/** @file include/bits/chrono_io.h + * This is an internal header file, included by other library headers. + * Do not attempt to use it directly. @headername{chrono} + */ + +#ifndef _GLIBCXX_CHRONO_IO_H +#define _GLIBCXX_CHRONO_IO_H 1 + +#pragma GCC system_header + +#if __cplusplus >= 202002L + +#include // ostringstream +#include // setw, setfill +#include + +#include + +namespace std _GLIBCXX_VISIBILITY(default) +{ +_GLIBCXX_BEGIN_NAMESPACE_VERSION + +namespace chrono +{ +/// @addtogroup chrono +/// @{ + +/// @cond undocumented +namespace __detail +{ + // STATICALLY-WIDEN, see C++20 [time.general] + // It doesn't matter for format strings (which can only be char or wchar_t) + // but this returns the narrow string for anything that isn't wchar_t. This + // is done because const char* can be inserted into any ostream type, and + // will be widened at runtime if necessary. + template + consteval auto + _Widen(const char* __narrow, const wchar_t* __wide) + { + if constexpr (is_same_v<_CharT, wchar_t>) + return __wide; + else + return __narrow; + } +#define _GLIBCXX_WIDEN_(C, S) ::std::chrono::__detail::_Widen(S, L##S) +#define _GLIBCXX_WIDEN(S) _GLIBCXX_WIDEN_(_CharT, S) + + + // Write an arbitrary duration suffix into the buffer. + template + constexpr const char* + __units_suffix_misc(char* __buf, size_t /* TODO check length? */) noexcept + { + namespace __tc = std::__detail; + char* __p = __buf; + __p[0] = '['; + unsigned __nlen = __tc::__to_chars_len((uintmax_t)_Period::num); + __tc::__to_chars_10_impl(__p + 1, __nlen, (uintmax_t)_Period::num); + __p += 1 + __nlen; + if constexpr (_Period::den != 1) + { + __p[0] = '/'; + unsigned __dlen = __tc::__to_chars_len((uintmax_t)_Period::den); + __tc::__to_chars_10_impl(__p + 1, __dlen, (uintmax_t)_Period::den); + __p += 1 + __dlen; + } + __p[0] = ']'; + __p[1] = 's'; + __p[2] = '\0'; + return __buf; + } + + template + constexpr auto + __units_suffix(char* __buf, size_t __n) noexcept + { + // The standard say these are all narrow strings, which would need to + // be widened at run-time when inserted into a wide stream. We use + // STATICALLY-WIDEN to widen at compile-time. +#define _GLIBCXX_UNITS_SUFFIX(period, suffix) \ + if constexpr (is_same_v<_Period, period>) \ + return _GLIBCXX_WIDEN(suffix); \ + else + + _GLIBCXX_UNITS_SUFFIX(atto, "as") + _GLIBCXX_UNITS_SUFFIX(femto, "fs") + _GLIBCXX_UNITS_SUFFIX(pico, "ps") + _GLIBCXX_UNITS_SUFFIX(nano, "ns") + _GLIBCXX_UNITS_SUFFIX(milli, "ms") +#if _GLIBCXX_USE_ALT_MICROSECONDS_SUFFIX + // Deciding this at compile-time is wrong, maybe use nl_langinfo(CODESET) + // to check runtime environment and return u8"\u00b5s", "\xb5s", or "us". + _GLIBCXX_UNITS_SUFFIX(micro, "\u00b5s") +#else + _GLIBCXX_UNITS_SUFFIX(micro, "us") +#endif + _GLIBCXX_UNITS_SUFFIX(centi, "cs") + _GLIBCXX_UNITS_SUFFIX(deci, "ds") + _GLIBCXX_UNITS_SUFFIX(ratio<1>, "s") + _GLIBCXX_UNITS_SUFFIX(deca, "das") + _GLIBCXX_UNITS_SUFFIX(hecto, "hs") + _GLIBCXX_UNITS_SUFFIX(kilo, "ks") + _GLIBCXX_UNITS_SUFFIX(mega, "Ms") + _GLIBCXX_UNITS_SUFFIX(giga, "Gs") + _GLIBCXX_UNITS_SUFFIX(tera, "Ts") + _GLIBCXX_UNITS_SUFFIX(tera, "Ts") + _GLIBCXX_UNITS_SUFFIX(peta, "Ps") + _GLIBCXX_UNITS_SUFFIX(exa, "Es") + _GLIBCXX_UNITS_SUFFIX(ratio<60>, "min") + _GLIBCXX_UNITS_SUFFIX(ratio<3600>, "h") + _GLIBCXX_UNITS_SUFFIX(ratio<86400>, "d") +#undef _GLIBCXX_UNITS_SUFFIX + return __detail::__units_suffix_misc<_Period>(__buf, __n); + } +} // namespace __detail +/// @endcond + + /** Write a `chrono::duration` to an ostream. + * + * @since C++20 + */ + template + inline basic_ostream<_CharT, _Traits>& + operator<<(std::basic_ostream<_CharT, _Traits>& __os, + const duration<_Rep, _Period>& __d) + { + using period = typename _Period::type; + char __buf[sizeof("[/]s") + 2 * numeric_limits::digits10]; + std::basic_ostringstream<_CharT, _Traits> __s; + __s.flags(__os.flags()); + __s.imbue(__os.getloc()); + __s.precision(__os.precision()); + __s << __d.count(); + __s << __detail::__units_suffix(__buf, sizeof(__buf)); + __os << std::move(__s).str(); + return __os; + } + +/// @cond undocumented +namespace __detail +{ + // An unspecified type returned by `chrono::local_time_format`. + template + struct __local_time_fmt + { + local_time<_Duration> _M_time; + const string* _M_abbrev; + const seconds* _M_offset_sec; + }; + + struct __local_fmt_t; +} +/// @endcond + + /** Return an object that asssociates timezone info with a local time. + * + * A `chrono::local_time` object has no timezone associated with it. This + * function creates an object that allows formatting a `local_time` as + * though it refers to a timezone with the given abbreviated name and + * offset from UTC. + * + * @since C++20 + */ + template + inline __detail::__local_time_fmt<_Duration> + local_time_format(local_time<_Duration> __time, + const string* __abbrev = nullptr, + const seconds* __offset_sec = nullptr) + { return {__time, __abbrev, __offset_sec}; } + + /// @} +} // namespace chrono + +/// @cond undocumented +namespace __format +{ + [[noreturn,__gnu__::__always_inline__]] + inline void + __no_timezone_available() + { __throw_format_error("format error: no timezone available for %Z or %z"); } + + [[noreturn,__gnu__::__always_inline__]] + inline void + __not_valid_for_duration() + { __throw_format_error("format error: chrono-format-spec not valid for " + "chrono::duration"); } + + [[noreturn,__gnu__::__always_inline__]] + inline void + __invalid_chrono_spec() + { __throw_format_error("format error: chrono-format-spec not valid for " + "argument type"); } + + template + struct _ChronoSpec : _Spec<_CharT> + { + basic_string_view<_CharT> _M_chrono_specs; + }; + + // Represents the information provided by a chrono type. + // e.g. month_weekday has month and weekday but no year or time of day, + // hh_mm_ss has time of day but no date, sys_time is time_point+timezone. + enum _ChronoParts { + _Year = 1, _Month = 2, _Day = 4, _Weekday = 8, _TimeOfDay = 16, + _TimeZone = 32, + _Date = _Year | _Month | _Day | _Weekday, + _DateTime = _Date | _TimeOfDay, + _ZonedDateTime = _DateTime | _TimeZone, + _Duration = 128 // special case + }; + + constexpr _ChronoParts + operator|(_ChronoParts __x, _ChronoParts __y) + { return static_cast<_ChronoParts>((int)__x | (int)__y); } + + // TODO rename this to chrono::__formatter? or chrono::__detail::__formatter? + template + struct __formatter_chrono + { + using __string_view = basic_string_view<_CharT>; + using __string = basic_string<_CharT>; + + template + constexpr typename _ParseContext::iterator + _M_parse(_ParseContext& __pc, _ChronoParts __parts) + { + auto __first = __pc.begin(); + auto __last = __pc.end(); + + _ChronoSpec<_CharT> __spec{}; + + auto __finalize = [this, &__spec] { + _M_spec = __spec; + }; + + auto __finished = [&] { + if (__first == __last || *__first == '}') + { + __finalize(); + return true; + } + return false; + }; + + if (__finished()) + return __first; + + __first = __spec._M_parse_fill_and_align(__first, __last); + if (__finished()) + return __first; + + __first = __spec._M_parse_width(__first, __last, __pc); + if (__finished()) + return __first; + + if (__parts & _ChronoParts::_Duration) + { + __first = __spec._M_parse_precision(__first, __last, __pc); + if (__finished()) + return __first; + } + + __first = __spec._M_parse_locale(__first, __last); + if (__finished()) + return __first; + + // Everything up to the end of the string or the first '}' is a + // chrono-specs string. Check it is valid. + { + __string_view __str(__first, __last - __first); + auto __end = __str.find('}'); + if (__end != __str.npos) + { + __str.remove_suffix(__str.length() - __end); + __last = __first + __end; + } + if (__str.find('{') != __str.npos) + __throw_format_error("chrono format error: '{' in chrono-specs"); + } + + // Parse chrono-specs in [first,last), checking each conversion-spec + // against __parts (so fail for %Y if no year in parts). + // Save range in __spec._M_chrono_specs. + + const auto __chrono_specs = __first++; // Skip leading '%' + if (*__chrono_specs != '%') + __throw_format_error("chrono format error: no '%' at start of " + "chrono-specs"); + + _CharT __mod{}; + bool __conv = true; + int __needed = 0; + + while (__first != __last) + { + enum _Mods { _Mod_none, _Mod_E, _Mod_O, _Mod_E_O }; + _Mods __allowed_mods = _Mod_none; + + _CharT __c = *__first++; + switch (__c) + { + case 'a': + case 'A': + __needed = _Weekday; + break; + case 'b': + case 'h': + case 'B': + __needed = _Month; + break; + case 'c': + __needed = _DateTime; + __allowed_mods = _Mod_E; + break; + case 'C': + __needed = _Year; + __allowed_mods = _Mod_E; + break; + case 'd': + case 'e': + __needed = _Day; + __allowed_mods = _Mod_O; + break; + case 'D': + case 'F': + __needed = _Date; + break; + case 'g': + case 'G': + __needed = _Date; + break; + case 'H': + case 'I': + __needed = _TimeOfDay; + __allowed_mods = _Mod_O; + break; + case 'j': + if (!(__parts & _Duration)) + __needed = _Date; + break; + case 'm': + __needed = _Month; + __allowed_mods = _Mod_O; + break; + case 'M': + __needed = _TimeOfDay; + __allowed_mods = _Mod_O; + break; + case 'p': + case 'r': + case 'R': + case 'T': + __needed = _TimeOfDay; + break; + case 'q': + case 'Q': + __needed = _Duration; + break; + case 'S': + __needed = _TimeOfDay; + __allowed_mods = _Mod_O; + break; + case 'u': + case 'w': + __needed = _Weekday; + __allowed_mods = _Mod_O; + break; + case 'U': + case 'V': + case 'W': + __needed = _Date; + __allowed_mods = _Mod_O; + break; + case 'x': + __needed = _Date; + __allowed_mods = _Mod_E; + break; + case 'X': + __needed = _TimeOfDay; + __allowed_mods = _Mod_E; + break; + case 'y': + __needed = _Year; + __allowed_mods = _Mod_E_O; + break; + case 'Y': + __needed = _Year; + __allowed_mods = _Mod_E; + break; + case 'z': + __needed = _TimeZone; + __allowed_mods = _Mod_E; + break; + case 'Z': + __needed = _TimeZone; + __allowed_mods = _Mod_E_O; + break; + case 'n': + case 't': + case '%': + break; + case 'O': + case 'E': + __mod = __c; + continue; + default: + __throw_format_error("chrono format error: invalid " + " specifier in chrono-specs"); + } + + if ((__mod == 'E' && !(__allowed_mods & _Mod_E)) + || __mod == 'O' && !(__allowed_mods & _Mod_O)) + __throw_format_error("chrono format error: invalid " + " modifier in chrono-specs"); + __mod = _CharT(); + + if ((__parts & __needed) != __needed) + __throw_format_error("chrono format error: format argument " + "does not contain the information " + "required by the chrono-specs"); + + // Scan for next '%', ignoring literal-chars before it. + size_t __pos = __string_view(__first, __last - __first).find('%'); + if (__pos == 0) + ++__first; + else + { + if (__pos == __string_view::npos) + { + __first = __last; + __conv = false; + } + else + __first += __pos + 1; + } + } + + // Check for a '%' conversion-spec without a type. + if (__conv || __mod != _CharT()) + __throw_format_error("chrono format error: unescaped '%' in " + "chrono-specs"); + + _M_spec = __spec; + _M_spec._M_chrono_specs = {__chrono_specs, __first - __chrono_specs}; + + return __first; + } + + // TODO this function template is instantiated for every different _Tp. + // Consider creating a polymorphic interface for calendar types so + // that we instantiate fewer different specializations. Similar to + // _Sink_iter for std::format. Replace each _S_year, _S_day etc. with + // member functions of that type. + template + typename _FormatContext::iterator + _M_format(const _Tp& __t, _FormatContext& __fc, + bool __is_neg = false) const + { + if constexpr (__is_specialization_of<_Tp, chrono::hh_mm_ss>) + __is_neg = __t.is_negative(); + else if constexpr (!chrono::__is_duration_v<_Tp>) + __is_neg = false; + + auto __first = _M_spec._M_chrono_specs.begin(); + const auto __last = _M_spec._M_chrono_specs.end(); + if (__first == __last) + return _M_format_to_ostream(__t, __fc, __is_neg); + + _Sink_iter<_CharT> __out; + __format::_Str_sink<_CharT> __sink; + bool __write_direct = false; + if constexpr (is_same_v>) + { + if (_M_spec._M_width_kind == __format::_WP_none) + { + __out = __fc.out(); + __write_direct = true; + } + else + __out = __sink.out(); + } + else + __out = __sink.out(); + + auto __print_sign = [&__is_neg, &__out] { + if (__is_neg) + { + *__out++ = _S_plus_minus[1]; + __is_neg = false; + } + return std::move(__out); + }; + + // Characters to output for "%n", "%t" and "%%" specifiers. + constexpr const _CharT* __literals = _GLIBCXX_WIDEN("\n\t%"); + + ++__first; // Skip leading '%' at start of chrono-specs. + + _CharT __mod{}; + do + { + _CharT __c = *__first++; + switch (__c) + { + case 'a': + case 'A': + __out = _M_a_A(__t, std::move(__out), __fc, __c == 'A'); + break; + case 'b': + case 'h': + case 'B': + __out = _M_b_B(__t, std::move(__out), __fc, __c == 'B'); + break; + case 'c': + __out = _M_c(__t, std::move(__out), __fc, __mod == 'E'); + break; + case 'C': + case 'y': + case 'Y': + __out = _M_C_y_Y(__t, std::move(__out), __fc, __c, __mod); + break; + case 'd': + // %d The day of month as a decimal number. + // %Od Locale's alternative representation. + __out = _S_dd_zero_fill((unsigned)_S_day(__t), + std::move(__out), + __fc, __mod == 'O'); + break; + case 'D': + __out = _M_D(__t, std::move(__out), __fc); + break; + case 'e': + __out = _M_e(__t, std::move(__out), __fc, __mod == 'O'); + break; + case 'F': + __out = _M_F(__t, std::move(__out), __fc); + break; + case 'g': + case 'G': + __out = _M_g_G(__t, std::move(__out), __fc, __c == 'G'); + break; + case 'H': + // %H The hour (24-hour clock) as a decimal number. + // %OH Locale's alternative representation. + __out = _S_dd_zero_fill(_S_hms(__t).hours().count(), + __print_sign(), __fc, __mod == 'O'); + break; + case 'I': + __out = _M_I(__t, __print_sign(), __fc, __mod == 'O'); + break; + case 'j': + __out = _M_j(__t, __print_sign(), __fc); + break; + case 'm': + // %m month as a decimal number. + // %Om Locale's alternative representation. + __out = _S_dd_zero_fill((unsigned)_S_month(__t), + std::move(__out), __fc, + __mod == 'O'); + break; + case 'M': + // %M The minute as a decimal number. + // %OM Locale's alternative representation. + __out = _S_dd_zero_fill(_S_hms(__t).minutes().count(), + __print_sign(), __fc, __mod == 'O'); + break; + case 'p': + __out = _M_p(__t, std::move(__out), __fc); + break; + case 'q': + __out = _M_q(__t, std::move(__out), __fc); + break; + case 'Q': + // %Q The duration's numeric value. + if constexpr (chrono::__is_duration_v<_Tp>) + __out = std::format_to(__print_sign(), _S_empty_spec, + __t.count()); + else + __throw_format_error("chrono format error: argument is " + "not a duration"); + break; + case 'r': + __out = _M_r(__t, __print_sign(), __fc); + break; + case 'R': + case 'T': + __out = _M_R_T(__t, __print_sign(), __fc, __c == 'T'); + break; + case 'S': + __out = _M_S(__t, __print_sign(), __fc, __mod == 'O'); + break; + case 'u': + case 'w': + __out = _M_u_w(__t, std::move(__out), __fc, __c, __mod == 'O'); + break; + case 'U': + case 'V': + case 'W': + __out = _M_U_V_W(__t, std::move(__out), __fc, __c, + __mod == 'O'); + break; + case 'x': + __out = _M_x(__t, std::move(__out), __fc, __mod == 'E'); + break; + case 'X': + __out = _M_X(__t, __print_sign(), __fc, __mod == 'E'); + break; + case 'z': + __out = _M_z(__t, std::move(__out), __fc, (bool)__mod); + break; + case 'Z': + __out = _M_Z(__t, std::move(__out), __fc); + break; + case 'n': + *__out++ = __literals[0]; + break; + case 't': + *__out++ = __literals[1]; + break; + case '%': + *__out++ = __literals[2]; + break; + case 'O': + case 'E': + __mod = __c; + continue; + case '}': + __first = __last; + break; + } + __mod = _CharT(); + // Scan for next '%' and write out everything before it. + __string_view __str(__first, __last - __first); + size_t __pos = __str.find('%'); + if (__pos == 0) + ++__first; + else + { + if (__pos == __str.npos) + __first = __last; + else + { + __str.remove_suffix(__str.length() - __pos); + __first += __pos + 1; + } + __out = __format::__write(std::move(__out), __str); + } + } + while (__first != __last); + + if constexpr (is_same_v>) + if (__write_direct) + return __out; + + auto __str = std::move(__sink).get(); + return __format::__write_padded_as_spec(__str, __str.size(), + __fc, _M_spec); + } + + _ChronoSpec<_CharT> _M_spec; + + private: + // Return the formatting locale. + template + std::locale + _M_locale(_FormatContext& __fc) const + { + if (!_M_spec._M_localized) + return std::locale::classic(); + else + return __fc.locale(); + } + + // TODO: consider moving body of every operator<< into this function + // and use std::format("{}", t) to implement those operators. That + // would avoid std::format("{}", t) calling operator<< which calls + // std::format again. + template + typename _FormatContext::iterator + _M_format_to_ostream(const _Tp& __t, _FormatContext& __fc, + bool __is_neg) const + { + using ::std::chrono::__detail::__utc_leap_second; + using ::std::chrono::__detail::__local_time_fmt; + + if constexpr (__is_specialization_of<_Tp, __local_time_fmt>) + return _M_format_to_ostream(__t._M_time, __fc, false); + else + { + basic_ostringstream<_CharT> __os; + __os.imbue(_M_locale(__fc)); + + if constexpr (__is_specialization_of<_Tp, __utc_leap_second>) + __os << __t._M_date << ' ' << __t._M_time; + else + { + if (__is_neg) [[unlikely]] + __os << _S_plus_minus[1]; + __os << __t; + } + + auto __str = std::move(__os).str(); + return __format::__write_padded_as_spec(__str, __str.size(), + __fc, _M_spec); + } + } + + static constexpr const _CharT* _S_chars + = _GLIBCXX_WIDEN("0123456789+-:/ {}"); + static constexpr const _CharT* _S_plus_minus = _S_chars + 10; + static constexpr _CharT _S_colon = _S_chars[12]; + static constexpr _CharT _S_slash = _S_chars[13]; + static constexpr _CharT _S_space = _S_chars[14]; + static constexpr const _CharT* _S_empty_spec = _S_chars + 15; + + template + typename _FormatContext::iterator + _M_a_A(const _Tp& __t, typename _FormatContext::iterator __out, + _FormatContext& __ctx, bool __full) const + { + // %a Locale's abbreviated weekday name. + // %A Locale's full weekday name. + chrono::weekday __wd = _S_weekday(__t); + if (!__wd.ok()) + __throw_format_error("format error: invalid weekday"); + + locale __loc = _M_locale(__ctx); + const auto& __tp = use_facet<__timepunct<_CharT>>(__loc); + const _CharT* __days[7]; + if (__full) + __tp._M_days(__days); + else + __tp._M_days_abbreviated(__days); + __string_view __str(__days[__wd.c_encoding()]); + return __format::__write(std::move(__out), __str); + } + + template + typename _FormatContext::iterator + _M_b_B(const _Tp& __t, typename _FormatContext::iterator __out, + _FormatContext& __ctx, bool __full) const + { + // %b Locale's abbreviated month name. + // %B Locale's full month name. + chrono::month __m = _S_month(__t); + if (!__m.ok()) + __throw_format_error("format error: invalid month"); + locale __loc = _M_locale(__ctx); + const auto& __tp = use_facet<__timepunct<_CharT>>(__loc); + const _CharT* __months[12]; + if (__full) + __tp._M_months(__months); + else + __tp._M_months_abbreviated(__months); + __string_view __str(__months[(unsigned)__m - 1]); + return __format::__write(std::move(__out), __str); + } + + template + typename _FormatContext::iterator + _M_c(const _Tp& __t, typename _FormatContext::iterator __out, + _FormatContext& __ctx, bool __mod = false) const + { + // %c Locale's date and time representation. + // %Ec Locale's alternate date and time representation. + + locale __loc = _M_locale(__ctx); + const auto& __tp = use_facet<__timepunct<_CharT>>(__loc); + const _CharT* __formats[2]; + __tp._M_date_time_formats(__formats); + const _CharT* __rep = __formats[__mod]; + if (!*__rep) + __rep = _GLIBCXX_WIDEN("%a %b %e %H:%M:%S %Y"); + basic_string<_CharT> __fmt(_S_empty_spec); + __fmt.insert(1u, 1u, _S_colon); + __fmt.insert(2u, __rep); + return std::vformat_to(std::move(__out), __loc, __fmt, + std::make_format_args<_FormatContext>(__t)); + } + + template + typename _FormatContext::iterator + _M_C_y_Y(const _Tp& __t, typename _FormatContext::iterator __out, + _FormatContext& __ctx, char __conv, char __mod = 0) const + { + // %C Year divided by 100 using floored division. + // %EC Locale's alternative preresentation of the century (era name). + // %y Last two decimal digits of the year. + // %OY Locale's alternative represenation. + // %Ey Locale's alternative representation of offset from %EC. + // %Y Year as a decimal number. + // %EY Locale's alternative full year represenation. + + chrono::year __y = _S_year(__t); + + if (__mod == 'E') + { + // TODO: %EC, %Ey or %EY + // return __out; + } + + basic_string<_CharT> __s; + int __yi = (int)__y; + const bool __is_neg = __yi < 0; + __yi = __builtin_abs(__yi); + + if (__conv == 'Y' || __conv == 'C') + { + if (__is_neg) + __s.assign(1, _S_plus_minus[1]); + int __ci = __yi / 100; + if (__ci >= 100) [[unlikely]] + { + __s += std::format(_S_empty_spec, __ci / 100); + __ci %= 100; + } + __s += _S_two_digits(__ci); + } + + if (__conv == 'Y' || __conv == 'y') + __s += _S_two_digits(__yi % 100); + + if (__mod == 'O') // %OY + _S_altnum(_M_locale(__ctx), __s, __is_neg); + + return __format::__write(std::move(__out), __string_view(__s)); + } + + template + typename _FormatContext::iterator + _M_D(const _Tp& __t, typename _FormatContext::iterator __out, + _FormatContext&) const + { + auto __ymd = _S_date(__t); + basic_string<_CharT> __s; +#if ! _GLIBCXX_USE_CXX11_ABI + __s.reserve(8); +#endif + __s = _S_two_digits((unsigned)__ymd.month()); + __s += _S_slash; + __s += _S_two_digits((unsigned)__ymd.day()); + __s += _S_slash; + __s += _S_two_digits(__builtin_abs((int)__ymd.year()) % 100); + return __format::__write(std::move(__out), __string_view(__s)); + } + + template + typename _FormatContext::iterator + _M_e(const _Tp& __t, typename _FormatContext::iterator __out, + _FormatContext& __ctx, bool __mod = false) const + { + // %e Day of month as decimal number, padded with space. + // %Oe Locale's alternative digits. + chrono::day __d = _S_day(__t); + unsigned __i = (unsigned)__d; + auto __sv = _S_two_digits(__i); + basic_string<_CharT> __s; + if (__mod) + __sv = _S_altnum(_M_locale(__ctx), __s.assign(__sv)); + if (__i < 10) + __sv = __s = {_S_space, __sv[1]}; + return __format::__write(std::move(__out), __sv); + } + + template + typename _FormatContext::iterator + _M_F(const _Tp& __t, typename _FormatContext::iterator __out, + _FormatContext&) const + { + auto __ymd = _S_date(__t); + basic_string<_CharT> __s; +#if ! _GLIBCXX_USE_CXX11_ABI + __s.reserve(11); +#endif + __s += std::format(_GLIBCXX_WIDEN("{:04d}- - "), (int)__ymd.year()); + auto __sv = _S_two_digits((unsigned)__ymd.month()); + __s[__s.size() - 5] = __sv[0]; + __s[__s.size() - 4] = __sv[1]; + __sv = _S_two_digits((unsigned)__ymd.day()); + __s[__s.size() - 2] = __sv[0]; + __s[__s.size() - 1] = __sv[1]; + __sv = __s; + return __format::__write(std::move(__out), __sv); + } + + template + typename _FormatContext::iterator + _M_g_G(const _Tp& __t, typename _FormatContext::iterator __out, + _FormatContext& __ctx, bool __full) const + { + // %g last two decimal digits of the ISO week-based year. + // %G ISO week-based year. + using namespace chrono; + auto __d = _S_days(__t); + // Move to nearest Thursday: + __d -= (weekday(__d) - Monday) - days(3); + // ISO week-based year is the year that contains that Thursday: + year __y = year_month_day(__d).year(); + return _M_C_y_Y(__y, std::move(__out), __ctx, "yY"[__full]); + } + + template + typename _FormatContext::iterator + _M_I(const _Tp& __t, typename _FormatContext::iterator __out, + _FormatContext& __ctx, bool __mod = false) const + { + auto __hms = _S_hms(__t); + int __i = __hms.hours().count(); + if (__i == 0) + __i = 12; + else if (__i > 12) + __i -= 12; + auto __sv = _S_two_digits(__i); + basic_string<_CharT> __s; + if (__mod) + __sv = _S_altnum(_M_locale(__ctx), __s.assign(__sv)); + return __format::__write(std::move(__out), __sv); + } + + template + typename _FormatContext::iterator + _M_j(const _Tp& __t, typename _FormatContext::iterator __out, + _FormatContext& __ctx) const + { + if constexpr (chrono::__is_duration_v<_Tp>) + { + // Decimal number of days, without padding. + unsigned __d = chrono::duration_cast(__t).count(); + return std::format_to(std::move(__out), _S_empty_spec, __d); + } + else + { + // Day of the year as a decimal number, padding with zero. + using namespace chrono; + auto __day = _S_days(__t); + auto __ymd = _S_date(__t); + days __d; + // See "Calculating Ordinal Dates" at + // https://github.com/HowardHinnant/date/wiki/Examples-and-Recipes + if constexpr (is_same_v) + __d = __day - local_days(__ymd.year()/January/0); + else + __d = __day - sys_days(__ymd.year()/January/0); + return std::format_to(std::move(__out), _GLIBCXX_WIDEN("{:03d}"), + __d.count()); + } + } + + template + typename _FormatContext::iterator + _M_p(const _Tp& __t, typename _FormatContext::iterator __out, + _FormatContext& __ctx) const + { + // %p The locale's equivalent of the AM/PM designations. + auto __hms = _S_hms(__t); + locale __loc = _M_locale(__ctx); + const auto& __tp = use_facet<__timepunct<_CharT>>(__loc); + const _CharT* __ampm[2]; + __tp._M_am_pm(__ampm); + return std::format_to(std::move(__out), _S_empty_spec, + __ampm[__hms.hours().count() >= 12]); + } + + template + typename _FormatContext::iterator + _M_q(const _Tp& __t, typename _FormatContext::iterator __out, + _FormatContext& __ctx) const + { + // %q The duration's unit suffix + if constexpr (!chrono::__is_duration_v<_Tp>) + __throw_format_error("format error: argument is not a duration"); + else + { + using period = typename _Tp::period; + char __buf[sizeof("[/]s") + 2 * numeric_limits::digits10]; + constexpr size_t __n = sizeof(__buf); + auto __s = chrono::__detail::__units_suffix(__buf, + __n); + if constexpr (is_same_v) + return std::format_to(std::move(__out), _S_empty_spec, __s); + else + { + // Suffix was written to __buf as narrow string. + _CharT __wbuf[__n]; + size_t __len = __builtin_strlen(__buf); + locale __loc = _M_locale(__ctx); + auto& __ct = use_facet>(__loc); + __ct.widen(__buf, __len, __wbuf); + __wbuf[__len] = 0; + return std::format_to(std::move(__out), _S_empty_spec, + __wbuf); + } + } + } + + template + typename _FormatContext::iterator + _M_r(const _Tp& __t, typename _FormatContext::iterator __out, + _FormatContext& __ctx) const + { + // %r locale's 12-hour clock time. + locale __loc = _M_locale(__ctx); + const auto& __tp = use_facet<__timepunct<_CharT>>(__loc); + const _CharT* __ampm_fmt; + __tp._M_am_pm_format(&__ampm_fmt); + basic_string<_CharT> __fmt(_S_empty_spec); + __fmt.insert(1u, 1u, _S_colon); + __fmt.insert(2u, __ampm_fmt); + return std::vformat_to(std::move(__out), __fmt, + std::make_format_args<_FormatContext>(__t)); + } + + template + typename _FormatContext::iterator + _M_R_T(const _Tp& __t, typename _FormatContext::iterator __out, + _FormatContext& __ctx, bool __secs) const + { + // %R Equivalent to %H:%M + // %T Equivalent to %H:%M:%S + auto __hms = _S_hms(__t); + + basic_string<_CharT> __s; +#if ! _GLIBCXX_USE_CXX11_ABI + __s.reserve(11); +#endif + __s = std::format(_GLIBCXX_WIDEN("{:02d}:00"), __hms.hours().count()); + auto __sv = _S_two_digits(__hms.minutes().count()); + __s[__s.size() - 2] = __sv[0]; + __s[__s.size() - 1] = __sv[1]; + __sv = __s; + __out = __format::__write(std::move(__out), __sv); + if (__secs) + { + *__out++ = _S_colon; + __out = _M_S(__hms, std::move(__out), __ctx); + } + return __out; + } + + template + typename _FormatContext::iterator + _M_S(const _Tp& __t, typename _FormatContext::iterator __out, + _FormatContext& __ctx, bool __mod = false) const + { + // %S Seconds as a decimal number. + // %OS (TODO) The locale's alternative representation. + auto __hms = _S_hms(__t); + __out = _S_dd_zero_fill(__hms.seconds().count(), + std::move(__out), __ctx, __mod); + using rep = typename decltype(__hms)::precision::rep; + if constexpr (__hms.fractional_width != 0) + { + locale __loc = _M_locale(__ctx); + auto __ss = __hms.subseconds(); + if constexpr (is_floating_point_v) + { + __out = std::format_to(__loc, std::move(__out), + _GLIBCXX_WIDEN("{:.{}Lg}"), + __ss.count(), + __hms.fractional_width); + } + else if constexpr (is_integral_v) + { + const auto& __np + = use_facet>(__loc); + __out = std::format_to(std::move(__out), + _GLIBCXX_WIDEN("{}{:0{}}"), + __np.decimal_point(), + __ss.count(), + __hms.fractional_width); + } + else + { + const auto& __np + = use_facet>(__loc); + *__out++ = __np.decimal_point(); + auto __str = std::format(_S_empty_spec, __ss.count()); + __out = std::format_to(_GLIBCXX_WIDEN("{:0>{}s}"), + __str, + __hms.fractional_width); + } + } + return __out; + } + + // %t handled in _M_format + + template + typename _FormatContext::iterator + _M_u_w(const _Tp& __t, typename _FormatContext::iterator __out, + _FormatContext& __ctx, _CharT __conv, bool __mod = false) const + { + // %u ISO weekday as a decimal number (1-7), where Monday is 1. + // %Ou Locale's alternative numeric rep. + // %w Weekday as a decimal number (0-6), where Sunday is 0. + // %Ow Locale's alternative numeric rep. + chrono::weekday __wd = _S_weekday(__t); + unsigned __wdi = __conv == 'u' ? __wd.iso_encoding() + : __wd.c_encoding(); + basic_string<_CharT> __s(1, _S_digit(__wdi)); + if (__mod) + _S_altnum(_M_locale(__ctx), __s); + return __format::__write(std::move(__out), __string_view(__s)); + return __out; + } + + template + typename _FormatContext::iterator + _M_U_V_W(const _Tp& __t, typename _FormatContext::iterator __out, + _FormatContext& __ctx, _CharT __conv, bool __mod = false) const + { + // %U Week number of the year as a decimal number, from first Sunday. + // %OU Locale's alternative numeric rep. + // %V ISO week-based week number as a decimal number. + // %OV Locale's alternative numeric rep. + // %W Week number of the year as a decimal number, from first Monday. + // %OW Locale's alternative numeric rep. + using namespace chrono; + auto __d = _S_days(__t); + using _TDays = decltype(__d); // Either sys_days or local_days. + + _TDays __first; // First day of week 1. + if (__conv == 'V') // W01 begins on Monday before first Thursday. + { + // Move to nearest Thursday: + __d -= (weekday(__d) - Monday) - days(3); + // ISO week of __t is number of weeks since January 1 of the + // same year as that nearest Thursday. + __first = _TDays(year_month_day(__d).year()/January/1); + } + else + { + year __y; + if constexpr (requires { __t.year(); }) + __y = __t.year(); + else + __y = year_month_day(__d).year(); + const weekday __weekstart = __conv == 'U' ? Sunday : Monday; + __first = _TDays(__y/January/__weekstart[1]); + } + auto __weeks = chrono::floor(__d - __first); + __string_view __sv = _S_two_digits(__weeks.count() + 1); + basic_string<_CharT> __s; + if (__mod) + __sv = _S_altnum(_M_locale(__ctx), __s.assign(__sv)); + return __format::__write(std::move(__out), __sv); + } + + template + typename _FormatContext::iterator + _M_x(const _Tp& __t, typename _FormatContext::iterator __out, + _FormatContext& __ctx, bool __mod = false) const + { + // %x Locale's date rep + // %Ex Locale's alternative date representation. + locale __loc = _M_locale(__ctx); + const auto& __tp = use_facet<__timepunct<_CharT>>(__loc); + const _CharT* __date_reps[2]; + __tp._M_date_formats(__date_reps); + const _CharT* __rep = __date_reps[__mod]; + if (!*__rep) + return _M_D(__t, std::move(__out), __ctx); + + basic_string<_CharT> __fmt(_S_empty_spec); + __fmt.insert(1u, 1u, _S_colon); + __fmt.insert(2u, __rep); + return std::vformat_to(std::move(__out), __fmt, + std::make_format_args<_FormatContext>(__t)); + } + + template + typename _FormatContext::iterator + _M_X(const _Tp& __t, typename _FormatContext::iterator __out, + _FormatContext& __ctx, bool __mod = false) const + { + // %X Locale's time rep + // %EX Locale's alternative time representation. + locale __loc = _M_locale(__ctx); + const auto& __tp = use_facet<__timepunct<_CharT>>(__loc); + const _CharT* __time_reps[2]; + __tp._M_time_formats(__time_reps); + const _CharT* __rep = __time_reps[__mod]; + if (!*__rep) + return _M_R_T(__t, std::move(__out), __ctx, true); + + basic_string<_CharT> __fmt(_S_empty_spec); + __fmt.insert(1u, 1u, _S_colon); + __fmt.insert(2u, __rep); + return std::vformat_to(std::move(__out), __fmt, + std::make_format_args<_FormatContext>(__t)); + } + + template + typename _FormatContext::iterator + _M_z(const _Tp& __t, typename _FormatContext::iterator __out, + _FormatContext& __ctx, bool __mod = false) const + { + using ::std::chrono::__detail::__utc_leap_second; + using ::std::chrono::__detail::__local_time_fmt; + + auto __utc = __mod ? __string_view(_GLIBCXX_WIDEN("+00:00"), 6) + : __string_view(_GLIBCXX_WIDEN("+0000"), 5); + + if constexpr (chrono::__is_time_point_v<_Tp>) + { + if constexpr (is_same_v) + return __format::__write(std::move(__out), __utc); + } + else if constexpr (__is_specialization_of<_Tp, __local_time_fmt>) + { + if (__t._M_offset_sec) + { + auto __sv = __utc; + basic_string<_CharT> __s; + if (*__t._M_offset_sec != 0s) + { + chrono:: hh_mm_ss __hms(*__t._M_offset_sec); + __s = _S_plus_minus[__hms.is_negative()]; + __s += _S_two_digits(__hms.hours().count()); + if (__mod) + __s += _S_colon; + __s += _S_two_digits(__hms.minutes().count()); + __sv = __s; + } + return __format::__write(std::move(__out), __sv); + } + } + else if constexpr (__is_specialization_of<_Tp, __utc_leap_second>) + return __format::__write(std::move(__out), __utc); + + __no_timezone_available(); + } + + template + typename _FormatContext::iterator + _M_Z(const _Tp& __t, typename _FormatContext::iterator __out, + _FormatContext& __ctx) const + { + using ::std::chrono::__detail::__utc_leap_second; + using ::std::chrono::__detail::__local_time_fmt; + + __string_view __utc(_GLIBCXX_WIDEN("UTC"), 3); + if constexpr (chrono::__is_time_point_v<_Tp>) + { + if constexpr (is_same_v) + return __format::__write(std::move(__out), __utc); + } + else if constexpr (__is_specialization_of<_Tp, __local_time_fmt>) + { + if (__t._M_abbrev) + { + __string_view __wsv; + if constexpr (is_same_v<_CharT, char>) + __wsv = *__t._M_abbrev; + else + { + string_view __sv = *__t._M_abbrev; + basic_string<_CharT> __ws(__sv.size()); + auto& __ct = use_facet>(_M_locale(__ctx)); + __ct.widen(__sv.data(), __sv.size(), __ws.data()); + __wsv = __ws; + } + return __format::__write(std::move(__out), __wsv); + } + } + else if constexpr (__is_specialization_of<_Tp, __utc_leap_second>) + return __format::__write(std::move(__out), __utc); + + __no_timezone_available(); + } + + // %% handled in _M_format + + // A single digit character in the range '0'..'9'. + static _CharT + _S_digit(int __n) noexcept + { + // Extra 9s avoid past-the-end read on bad input. + return _GLIBCXX_WIDEN("0123456789999999")[__n & 0xf]; + } + + // A string view of two digit characters, "00".."99". + static basic_string_view<_CharT> + _S_two_digits(int __n) noexcept + { + return { + _GLIBCXX_WIDEN("0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899" + "9999999999999999999999999999999999999999" + "9999999999999999") + 2 * (__n & 0x7f), + 2 + }; + } + + // Convert a numeric string to the locale's alternative numeric symbols. + static basic_string_view<_CharT> + _S_altnum(const locale& __loc, basic_string<_CharT>& __s, + bool __is_neg = false) + { + if (__loc == locale::classic()) + return __s; + +#if 0 // TODO how can we access numpunct_cache?! Need to go via std::time_put? + auto& __np = use_facet<__numpunct_cache<_CharT>>(__loc); + auto __nums = __np._M_atoms_out; // alts for "-+xX01234..." + if (__is_neg) + __s[0] = __nums[0]; + __nums += 4; // now points to alternate digits + for (int __i = __is_neg; __i < __s.size(); ++__i) + __s[__i] = __nums[__s[__i] - '0']; +#endif + return __s; + } + + // Write two digits, zero-filled. + template + typename _FormatContext::iterator + _S_dd_zero_fill(int __val, typename _FormatContext::iterator __out, + _FormatContext& __ctx, bool __alt_num) const + { + auto __sv = _S_two_digits(__val); + basic_string<_CharT> __s; + if (__alt_num) + __sv = _S_altnum(_M_locale(__ctx), __s.assign(__sv)); + return __format::__write(std::move(__out), __sv); + } + + // Accessors for the components of chrono types: + + // Returns a hh_mm_ss. + template + static decltype(auto) + _S_hms(const _Tp& __t) + { + using ::std::chrono::__detail::__utc_leap_second; + using ::std::chrono::__detail::__local_time_fmt; + + if constexpr (__is_specialization_of<_Tp, chrono::hh_mm_ss>) + return __t; + else if constexpr (__is_specialization_of<_Tp, __utc_leap_second>) + return __t._M_time; + else if constexpr (chrono::__is_duration_v<_Tp>) + return chrono::hh_mm_ss<_Tp>(__t); + else if constexpr (chrono::__is_time_point_v<_Tp>) + return chrono::hh_mm_ss(__t - chrono::floor(__t)); + else if constexpr (__is_specialization_of<_Tp, __local_time_fmt>) + return _S_hms(__t._M_time); + else + { + __invalid_chrono_spec(); + return chrono::hh_mm_ss(); + } + } + + // Returns a sys_days or local_days. + template + static auto + _S_days(const _Tp& __t) + { + using namespace chrono; + using ::std::chrono::__detail::__utc_leap_second; + using ::std::chrono::__detail::__local_time_fmt; + + if constexpr (__is_time_point_v<_Tp>) + return chrono::floor(__t); + else if constexpr (__is_specialization_of<_Tp, __utc_leap_second>) + return __t._M_date; + else if constexpr (__is_specialization_of<_Tp, __local_time_fmt>) + return chrono::floor(__t._M_time); + else if constexpr (is_same_v<_Tp, year_month_day> + || is_same_v<_Tp, year_month_day_last> + || is_same_v<_Tp, year_month_weekday> + || is_same_v<_Tp, year_month_weekday_last>) + return sys_days(__t); + else + { + if constexpr (__is_duration_v<_Tp>) + __not_valid_for_duration(); + else + __invalid_chrono_spec(); + return chrono::sys_days(); + } + } + + // Returns a year_month_day. + template + static chrono::year_month_day + _S_date(const _Tp& __t) + { + if constexpr (is_same_v<_Tp, chrono::year_month_day>) + return __t; + else + return chrono::year_month_day(_S_days(__t)); + } + + template + static chrono::day + _S_day(const _Tp& __t) + { + using namespace chrono; + + if constexpr (is_same_v<_Tp, day>) + return __t; + else if constexpr (requires { __t.day(); }) + return __t.day(); + else + return _S_date(__t).day(); + } + + template + static chrono::month + _S_month(const _Tp& __t) + { + using namespace chrono; + + if constexpr (is_same_v<_Tp, month>) + return __t; + else if constexpr (requires { __t.month(); }) + return __t.month(); + else + return _S_date(__t).month(); + } + + template + static chrono::year + _S_year(const _Tp& __t) + { + using namespace chrono; + + if constexpr (is_same_v<_Tp, year>) + return __t; + else if constexpr (requires { __t.year(); }) + return __t.year(); + else + return _S_date(__t).year(); + } + + template + static chrono::weekday + _S_weekday(const _Tp& __t) + { + using namespace ::std::chrono; + using ::std::chrono::__detail::__local_time_fmt; + + if constexpr (is_same_v<_Tp, weekday>) + return __t; + else if constexpr (requires { __t.weekday(); }) + return __t.weekday(); + else if constexpr (is_same_v<_Tp, month_weekday>) + return __t.weekday_indexed().weekday(); + else if constexpr (is_same_v<_Tp, month_weekday_last>) + return __t.weekday_last().weekday(); + else + return weekday(_S_days(__t)); + } + }; + +} // namespace __format +/// @endcond + + template + struct formatter, _CharT> + { + constexpr typename basic_format_parse_context<_CharT>::iterator + parse(basic_format_parse_context<_CharT>& __pc) + { + using namespace __format; + auto __it = _M_f._M_parse(__pc, _Duration|_TimeOfDay); + if constexpr (!is_floating_point_v<_Rep>) + if (_M_f._M_spec._M_prec_kind != __format::_WP_none) + __throw_format_error("format error: invalid precision for duration"); + return __it; + } + + template + typename basic_format_context<_Out, _CharT>::iterator + format(const chrono::duration<_Rep, _Period>& __d, + basic_format_context<_Out, _CharT>& __fc) const + { + return _M_f._M_format(chrono::abs(__d), __fc, __d < __d.zero()); + } + + private: + __format::__formatter_chrono<_CharT> _M_f; + }; + + template + struct formatter + { + template + constexpr typename _ParseContext::iterator + parse(_ParseContext& __pc) + { return _M_f._M_parse(__pc, __format::_Day); } + + template + typename _FormatContext::iterator + format(const chrono::day& __t, _FormatContext& __fc) const + { return _M_f._M_format(__t, __fc); } + + private: + __format::__formatter_chrono<_CharT> _M_f; + }; + + template + struct formatter + { + template + constexpr typename _ParseContext::iterator + parse(_ParseContext& __pc) + { return _M_f._M_parse(__pc, __format::_Month); } + + template + typename _FormatContext::iterator + format(const chrono::month& __t, _FormatContext& __fc) const + { return _M_f._M_format(__t, __fc); } + + private: + __format::__formatter_chrono<_CharT> _M_f; + }; + + template + struct formatter + { + template + constexpr typename _ParseContext::iterator + parse(_ParseContext& __pc) + { return _M_f._M_parse(__pc, __format::_Year); } + + template + typename _FormatContext::iterator + format(const chrono::year& __t, _FormatContext& __fc) const + { return _M_f._M_format(__t, __fc); } + + private: + __format::__formatter_chrono<_CharT> _M_f; + }; + + template + struct formatter + { + template + constexpr typename _ParseContext::iterator + parse(_ParseContext& __pc) + { return _M_f._M_parse(__pc, __format::_Weekday); } + + template + typename _FormatContext::iterator + format(const chrono::weekday& __t, _FormatContext& __fc) const + { return _M_f._M_format(__t, __fc); } + + private: + __format::__formatter_chrono<_CharT> _M_f; + }; + + template + struct formatter + { + template + constexpr typename _ParseContext::iterator + parse(_ParseContext& __pc) + { return _M_f._M_parse(__pc, __format::_Weekday); } + + template + typename _FormatContext::iterator + format(const chrono::weekday_indexed& __t, _FormatContext& __fc) const + { return _M_f._M_format(__t, __fc); } + + private: + __format::__formatter_chrono<_CharT> _M_f; + }; + + template + struct formatter + { + template + constexpr typename _ParseContext::iterator + parse(_ParseContext& __pc) + { return _M_f._M_parse(__pc, __format::_Weekday); } + + template + typename _FormatContext::iterator + format(const chrono::weekday_last& __t, _FormatContext& __fc) const + { return _M_f._M_format(__t, __fc); } + + private: + __format::__formatter_chrono<_CharT> _M_f; + }; + + template + struct formatter + { + template + constexpr typename _ParseContext::iterator + parse(_ParseContext& __pc) + { return _M_f._M_parse(__pc, __format::_Month|__format::_Day); } + + template + typename _FormatContext::iterator + format(const chrono::month_day& __t, _FormatContext& __fc) const + { return _M_f._M_format(__t, __fc); } + + private: + __format::__formatter_chrono<_CharT> _M_f; + }; + + template + struct formatter + { + template + constexpr typename _ParseContext::iterator + parse(_ParseContext& __pc) + { return _M_f._M_parse(__pc, __format::_Month|__format::_Day); } + + template + typename _FormatContext::iterator + format(const chrono::month_day_last& __t, _FormatContext& __fc) const + { return _M_f._M_format(__t, __fc); } + + private: + __format::__formatter_chrono<_CharT> _M_f; + }; + + template + struct formatter + { + template + constexpr typename _ParseContext::iterator + parse(_ParseContext& __pc) + { return _M_f._M_parse(__pc, __format::_Month|__format::_Weekday); } + + template + typename _FormatContext::iterator + format(const chrono::month_weekday& __t, _FormatContext& __fc) const + { return _M_f._M_format(__t, __fc); } + + private: + __format::__formatter_chrono<_CharT> _M_f; + }; + + template + struct formatter + { + template + constexpr typename _ParseContext::iterator + parse(_ParseContext& __pc) + { return _M_f._M_parse(__pc, __format::_Month|__format::_Weekday); } + + template + typename _FormatContext::iterator + format(const chrono::month_weekday_last& __t, + _FormatContext& __fc) const + { return _M_f._M_format(__t, __fc); } + + private: + __format::__formatter_chrono<_CharT> _M_f; + }; + + template + struct formatter + { + template + constexpr typename _ParseContext::iterator + parse(_ParseContext& __pc) + { return _M_f._M_parse(__pc, __format::_Year|__format::_Month); } + + template + typename _FormatContext::iterator + format(const chrono::year_month& __t, _FormatContext& __fc) const + { return _M_f._M_format(__t, __fc); } + + private: + __format::__formatter_chrono<_CharT> _M_f; + }; + + template + struct formatter + { + template + constexpr typename _ParseContext::iterator + parse(_ParseContext& __pc) + { return _M_f._M_parse(__pc, __format::_Date); } + + template + typename _FormatContext::iterator + format(const chrono::year_month_day& __t, _FormatContext& __fc) const + { return _M_f._M_format(__t, __fc); } + + private: + __format::__formatter_chrono<_CharT> _M_f; + }; + + template + struct formatter + { + template + constexpr typename _ParseContext::iterator + parse(_ParseContext& __pc) + { return _M_f._M_parse(__pc, __format::_Date); } + + template + typename _FormatContext::iterator + format(const chrono::year_month_day_last& __t, + _FormatContext& __fc) const + { return _M_f._M_format(__t, __fc); } + + private: + __format::__formatter_chrono<_CharT> _M_f; + }; + + template + struct formatter + { + template + constexpr typename _ParseContext::iterator + parse(_ParseContext& __pc) + { return _M_f._M_parse(__pc, __format::_Date); } + + template + typename _FormatContext::iterator + format(const chrono::year_month_weekday& __t, + _FormatContext& __fc) const + { return _M_f._M_format(__t, __fc); } + + private: + __format::__formatter_chrono<_CharT> _M_f; + }; + + template + struct formatter + { + template + constexpr typename _ParseContext::iterator + parse(_ParseContext& __pc) + { return _M_f._M_parse(__pc, __format::_Date); } + + template + typename _FormatContext::iterator + format(const chrono::year_month_weekday_last& __t, + _FormatContext& __fc) const + { return _M_f._M_format(__t, __fc); } + + private: + __format::__formatter_chrono<_CharT> _M_f; + }; + + template + struct formatter>, _CharT> + { + template + constexpr typename _ParseContext::iterator + parse(_ParseContext& __pc) + { return _M_f._M_parse(__pc, __format::_TimeOfDay); } + + template + typename _FormatContext::iterator + format(const chrono::hh_mm_ss>& __t, + _FormatContext& __fc) const + { return _M_f._M_format(__t, __fc); } + + private: + __format::__formatter_chrono<_CharT> _M_f; + }; + +#if _GLIBCXX_USE_CXX11_ABI || ! _GLIBCXX_USE_DUAL_ABI + template + struct formatter + { + template + constexpr typename _ParseContext::iterator + parse(_ParseContext& __pc) + { return _M_f._M_parse(__pc, __format::_ChronoParts{}); } + + template + typename _FormatContext::iterator + format(const chrono::sys_info& __i, _FormatContext& __fc) const + { return _M_f._M_format(__i, __fc); } + + private: + __format::__formatter_chrono<_CharT> _M_f; + }; + + template + struct formatter + { + template + constexpr typename _ParseContext::iterator + parse(_ParseContext& __pc) + { return _M_f._M_parse(__pc, __format::_ChronoParts{}); } + + template + typename _FormatContext::iterator + format(const chrono::local_info& __i, _FormatContext& __fc) const + { return _M_f._M_format(__i, __fc); } + + private: + __format::__formatter_chrono<_CharT> _M_f; + }; +#endif + + template + struct formatter, _CharT> + { + template + constexpr typename _ParseContext::iterator + parse(_ParseContext& __pc) + { return _M_f._M_parse(__pc, __format::_ZonedDateTime); } + + template + typename _FormatContext::iterator + format(const chrono::sys_time<_Duration>& __t, + _FormatContext& __fc) const + { return _M_f._M_format(__t, __fc); } + + private: + __format::__formatter_chrono<_CharT> _M_f; + }; + + template + struct formatter, _CharT> + : __format::__formatter_chrono<_CharT> + { + template + constexpr typename _ParseContext::iterator + parse(_ParseContext& __pc) + { return _M_f._M_parse(__pc, __format::_ZonedDateTime); } + + template + typename _FormatContext::iterator + format(const chrono::utc_time<_Duration>& __t, + _FormatContext& __fc) const + { + // Adjust by removing leap seconds to get equivalent sys_time. + // We can't just use clock_cast because we want to know if the time + // falls within a leap second insertion, and format seconds as "60". + using chrono::__detail::__utc_leap_second; + using chrono::seconds; + using chrono::sys_time; + using _CDur = common_type_t<_Duration, seconds>; + const auto __li = chrono::get_leap_second_info(__t); + sys_time<_CDur> __s{__t.time_since_epoch() - __li.elapsed}; + if (!__li.is_leap_second) [[likely]] + return _M_f._M_format(__s, __fc); + else + return _M_f._M_format(__utc_leap_second(__s), __fc); + } + + private: + friend formatter, _CharT>; + + __format::__formatter_chrono<_CharT> _M_f; + }; + + template + struct formatter, _CharT> + : __format::__formatter_chrono<_CharT> + { + template + constexpr typename _ParseContext::iterator + parse(_ParseContext& __pc) + { return _M_f._M_parse(__pc, __format::_ZonedDateTime); } + + template + typename _FormatContext::iterator + format(const chrono::tai_time<_Duration>& __t, + _FormatContext& __fc) const + { + // Convert to __local_time_fmt with abbrev "TAI" and offset 0s. + + // Offset is 1970y/January/1 - 1958y/January/1 + constexpr chrono::days __tai_offset = chrono::days(4383); + using _CDur = common_type_t<_Duration, chrono::days>; + chrono::local_time<_CDur> __lt(__t.time_since_epoch() - __tai_offset); + const string __abbrev("TAI", 3); + const chrono::seconds __off = 0s; + const auto __lf = chrono::local_time_format(__lt, &__abbrev, &__off); + return _M_f._M_format(__lf, __fc); + } + + private: + __format::__formatter_chrono<_CharT> _M_f; + }; + + template + struct formatter, _CharT> + : __format::__formatter_chrono<_CharT> + { + template + constexpr typename _ParseContext::iterator + parse(_ParseContext& __pc) + { return _M_f._M_parse(__pc, __format::_ZonedDateTime); } + + template + typename _FormatContext::iterator + format(const chrono::gps_time<_Duration>& __t, + _FormatContext& __fc) const + { + // Convert to __local_time_fmt with abbrev "GPS" and offset 0s. + + // Offset is 1980y/January/Sunday[1] - 1970y/January/1 + constexpr chrono::days __gps_offset = chrono::days(3657); + using _CDur = common_type_t<_Duration, chrono::days>; + chrono::local_time<_CDur> __lt(__t.time_since_epoch() + __gps_offset); + const string __abbrev("GPS", 3); + const chrono::seconds __off = 0s; + const auto __lf = chrono::local_time_format(__lt, &__abbrev, &__off); + return _M_f._M_format(__lf, __fc); + } + + private: + __format::__formatter_chrono<_CharT> _M_f; + }; + + template + struct formatter, _CharT> + { + template + constexpr typename _ParseContext::iterator + parse(_ParseContext& __pc) + { return _M_f._M_parse(__pc, __format::_ZonedDateTime); } + + template + typename _FormatContext::iterator + format(const chrono::file_time<_Duration>& __t, + _FormatContext& __ctx) const + { + using namespace chrono; + return _M_f._M_format(chrono::clock_cast(__t), __ctx); + } + + private: + __format::__formatter_chrono<_CharT> _M_f; + }; + + template + struct formatter, _CharT> + { + template + constexpr typename _ParseContext::iterator + parse(_ParseContext& __pc) + { return _M_f._M_parse(__pc, __format::_DateTime); } + + template + typename _FormatContext::iterator + format(const chrono::local_time<_Duration>& __t, + _FormatContext& __ctx) const + { return _M_f._M_format(__t, __ctx); } + + private: + __format::__formatter_chrono<_CharT> _M_f; + }; + + template + struct formatter, _CharT> + { + template + constexpr typename _ParseContext::iterator + parse(_ParseContext& __pc) + { return _M_f._M_parse(__pc, __format::_ZonedDateTime); } + + template + typename _FormatContext::iterator + format(const chrono::__detail::__local_time_fmt<_Duration>& __t, + _FormatContext& __ctx) const + { return _M_f._M_format(__t, __ctx); } + + private: + __format::__formatter_chrono<_CharT> _M_f; + }; + +#if _GLIBCXX_USE_CXX11_ABI || ! _GLIBCXX_USE_DUAL_ABI + template + struct formatter, _CharT> + : formatter, _CharT> + { + template + typename _FormatContext::iterator + format(const chrono::zoned_time<_Duration, _TimeZonePtr>& __tp, + _FormatContext& __ctx) const + { + using chrono::__detail::__local_time_fmt; + using _Base = formatter<__local_time_fmt<_Duration>, _CharT>; + const chrono::sys_info __info = __tp.get_info(); + const auto __lf = chrono::local_time_format(__tp.get_local_time(), + &__info.abbrev, + &__info.offset); + return _Base::format(__lf, __ctx); + } + }; +#endif + + // Partial specialization needed for %c formatting of __utc_leap_second. + template + struct formatter, _CharT> + : formatter, _CharT> + { + template + typename _FormatContext::iterator + format(const chrono::__detail::__utc_leap_second<_Duration>& __t, + _FormatContext& __fc) const + { return this->_M_f._M_format(__t, __fc); } + }; + +namespace chrono +{ +/// @addtogroup chrono +/// @{ + + // TODO: from_stream for duration +#if 0 + template> + 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) + { + } +#endif + + template + inline basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __os, const day& __d) + { + using _Ctx = __conditional_t, + format_context, wformat_context>; + using _Str = basic_string_view<_CharT>; + _Str __s = _GLIBCXX_WIDEN("{:02d} is not a valid day"); + if (__d.ok()) + __s = __s.substr(0, 6); + __os << std::vformat(__s, make_format_args<_Ctx>((unsigned)__d)); + return __os; + } + + // TODO from_stream for day + + template + inline basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __os, const month& __m) + { + using _Ctx = __conditional_t, + format_context, wformat_context>; + using _Str = basic_string_view<_CharT>; + _Str __s = _GLIBCXX_WIDEN("{:L%b}{} is not a valid month"); + if (__m.ok()) + __os << std::vformat(__os.getloc(), __s.substr(0, 6), + make_format_args<_Ctx>(__m)); + else + __os << std::vformat(__s.substr(6), + make_format_args<_Ctx>((unsigned)__m)); + return __os; + } + + // TODO from_stream for month + + template + inline basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __os, const year& __y) + { + using _Ctx = __conditional_t, + format_context, wformat_context>; + using _Str = basic_string_view<_CharT>; + _Str __s = _GLIBCXX_WIDEN("-{:04d} is not a valid year"); + if (__y.ok()) + __s = __s.substr(0, 7); + int __i = (int)__y; + if (__i >= 0) [[likely]] + __s.remove_prefix(1); + else + __i = -__i; + __os << std::vformat(__s, make_format_args<_Ctx>(__i)); + return __os; + } + + // TODO from_stream for year + + template + inline basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __os, const weekday& __wd) + { + using _Ctx = __conditional_t, + format_context, wformat_context>; + using _Str = basic_string_view<_CharT>; + _Str __s = _GLIBCXX_WIDEN("{:L%a}{} is not a valid weekday"); + if (__wd.ok()) + __os << std::vformat(__os.getloc(), __s.substr(0, 6), + make_format_args<_Ctx>(__wd)); + else + __os << std::vformat(__s.substr(6), + make_format_args<_Ctx>(__wd.c_encoding())); + return __os; + } + + // TODO from_stream for weekday + + template + inline basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __os, + const weekday_indexed& __wdi) + { + // The standard says to format wdi.weekday() and wdi.index() using + // either "{:L}[{}]" or "{:L}[{} is not a valid index]". The {:L} spec + // means to format the weekday using ostringstream, so just do that. + basic_stringstream<_CharT> __os2; + __os2.imbue(__os.getloc); + __os2 << __wdi.weekday(); + const auto __i = __wdi.index(); + if constexpr (is_same_v<_CharT, char>) + __os2 << std::format("[{}", __i); + else + __os2 << std::format(L"[{}", __i); + basic_string_view<_CharT> __s = _GLIBCXX_WIDEN(" is not a valid index]"); + if (__i >= 1 && __i <= 5) + __os2 << __s.back(); + else + __os2 << __s; + __os << __os2.view(); + return __os; + } + + template + inline basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __os, + const weekday_last& __wdl) + { + // As above, just write straight to a stringstream, as if by "{:L}[last]" + basic_stringstream<_CharT> __os2; + __os2.imbue(__os.getloc); + __os2 << __wdl.weekday() << _GLIBCXX_WIDEN("[last]"); + __os << __os2.view(); + return __os; + } + + template + inline basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __os, const month_day& __md) + { + // As above, just write straight to a stringstream, as if by "{:L}/{}" + basic_stringstream<_CharT> __os2; + __os2.imbue(__os.getloc); + __os2 << __md.month(); + if constexpr (is_same_v<_CharT, char>) + __os2 << '/'; + else + __os2 << L'/'; + __os2 << __md.day(); + __os << __os2.view(); + return __os; + } + + // TODO from_stream for month_day + + template + inline basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __os, + const month_day_last& __mdl) + { + // As above, just write straight to a stringstream, as if by "{:L}/last" + basic_stringstream<_CharT> __os2; + __os2.imbue(__os.getloc); + __os2 << __mdl.month(); + if constexpr (is_same_v<_CharT, char>) + __os2 << "/last"; + else + __os2 << L"/last"; + __os << __os2.view(); + return __os; + } + + template + inline basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __os, + const month_weekday& __mwd) + { + // As above, just write straight to a stringstream, as if by "{:L}/{:L}" + basic_stringstream<_CharT> __os2; + __os2.imbue(__os.getloc); + __os2 << __mwd.month(); + if constexpr (is_same_v<_CharT, char>) + __os2 << '/'; + else + __os2 << L'/'; + __os2 << __mwd.weekday_indexed(); + __os << __os2.view(); + return __os; + } + + template + inline basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __os, + const month_weekday_last& __mwdl) + { + // As above, just write straight to a stringstream, as if by "{:L}/{:L}" + basic_stringstream<_CharT> __os2; + __os2.imbue(__os.getloc); + __os2 << __mwdl.month(); + if constexpr (is_same_v<_CharT, char>) + __os2 << '/'; + else + __os2 << L'/'; + __os2 << __mwdl.weekday_last(); + __os << __os2.view(); + return __os; + } + + template + inline basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __os, const year_month& __ym) + { + // As above, just write straight to a stringstream, as if by "{}/{:L}" + basic_stringstream<_CharT> __os2; + __os2.imbue(__os.getloc); + __os2 << __ym.year(); + if constexpr (is_same_v<_CharT, char>) + __os2 << '/'; + else + __os2 << L'/'; + __os2 << __ym.month(); + __os << __os2.view(); + return __os; + } + + // TODO from_stream for year_month + + template + inline basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __os, + const year_month_day& __ymd) + { + using _Ctx = __conditional_t, + format_context, wformat_context>; + using _Str = basic_string_view<_CharT>; + _Str __s = _GLIBCXX_WIDEN("{:%F} is not a valid date"); + __os << std::vformat(__ymd.ok() ? __s.substr(0, 5) : __s, + make_format_args<_Ctx>(__ymd)); + return __os; + } + + // TODO from_stream for year_month_day + + template + inline basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __os, + const year_month_day_last& __ymdl) + { + // As above, just write straight to a stringstream, as if by "{}/{:L}" + basic_stringstream<_CharT> __os2; + __os2.imbue(__os.getloc); + __os2 << __ymdl.year(); + if constexpr (is_same_v<_CharT, char>) + __os2 << '/'; + else + __os2 << L'/'; + __os2 << __ymdl.month_day_last(); + __os << __os2.view(); + return __os; + } + + template + inline basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __os, + const year_month_weekday& __ymwd) + { + // As above, just write straight to a stringstream, as if by + // "{}/{:L}/{:L}" + basic_stringstream<_CharT> __os2; + __os2.imbue(__os.getloc); + _CharT __slash; + if constexpr (is_same_v<_CharT, char>) + __slash = '/'; + else + __slash = L'/'; + __os2 << __ymwd.year() << __slash << __ymwd.month() << __slash + << __ymwd.weekday_indexed(); + __os << __os2.view(); + return __os; + } + + template + inline basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __os, + const year_month_weekday_last& __ymwdl) + { + // As above, just write straight to a stringstream, as if by + // "{}/{:L}/{:L}" + basic_stringstream<_CharT> __os2; + __os2.imbue(__os.getloc); + _CharT __slash; + if constexpr (is_same_v<_CharT, char>) + __slash = '/'; + else + __slash = L'/'; + __os2 << __ymwdl.year() << __slash << __ymwdl.month() << __slash + << __ymwdl.weekday_last(); + __os << __os2.view(); + return __os; + } + + template + inline basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __os, + const hh_mm_ss<_Duration>& __hms) + { + return __os << format(__os.getloc(), _GLIBCXX_WIDEN("{:L%T}"), __hms); + } + +#if _GLIBCXX_USE_CXX11_ABI || ! _GLIBCXX_USE_DUAL_ABI + /// Writes a sys_info object to an ostream in an unspecified format. + template + basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __os, const sys_info& __i) + { + __os << '[' << __i.begin << ',' << __i.end + << ',' << hh_mm_ss(__i.offset) << ',' << __i.save + << ',' << __i.abbrev << ']'; + return __os; + } + + /// Writes a local_info object to an ostream in an unspecified format. + template + basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __os, const local_info& __li) + { + __os << '['; + if (__li.result == local_info::unique) + __os << __li.first; + else + { + if (__li.result == local_info::nonexistent) + __os << "nonexistent"; + else + __os << "ambiguous"; + __os << " local time between " << __li.first; + __os << " and " << __li.second; + } + __os << ']'; + return __os; + } + + template + inline basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __os, + const zoned_time<_Duration, _TimeZonePtr>& __t) + { + __os << format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T %Z}"), __t); + return __os; + } +#endif + + template + requires (!treat_as_floating_point_v) + && ratio_less_v + inline basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __os, + const sys_time<_Duration>& __tp) + { + __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"), __tp); + return __os; + } + + template + inline basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __os, const sys_days& __dp) + { + __os << year_month_day{__dp}; + return __os; + } + + // TODO: from_stream for sys_time + + template + inline basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __os, + const utc_time<_Duration>& __t) + { + __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"), __t); + return __os; + } + + // TODO: from_stream for utc_time + + template + inline basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __os, + const tai_time<_Duration>& __t) + { + __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"), __t); + return __os; + } + + // TODO: from_stream for tai_time + + template + inline basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __os, + const gps_time<_Duration>& __t) + { + __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"), __t); + return __os; + } + + // TODO: from_stream for gps_time + + + template + inline basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __os, + const file_time<_Duration>& __t) + { + __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"), __t); + return __os; + } + + // TODO: from_stream for file_time + + template + inline basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __os, + const local_time<_Duration>& __lt) + { + __os << sys_time<_Duration>{__lt.time_since_epoch()}; + return __os; + } + + // TODO: from_stream for local_time +#undef _GLIBCXX_WIDEN + + /// @} group chrono +} // namespace chrono + +_GLIBCXX_END_NAMESPACE_VERSION +} // namespace std + +#endif // C++20 + +#endif //_GLIBCXX_CHRONO_IO_H diff --git a/libstdc++-v3/include/std/chrono b/libstdc++-v3/include/std/chrono index aeb8f6f462f..27f391a1455 100644 --- a/libstdc++-v3/include/std/chrono +++ b/libstdc++-v3/include/std/chrono @@ -45,7 +45,6 @@ # include # include # include -# include // __to_chars_len, __to_chars_10_impl # include // upper_bound # include # include @@ -627,8 +626,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION friend constexpr year_month_day operator/(const year_month& __ym, const day& __d) noexcept; - - // TODO: Implement operator<<, to_stream, from_stream. }; // MONTH @@ -751,8 +748,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION friend constexpr month_weekday_last operator/(const weekday_last& __wdl, const month& __m) noexcept; - - // TODO: Implement operator<<, to_stream, from_stream. }; inline constexpr month January{1}; @@ -929,8 +924,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION friend constexpr year_month_weekday_last operator/(const month_weekday_last& __mwdl, const year& __y) noexcept; - - // TODO: Implement operator<<, to_stream, from_stream. }; // WEEKDAY @@ -1052,8 +1045,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION auto __n = static_cast(__x._M_wd) - __y._M_wd; return days{__detail::__modulo(__n, 7)}; } - - // TODO: operator<<, from_stream. }; inline constexpr weekday Sunday{0}; @@ -1110,8 +1101,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION friend constexpr year_month_weekday operator/(const year_month& __ym, const weekday_indexed& __wdi) noexcept; - - // TODO: Implement operator<<. }; constexpr weekday_indexed @@ -1151,8 +1140,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION friend constexpr year_month_weekday_last operator/(const year_month& __ym, const weekday_last& __wdl) noexcept; - - // TODO: Implement operator<<. }; constexpr weekday_last @@ -1224,8 +1211,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION friend constexpr year_month_day operator/(const month_day& __md, int __y) noexcept; - - // TODO: Implement operator<<, from_stream. }; // MONTH_DAY_LAST @@ -1278,8 +1263,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION friend constexpr year_month_day_last operator/(const month_day_last& __mdl, int __y) noexcept; - - // TODO: Implement operator<<. }; // MONTH_WEEKDAY @@ -1339,8 +1322,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION friend constexpr year_month_weekday operator/(const month_weekday& __mwd, int __y) noexcept; - - // TODO: Implement operator<<. }; // MONTH_WEEKDAY_LAST @@ -1401,8 +1382,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION friend constexpr year_month_weekday_last operator/(const month_weekday_last& __mwdl, int __y) noexcept; - - // TODO: Implement operator<<. }; // YEAR_MONTH @@ -1544,8 +1523,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION friend constexpr year_month_day_last operator/(const year_month& __ym, last_spec) noexcept; - - // TODO: Implement operator<<, from_stream. }; // YEAR_MONTH_DAY @@ -1697,8 +1674,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION friend constexpr year_month_day operator/(const month_day& __md, int __y) noexcept { return chrono::year(__y) / __md; } - - // TODO: Implement operator<<, from_stream. }; // Construct from days since 1970/01/01. @@ -1928,8 +1903,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION friend constexpr year_month_day_last operator/(const chrono::month_day_last& __mdl, int __y) noexcept { return chrono::year(__y) / __mdl; } - - // TODO: Implement operator<<. }; // year_month_day ctor from year_month_day_last @@ -2118,8 +2091,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION friend constexpr year_month_weekday operator/(const month_weekday& __mwd, int __y) noexcept { return chrono::year(__y) / __mwd; } - - // TODO: Implement operator<<. }; // YEAR_MONTH_WEEKDAY_LAST @@ -2267,8 +2238,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION friend constexpr year_month_weekday_last operator/(const chrono::month_weekday_last& __mwdl, int __y) noexcept { return chrono::year(__y) / __mwdl; } - - // TODO: Implement operator<<. }; // HH_MM_SS @@ -2284,6 +2253,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION __r *= 10; return __r; } + + template struct __utc_leap_second; } /// @endcond @@ -2389,8 +2360,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION return _M_h + _M_m + _M_s + subseconds(); } - // TODO: Implement operator<<. - private: static constexpr bool _S_is_unsigned = __and_v, @@ -2459,8 +2428,30 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION __byte_duration> _M_s{}; bool _M_is_neg{}; __subseconds _M_ss{}; + + template friend struct __detail::__utc_leap_second; }; + /// @cond undocumented + namespace __detail + { + // Represents a time that is within a leap second insertion. + template + struct __utc_leap_second + { + explicit + __utc_leap_second(const sys_time<_Duration>& __s) + : _M_date(chrono::floor(__s)), _M_time(__s - _M_date) + { + ++_M_time._M_s; + } + + sys_days _M_date; + hh_mm_ss> _M_time; + }; + } + /// @endcond + // 12/24 HOURS FUNCTIONS constexpr bool @@ -2540,9 +2531,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _S_make_what_str(const local_time<_Duration>& __tp, const local_info& __i) { -#if 1 - return "local time is non-existent"; -#else std::ostringstream __os; __os << __tp << " is in a gap between\n" << local_seconds(__i.first.end.time_since_epoch()) @@ -2552,7 +2540,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION << " which are both equivalent to\n" << __i.first.end << " UTC"; return std::move(__os).str(); -#endif } }; @@ -2571,9 +2558,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _S_make_what_str(const local_time<_Duration>& __tp, const local_info& __i) { -#if 1 - return "local time is ambiguous"; -#else std::ostringstream __os; __os << __tp << " is ambiguous. It could be\n" << __tp << ' ' << __i.first.abbrev << " == " @@ -2581,7 +2565,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION << __tp << ' ' << __i.second.abbrev << " == " << __tp - __i.second.offset << " UTC"; return std::move(__os).str(); -#endif } }; @@ -3329,106 +3312,15 @@ namespace __detail /// @} } // inline namespace chrono_literals } // inline namespace literals - - namespace chrono - { - /// @addtogroup chrono - /// @{ - - /// @cond undocumented - namespace __detail - { - template - const char* - __units_suffix_misc(char* __buf, size_t __n) noexcept - { - namespace __tc = std::__detail; - char* __p = __buf; - __p[0] = '['; - unsigned __nlen = __tc::__to_chars_len((uintmax_t)_Period::num); - __tc::__to_chars_10_impl(__p + 1, __nlen, (uintmax_t)_Period::num); - __p += 1 + __nlen; - if constexpr (_Period::den != 1) - { - __p[0] = '/'; - unsigned __dlen = __tc::__to_chars_len((uintmax_t)_Period::den); - __tc::__to_chars_10_impl(__p + 1, __dlen, (uintmax_t)_Period::den); - __p += 1 + __dlen; - } - __p[0] = ']'; - __p[1] = 's'; - __p[2] = '\0'; - return __buf; - } - - template - auto - __units_suffix(char* __buf, size_t __n) noexcept - { -#define _GLIBCXX_UNITS_SUFFIX(period, suffix) \ - if constexpr (is_same_v<_Period, period>) \ - { \ - if constexpr (is_same_v<_CharT, wchar_t>) \ - return L##suffix; \ - else \ - return suffix; \ - } \ - else - - _GLIBCXX_UNITS_SUFFIX(atto, "as") - _GLIBCXX_UNITS_SUFFIX(femto, "fs") - _GLIBCXX_UNITS_SUFFIX(pico, "ps") - _GLIBCXX_UNITS_SUFFIX(nano, "ns") - _GLIBCXX_UNITS_SUFFIX(micro, "\u00b5s") - _GLIBCXX_UNITS_SUFFIX(milli, "ms") - _GLIBCXX_UNITS_SUFFIX(centi, "cs") - _GLIBCXX_UNITS_SUFFIX(deci, "ds") - _GLIBCXX_UNITS_SUFFIX(ratio<1>, "s") - _GLIBCXX_UNITS_SUFFIX(deca, "das") - _GLIBCXX_UNITS_SUFFIX(hecto, "hs") - _GLIBCXX_UNITS_SUFFIX(kilo, "ks") - _GLIBCXX_UNITS_SUFFIX(mega, "Ms") - _GLIBCXX_UNITS_SUFFIX(giga, "Gs") - _GLIBCXX_UNITS_SUFFIX(tera, "Ts") - _GLIBCXX_UNITS_SUFFIX(tera, "Ts") - _GLIBCXX_UNITS_SUFFIX(peta, "Ps") - _GLIBCXX_UNITS_SUFFIX(exa, "Es") - _GLIBCXX_UNITS_SUFFIX(ratio<60>, "min") - _GLIBCXX_UNITS_SUFFIX(ratio<3600>, "h") - _GLIBCXX_UNITS_SUFFIX(ratio<86400>, "d") -#undef _GLIBCXX_UNITS_SUFFIX - return __detail::__units_suffix_misc<_Period>(__buf, __n); - } - } // namespace __detail - /// @endcond - - template - inline basic_ostream<_CharT, _Traits>& - operator<<(std::basic_ostream<_CharT, _Traits>& __os, - const duration<_Rep, _Period>& __d) - { - using period = typename _Period::type; - char __buf[sizeof("[/]s") + 2 * numeric_limits::digits10]; - std::basic_ostringstream<_CharT, _Traits> __s; - __s.flags(__os.flags()); - __s.imbue(__os.getloc()); - __s.precision(__os.precision()); - __s << __d.count(); - __s << __detail::__units_suffix(__buf, sizeof(__buf)); - __os << std::move(__s).str(); - return __os; - } - - // TODO: from_stream for duration - - /// @} group chrono - } // namespace chrono #endif // C++20 _GLIBCXX_END_NAMESPACE_VERSION } // namespace std +#if __cplusplus >= 202002L +# include +#endif + #endif // C++11 #endif //_GLIBCXX_CHRONO diff --git a/libstdc++-v3/src/c++20/tzdb.cc b/libstdc++-v3/src/c++20/tzdb.cc index dcff021d9d4..a0bb03173a9 100644 --- a/libstdc++-v3/src/c++20/tzdb.cc +++ b/libstdc++-v3/src/c++20/tzdb.cc @@ -511,27 +511,25 @@ namespace std::chrono friend ostream& operator<<(ostream& out, const Rule& r) { out << "Rule " << r.name << ' ' << (int)r.from << ' ' << (int)r.to - << ' ' << (unsigned)r.when.day.get_month() << ' '; + << ' ' << r.when.day.get_month() << ' '; switch (r.when.day.kind) { case on_day::DayOfMonth: out << (unsigned)r.when.day.get_day(); break; case on_day::LastWeekday: - out << "last" << weekday(r.when.day.day_of_week).c_encoding(); + out << "last" << weekday(r.when.day.day_of_week); break; case on_day::LessEq: - out << weekday(r.when.day.day_of_week).c_encoding() << " <= " + out << weekday(r.when.day.day_of_week) << " <= " << r.when.day.day_of_month; break; case on_day::GreaterEq: - out << weekday(r.when.day.day_of_week).c_encoding() << " >= " + out << weekday(r.when.day.day_of_week) << " >= " << r.when.day.day_of_month; break; } - hh_mm_ss hms(r.when.time); - out << ' ' << hms.hours().count() << ':' << hms.minutes().count() - << ':' << hms.seconds().count() << "wusd"[r.when.indicator]; + out << ' ' << hh_mm_ss(r.when.time) << "wusd"[r.when.indicator]; out << ' ' << r.save.count(); if (!r.letters.empty()) out << ' ' << r.letters; diff --git a/libstdc++-v3/testsuite/20_util/duration/io.cc b/libstdc++-v3/testsuite/20_util/duration/io.cc index 405e1afa440..27586b54392 100644 --- a/libstdc++-v3/testsuite/20_util/duration/io.cc +++ b/libstdc++-v3/testsuite/20_util/duration/io.cc @@ -47,8 +47,56 @@ test02() #endif } +void +test_format() +{ + using namespace std::chrono_literals; + auto s = std::format("{} {}", 1h + 23min + 45s, -42min); + VERIFY( s == "5025s -42min" ); + s = std::format("{:%j} {:%j} {:%j}", 1h + 23min + 45s, 75h, -99h); + VERIFY( s == "0 3 -4" ); + s = std::format("{:%T = %H:%M:%S}", 1h + 23min + 45s); + VERIFY( s == "01:23:45 = 01:23:45" ); + s = std::format("{:%Q} {:%q} {:%Q%q}", 6min + 1s, 44min, -22h); + VERIFY( s == "361 min -22h" ); + + std::wstring ws = std::format(L"{:%Q%q}", 81s); + VERIFY( ws == L"81s" ); + + // Only print '-' on numeric fields for negative durations: + s = std::format("{:%Q} {:%q} {:%q%Q}", -21h, -20h, -19h); + VERIFY( s == "-21 h h-19" ); + s = std::format("{:%p} {:%p%H}", -2h, -13h); + VERIFY( s == "AM PM-13" ); + s = std::format("{:%t} {:%t%M}", -2h, -123s); + VERIFY( s == "\t \t-02" ); + + std::string_view specs = "aAbBcCdDeFgGhHIjmMpqQrRSTuUVwWxXyYzZ"; + std::string_view my_specs = "HIjMpqQrRSTX"; + for (char c : specs) + { + char fmt[] = { '{', ':', '%', c, '}' }; + try + { + (void) std::vformat(std::string_view(fmt, 5), std::make_format_args(1s)); + // The call above should throw for any conversion-spec not in my_specs: + VERIFY(my_specs.find(c) != my_specs.npos); + } + catch (const std::format_error& e) + { + VERIFY(my_specs.find(c) == my_specs.npos); + std::string_view s = e.what(); + // Libstdc++-specific message: + VERIFY(s.find("format argument does not contain the information " + "required by the chrono-specs") != s.npos); + } + } +} + int main() { test01(); test02(); + test_format(); + // TODO: test_parse(); } diff --git a/libstdc++-v3/testsuite/std/time/clock/file/io.cc b/libstdc++-v3/testsuite/std/time/clock/file/io.cc new file mode 100644 index 00000000000..c8e82bb111c --- /dev/null +++ b/libstdc++-v3/testsuite/std/time/clock/file/io.cc @@ -0,0 +1,23 @@ +// { dg-options "-std=gnu++20" } +// { dg-do run { target c++20 } } + +#include +#include +#include + +void +test_ostream() +{ + using namespace std::chrono; + + file_time t = file_clock::now(); + std::ostringstream ss1, ss2; + ss1 << floor(t); + ss2 << floor(clock_cast(t)); + VERIFY( ss1.str() == ss2.str() ); +} + +int main() +{ + test_ostream(); +} diff --git a/libstdc++-v3/testsuite/std/time/clock/gps/io.cc b/libstdc++-v3/testsuite/std/time/clock/gps/io.cc new file mode 100644 index 00000000000..29f3148cf14 --- /dev/null +++ b/libstdc++-v3/testsuite/std/time/clock/gps/io.cc @@ -0,0 +1,24 @@ +// { dg-options "-std=gnu++20" } +// { dg-do run { target c++20 } } + +#include +#include +#include + +void +test01() +{ + using std::format; + using namespace std::chrono; + + auto st = sys_days{2000y/January/1}; + auto gt = clock_cast(st); + + auto s = format("{0:%F %T %Z} == {1:%F %T %Z}", st, gt); + VERIFY( s == "2000-01-01 00:00:00 UTC == 2000-01-01 00:00:13 GPS" ); +} + +int main() +{ + test01(); +} diff --git a/libstdc++-v3/testsuite/std/time/clock/system/io.cc b/libstdc++-v3/testsuite/std/time/clock/system/io.cc new file mode 100644 index 00000000000..e7feebc9a3c --- /dev/null +++ b/libstdc++-v3/testsuite/std/time/clock/system/io.cc @@ -0,0 +1,72 @@ +// { dg-options "-std=gnu++20" } +// { dg-do run { target c++20 } } + +#include +#include +#include + +void +test_ostream() +{ + using namespace std::chrono; + std::stringstream ss; + ss << sys_seconds{0s} << '\n'; // 1970-01-01 00:00:00 + ss << sys_seconds{946'684'800s} << '\n'; // 2000-01-01 00:00:00 + ss << sys_seconds{946'688'523s} << '\n'; // 2000-01-01 01:02:03 + std::string s1, s2, s3; + std::getline(ss, s1); + std::getline(ss, s2); + std::getline(ss, s3); + VERIFY( s1 == "1970-01-01 00:00:00" ); + VERIFY( s2 == "2000-01-01 00:00:00" ); + VERIFY( s3 == "2000-01-01 01:02:03" ); +} + +template +concept stream_insertable + = requires (std::ostream& out, const T& t) { out << t; }; + +// operator<<(ostream&, const sys_time&) is constrained to not +// allow floating-point types or periods of days or greater. +using fp_sys_time = std::chrono::sys_time>; +static_assert( !stream_insertable ); + +// But there is an overload for sys_days. +static_assert( stream_insertable ); + +void +test_format() +{ + using namespace std::chrono_literals; + std::chrono::sys_time t(1671470785708ms); + + // Every conversion specifier is valid for a sys_time except %q and %Q. + + std::string s = std::format("{:%a | %A | %b | %B | %c" + " | %C | %d | %D | %e | %F | %g | %G | %h" + " | %H | %I | %j | %m | %M | %p | %r | %R" + " | %S | %T | %u | %U | %V | %w | %W | %x" + " | %X | %y | %Y | %z | %Z}", t); + VERIFY( s == "Mon | Monday | Dec | December | Mon Dec 19 17:26:25.708 2022" + " | 20 | 19 | 12/19/22 | 19 | 2022-12-19 | 22 | 2022 | Dec" + " | 17 | 05 | 353 | 12 | 26 | PM | 05:26:25.708 PM | 17:26" + " | 25.708 | 17:26:25.708 | 1 | 51 | 51 | 1 | 51 | 12/19/22" + " | 17:26:25.708 | 22 | 2022 | +0000 | UTC" ); + + std::wstring ws = std::format(L"{:%a | %A | %b | %B | %c" + " | %C | %d | %D | %e | %F | %g | %G | %h" + " | %H | %I | %j | %m | %M | %p | %r | %R" + " | %S | %T | %u | %U | %V | %w | %W | %x" + " | %X | %y | %Y | %z | %Z}", t); + VERIFY( ws == L"Mon | Monday | Dec | December | Mon Dec 19 17:26:25.708 2022" + " | 20 | 19 | 12/19/22 | 19 | 2022-12-19 | 22 | 2022 | Dec" + " | 17 | 05 | 353 | 12 | 26 | PM | 05:26:25.708 PM | 17:26" + " | 25.708 | 17:26:25.708 | 1 | 51 | 51 | 1 | 51 | 12/19/22" + " | 17:26:25.708 | 22 | 2022 | +0000 | UTC" ); +} + +int main() +{ + test_ostream(); + test_format(); +} diff --git a/libstdc++-v3/testsuite/std/time/clock/tai/io.cc b/libstdc++-v3/testsuite/std/time/clock/tai/io.cc new file mode 100644 index 00000000000..d0255f5431a --- /dev/null +++ b/libstdc++-v3/testsuite/std/time/clock/tai/io.cc @@ -0,0 +1,24 @@ +// { dg-options "-std=gnu++20" } +// { dg-do run { target c++20 } } + +#include +#include +#include + +void +test01() +{ + using std::format; + using namespace std::chrono; + + auto st = sys_days{2000y/January/1}; + auto tt = clock_cast(st); + + auto s = format("{0:%F %T %Z} == {1:%F %T %Z}", st, tt); + VERIFY( s == "2000-01-01 00:00:00 UTC == 2000-01-01 00:00:32 TAI" ); +} + +int main() +{ + test01(); +} diff --git a/libstdc++-v3/testsuite/std/time/clock/utc/io.cc b/libstdc++-v3/testsuite/std/time/clock/utc/io.cc new file mode 100644 index 00000000000..b327c7f50c7 --- /dev/null +++ b/libstdc++-v3/testsuite/std/time/clock/utc/io.cc @@ -0,0 +1,120 @@ +// { dg-options "-std=gnu++20" } +// { dg-do run { target c++20 } } + +#include +#include +#include + +void +test_ostream() +{ + using std::ostringstream; + using namespace std::chrono; + + auto t = sys_days{July/1/2015} - 500ms; + auto u = clock_cast(t); + + std::string_view results[] = { + "2015-06-30 23:59:59.500 UTC", + "2015-06-30 23:59:59.750 UTC", + "2015-06-30 23:59:60.000 UTC", + "2015-06-30 23:59:60.250 UTC", + "2015-06-30 23:59:60.500 UTC", + "2015-06-30 23:59:60.750 UTC", + "2015-07-01 00:00:00.000 UTC", + "2015-07-01 00:00:00.250 UTC", + }; + + for (auto result : results) + { + ostringstream out; + out << u << " UTC"; + VERIFY( out.str() == result ); + u += 250ms; + } +} + +void +test_format() +{ + using namespace std::chrono_literals; + std::chrono::utc_time t(1671470812708ms); + + // Every conversion specifier is valid for a utc_time except %q and %Q. + std::string_view specs = "aAbBcCdDeFgGhHIjmMpqQrRSTuUVwWxXyYzZ"; + std::string_view badspecs = "qQ"; + + std::ostringstream ss; + std::wostringstream wss; + + for (char c : specs) + { + char fmt[] = { '{', ':', '%', c, '}' }; + try + { + ss << std::vformat(std::string_view(fmt, 5), + std::make_format_args(t)); + ss << " | "; + // The call above should throw for any conversion-spec in badspecs: + VERIFY(badspecs.find(c) == badspecs.npos); + + } + catch (const std::format_error& e) + { + VERIFY(badspecs.find(c) != badspecs.npos); + std::string_view s = e.what(); + // Libstdc++-specific message: + VERIFY(s.find("format argument does not contain the information " + "required by the chrono-specs") != s.npos); + } + + wchar_t wfmt[] = { L'{', L':', L'%', c, L'}' }; + try + { + wss << std::vformat(std::wstring_view(wfmt, 5), + std::make_wformat_args(t)); + wss << L" | "; + // The call above should throw for any conversion-spec in badspecs: + VERIFY(badspecs.find(c) == badspecs.npos); + } + catch (const std::format_error& e) + { + VERIFY(badspecs.find(c) != badspecs.npos); + std::string_view s = e.what(); + // Libstdc++-specific message: + VERIFY(s.find("format argument does not contain the information " + "required by the chrono-specs") != s.npos); + } + } + + std::string s = ss.str(); + VERIFY( s == "Mon | Monday | Dec | December | Mon Dec 19 17:26:25.708 2022" + " | 20 | 19 | 12/19/22 | 19 | 2022-12-19 | 22 | 2022 | Dec" + " | 17 | 05 | 353 | 12 | 26 | PM | 05:26:25.708 PM | 17:26" + " | 25.708 | 17:26:25.708 | 1 | 51 | 51 | 1 | 51 | 12/19/22" + " | 17:26:25.708 | 22 | 2022 | +0000 | UTC | " ); + + std::wstring ws = wss.str(); + VERIFY( ws == L"Mon | Monday | Dec | December | Mon Dec 19 17:26:25.708 2022" + " | 20 | 19 | 12/19/22 | 19 | 2022-12-19 | 22 | 2022 | Dec" + " | 17 | 05 | 353 | 12 | 26 | PM | 05:26:25.708 PM | 17:26" + " | 25.708 | 17:26:25.708 | 1 | 51 | 51 | 1 | 51 | 12/19/22" + " | 17:26:25.708 | 22 | 2022 | +0000 | UTC | " ); + + std::chrono::utc_seconds leap(1483228800s + 26s); // 1 Jan 2017 + s = std::format("{:%T}", leap - 1s); + VERIFY( s == "23:59:59" ); + s = std::format("{:%T}", leap); + VERIFY( s == "23:59:60" ); + s = std::format("{:%T}", leap + 10ms); + VERIFY( s == "23:59:60.010" ); + + s = std::format("{:%T}", leap + 1s); + VERIFY( s == "00:00:00" ); +} + +int main() +{ + test_ostream(); + test_format(); +} diff --git a/libstdc++-v3/testsuite/std/time/day/io.cc b/libstdc++-v3/testsuite/std/time/day/io.cc new file mode 100644 index 00000000000..6158230f288 --- /dev/null +++ b/libstdc++-v3/testsuite/std/time/day/io.cc @@ -0,0 +1,75 @@ +// { dg-options "-std=gnu++20" } +// { dg-do run { target c++20 } } +// { dg-require-namedlocale "fr_FR.ISO8859-15" } + +#include +#include +#include + +void +test_ostream() +{ + using std::ostringstream; + using namespace std::chrono; + + ostringstream ss; + ss << day(1) << ' ' << day(11) << ' ' << day(21) << ' ' << day(31) + << ' ' << day(41); + auto s = ss.str(); + VERIFY( s == "01 11 21 31 41 is not a valid day" ); + + ss.str(""); + ss.imbue(std::locale(ISO_8859(15,fr_FR))); + ss << day(1); + VERIFY( ss.str() == "01" ); +} + +void +test_format() +{ + using std::chrono::day; + + auto s = std::format("{:%d%%%e%t}{:%d%%%e%n}", day(1), day(11)); + VERIFY( s == "01% 1\t11%11\n" ); + auto ws = std::format(L"{:%d%%%e%t}{:%d%%%e%n}", day(1), day(11)); + VERIFY( ws == L"01% 1\t11%11\n" ); + + VERIFY( std::format("{} {}", day(8), day(0)) == "08 00 is not a valid day" ); + + s = std::format("{:%Od}", day(1)); + VERIFY( s == "01" ); + s = std::format(std::locale::classic(), "{:%Od}", day(1)); + VERIFY( s == "01" ); + s = std::format(std::locale::classic(), "{:L%Od}", day(1)); + VERIFY( s == "01" ); + // TODO test "{:L%Od}" with locale that has alternative numeric rep. + + std::string_view specs = "aAbBcCdDeFgGhHIjmMpqQrRSTuUVwWxXyYzZ"; + std::string_view my_specs = "de"; + for (char c : specs) + { + char fmt[] = { '{', ':', '%', c, '}' }; + try + { + (void) std::vformat(std::string_view(fmt, 5), + std::make_format_args(day(1))); + // The call above should throw for any conversion-spec not in my_specs: + VERIFY(my_specs.find(c) != my_specs.npos); + } + catch (const std::format_error& e) + { + VERIFY(my_specs.find(c) == my_specs.npos); + std::string_view s = e.what(); + // Libstdc++-specific message: + VERIFY(s.find("format argument does not contain the information " + "required by the chrono-specs") != s.npos); + } + } +} + +int main() +{ + test_ostream(); + test_format(); + // TODO: test_parse(); +} diff --git a/libstdc++-v3/testsuite/std/time/exceptions.cc b/libstdc++-v3/testsuite/std/time/exceptions.cc index 1b81d5ee27a..650b1fe7a37 100644 --- a/libstdc++-v3/testsuite/std/time/exceptions.cc +++ b/libstdc++-v3/testsuite/std/time/exceptions.cc @@ -20,7 +20,7 @@ test_nonexistent() local_days{Sunday[2]/March/2016} + 2h + 30min}; VERIFY(false); } catch (const nonexistent_local_time& e) { - // VERIFY( e.what() == expected ); + VERIFY( e.what() == expected ); } } @@ -38,7 +38,7 @@ test_ambiguous() local_days{Sunday[1]/November/2016} + 1h + 30min}; VERIFY(false); } catch (const ambiguous_local_time& e) { - // VERIFY( e.what() == expected ); + VERIFY( e.what() == expected ); } } diff --git a/libstdc++-v3/testsuite/std/time/format.cc b/libstdc++-v3/testsuite/std/time/format.cc new file mode 100644 index 00000000000..b05e5da1af8 --- /dev/null +++ b/libstdc++-v3/testsuite/std/time/format.cc @@ -0,0 +1,117 @@ +// { dg-options "-std=gnu++20" } +// { dg-do run { target c++20 } } + +#include +#include + +void +test_format_strings() +{ + using namespace std::chrono_literals; + + // valid format strings + VERIFY( std::format("{}", 1s) == "1s" ); + VERIFY( std::format("{:}", 1s) == "1s" ); + VERIFY( std::format("{:L}", 1s) == "1s" ); + VERIFY( std::format("{:%%%n%t}", 1s) == "%\n\t" ); + VERIFY( std::format("{:L%%%n%t}", 1s) == "%\n\t" ); + VERIFY( std::format("{:4%%}", 1s) == "% " ); + VERIFY( std::format("{:4L%%}", 1s) == "% " ); + VERIFY( std::format("{: >4}", 1s) == " 1s" ); + VERIFY( std::format("{: <4}", 1s) == "1s " ); + VERIFY( std::format("{: <4L}", 1s) == "1s " ); + VERIFY( std::format("{: >4%%}", 1s) == " %" ); + VERIFY( std::format("{: >4L%%}", 1s) == " %" ); + VERIFY( std::format("{: ^4%%}", 1s) == " % " ); +} + +template +bool +is_format_string_for(const char* str, Args&&... args) +{ + try { + (void) std::vformat(str, std::make_format_args(args...)); + return true; + } catch (const std::format_error&) { + return false; + } +} + +void +test_bad_format_strings() +{ + std::chrono::sys_seconds t{}; + + // literal '%' must be formatted as "%%" + VERIFY( not is_format_string_for("{:%}", t) ); + + // chrono-specs must start with '%' + VERIFY( not is_format_string_for("{:a%}", t) ); + VERIFY( not is_format_string_for("{:La%}", t) ); + + // '{' not valid in chrono-specs + VERIFY( not is_format_string_for("{:%%{{%%}", t) ); + + // padding with leading zero not valid for chrono types + VERIFY( not is_format_string_for("{:04%T}", t) ); + + // precision only valid for chrono::duration types with floating-point rep. + VERIFY( not is_format_string_for("{:.4}", t) ); + + // unfinished format string + VERIFY( not is_format_string_for("{:", t) ); + + // dangling modifiers + VERIFY( not is_format_string_for("{:%E}", t) ); + VERIFY( not is_format_string_for("{:%O}", t) ); + + // modifier not valid for conversion specifier + VERIFY( not is_format_string_for("{:%Ea}", t) ); + VERIFY( not is_format_string_for("{:%Oa}", t) ); +} + +template +struct move_only_iterator +{ + using iterator = I; + using value_type = iterator::value_type; + using difference_type = iterator::difference_type; + using iterator_category = std::output_iterator_tag; + + move_only_iterator(iterator b) : base_(b) { } + move_only_iterator(move_only_iterator&&) = default; + move_only_iterator& operator=(move_only_iterator&&) = default; + + move_only_iterator& operator++() { ++base_; return *this; } + move_only_iterator operator++(int) { auto tmp = *this; ++base_; return tmp; } + + decltype(auto) operator*() { return *base_; } + +private: + iterator base_; +}; + +void +test_move_only_iterator() +{ + using namespace std::chrono; + utc_seconds ut(1671543754s); + sys_seconds st(1671543727s); + + std::string str; + move_only_iterator mo(std::back_inserter(str)); + std::format_to(std::move(mo), "{:%F} {:%T} {:%Q}", ut, st, 1s); + VERIFY( str == "2022-12-20 13:42:07 1" ); + + std::vector vec; + move_only_iterator wmo(std::back_inserter(vec)); + std::format_to(std::move(wmo), L"{:%F} {:%T} {:%Q}", ut, st, 2s); + VERIFY( std::wstring_view(vec.data(), vec.size()) == L"2022-12-20 13:42:07 2" ); +} + +int main() +{ + test_format_strings(); + test_bad_format_strings(); + test_move_only_iterator(); +} diff --git a/libstdc++-v3/testsuite/std/time/hh_mm_ss/io.cc b/libstdc++-v3/testsuite/std/time/hh_mm_ss/io.cc new file mode 100644 index 00000000000..3b50f40c1f6 --- /dev/null +++ b/libstdc++-v3/testsuite/std/time/hh_mm_ss/io.cc @@ -0,0 +1,46 @@ +// { dg-options "-std=gnu++20" } +// { dg-do run { target c++20 } } + +#include +#include +#include + +void +test01() +{ + using std::ostringstream; + using std::chrono::hh_mm_ss; + using namespace std::chrono_literals; + + std::locale::global(std::locale::classic()); + + { + hh_mm_ss hms{-4083007ms}; + ostringstream out; + out << hms; + VERIFY( out.str() == "-01:08:03.007" ); + } + + { + hh_mm_ss hms{4083007ms}; + ostringstream out; + out << hms; + VERIFY( out.str() == "01:08:03.007" ); + } + + { + hh_mm_ss hms{65745123ms}; + ostringstream out; + out << hms; + VERIFY( out.str() == "18:15:45.123" ); + } + + ostringstream out; + out << hh_mm_ss{65745s}; + VERIFY( out.str() == "18:15:45" ); +} + +int main() +{ + test01(); +} diff --git a/libstdc++-v3/testsuite/std/time/month/io.cc b/libstdc++-v3/testsuite/std/time/month/io.cc new file mode 100644 index 00000000000..7ceeafd725a --- /dev/null +++ b/libstdc++-v3/testsuite/std/time/month/io.cc @@ -0,0 +1,98 @@ +// { dg-options "-std=gnu++20" } +// { dg-do run { target c++20 } } +// { dg-require-namedlocale "fr_FR.ISO8859-15" } + +#include +#include +#include + +void +test_ostream() +{ + using std::ostringstream; + using namespace std::chrono; + + ostringstream ss; + for (int i = 1; i <= 12; ++i) + ss << month(i); + VERIFY( ss.str() == "JanFebMarAprMayJunJulAugSepOctNovDec" ); + ss.str(""); + ss << month(0) << '|' << month(13); + VERIFY( ss.str() == "0 is not a valid month|13 is not a valid month" ); + + ss.str(""); + ss.imbue(std::locale(ISO_8859(15,fr_FR))); + ss << month(1); + VERIFY( ss.str() == "janv." ); +} + +void +test_format() +{ + using std::chrono::month; + + auto s = std::format("{:%b%%%B%t%m%n}", month(1)); + VERIFY( s == "Jan%January\t01\n" ); + auto ws = std::format(L"{:%b%%%B%t%m%n}", month(12)); + VERIFY( ws == L"Dec%December\t12\n" ); + + try + { + (void) std::format("{:%b}", month(13)); + VERIFY(false); + } + catch (const std::format_error&) + { + } + + s = std::format("{} is OK, but {:L}", month(2), month(13)); + VERIFY( s == "Feb is OK, but 13 is not a valid month" ); + + std::locale loc_fr(ISO_8859(15,fr_FR)); + + s = std::format("{:%Om}", month(1)); + VERIFY( s == "01" ); + s = std::format(std::locale::classic(), "{:%Om}", month(1)); + VERIFY( s == "01" ); + s = std::format(std::locale::classic(), "{:L%Om}", month(1)); + VERIFY( s == "01" ); + s = std::format(loc_fr, "{:%Om}", month(1)); + VERIFY( s == "01" ); + s = std::format(loc_fr, "{:L%Om}", month(1)); + VERIFY( s == "01" ); + // TODO test "{:L%Om}" with locale that has alternative numeric rep. + + s = std::format(loc_fr, "{:%b}", month(1)); + VERIFY( s == "Jan" ); + s = std::format(loc_fr, "{:L%b}", month(1)); + VERIFY( s == "janv." ); + + std::string_view specs = "aAbBcCdDeFgGhHIjmMpqQrRSTuUVwWxXyYzZ"; + std::string_view my_specs = "bBhm"; + for (char c : specs) + { + char fmt[] = { '{', ':', '%', c, '}' }; + try + { + (void) std::vformat(std::string_view(fmt, 5), + std::make_format_args(month(1))); + // The call above should throw for any conversion-spec not in my_specs: + VERIFY(my_specs.find(c) != my_specs.npos); + } + catch (const std::format_error& e) + { + VERIFY(my_specs.find(c) == my_specs.npos); + std::string_view s = e.what(); + // Libstdc++-specific message: + VERIFY(s.find("format argument does not contain the information " + "required by the chrono-specs") != s.npos); + } + } +} + +int main() +{ + test_ostream(); + test_format(); + // TODO: test_parse(); +} diff --git a/libstdc++-v3/testsuite/std/time/syn_c++20.cc b/libstdc++-v3/testsuite/std/time/syn_c++20.cc index c91723bebd6..ad6f58bc9a1 100644 --- a/libstdc++-v3/testsuite/std/time/syn_c++20.cc +++ b/libstdc++-v3/testsuite/std/time/syn_c++20.cc @@ -124,8 +124,7 @@ namespace __gnu_test using std::chrono::time_zone_link; #endif - // FIXME - // using std::chrono::local_time_format; + using std::chrono::local_time_format; // FIXME // using std::chrono::parse; diff --git a/libstdc++-v3/testsuite/std/time/time_zone/get_info_local.cc b/libstdc++-v3/testsuite/std/time/time_zone/get_info_local.cc index d15e1c7036e..46ca34b71fd 100644 --- a/libstdc++-v3/testsuite/std/time/time_zone/get_info_local.cc +++ b/libstdc++-v3/testsuite/std/time/time_zone/get_info_local.cc @@ -148,7 +148,6 @@ test_egypt() VERIFY( info.second.save == 1h ); VERIFY( info.second.abbrev == "EEST" ); -#if 0 std::ostringstream out; local_seconds lt(local_days(2001y/January/1)); const local_days end(2021y/January/1); @@ -209,7 +208,6 @@ test_egypt() [[2014-09-25 21:00:00,32767-12-31 00:00:00,02:00:00,0min,EET]] )"; VERIFY( out.str() == expected ); -#endif } int main() diff --git a/libstdc++-v3/testsuite/std/time/weekday/io.cc b/libstdc++-v3/testsuite/std/time/weekday/io.cc new file mode 100644 index 00000000000..6cdb98467b1 --- /dev/null +++ b/libstdc++-v3/testsuite/std/time/weekday/io.cc @@ -0,0 +1,101 @@ +// { dg-options "-std=gnu++20" } +// { dg-do run { target c++20 } } +// { dg-require-namedlocale "fr_FR.ISO8859-15" } + +#include +#include +#include + +void +test_ostream() +{ + using std::ostringstream; + using namespace std::chrono; + + ostringstream ss; + for (int i = 0; i <= 7; ++i) + ss << weekday(i); + VERIFY( ss.str() == "SunMonTueWedThuFriSatSun" ); + ss.str(""); + ss << weekday(8) << '|' << weekday(99); + VERIFY( ss.str() == "8 is not a valid weekday|99 is not a valid weekday" ); + + ss.str(""); + ss.imbue(std::locale(ISO_8859(15,fr_FR))); + ss << weekday(6); + VERIFY( ss.str() == "sam." ); +} + +void +test_format() +{ + using std::chrono::weekday; + + auto s = std::format("{:%a%%%A%t%u%n%w}", std::chrono::Monday); + VERIFY( s == "Mon%Monday\t1\n1" ); + auto ws = std::format(L"{:%a%%%A%t%u%n%w}", weekday(7)); + VERIFY( ws == L"Sun%Sunday\t7\n0" ); + + s = std::format("{:%w}", weekday(8)); + VERIFY( s == "8" ); + + try + { + (void) std::format("{:%a}", weekday(8)); + VERIFY(false); + } + catch (const std::format_error&) + { + } + + s = std::format("{} is OK, but {:L}", weekday(2), weekday(13)); + VERIFY( s == "Tue is OK, but 13 is not a valid weekday" ); + + std::locale loc_fr(ISO_8859(15,fr_FR)); + + s = std::format("{:%Ow}", weekday(1)); + VERIFY( s == "1" ); + s = std::format(std::locale::classic(), "{:%Ow}", weekday(1)); + VERIFY( s == "1" ); + s = std::format(std::locale::classic(), "{:L%Ow}", weekday(1)); + VERIFY( s == "1" ); + s = std::format(loc_fr, "{:%Ow}", weekday(1)); + VERIFY( s == "1" ); + s = std::format(loc_fr, "{:L%Ow}", weekday(1)); + VERIFY( s == "1" ); + // TODO test "{:L%Ow}" with locale that has alternative numeric rep. + + s = std::format(loc_fr, "{:%a}", weekday(1)); + VERIFY( s == "Mon" ); + s = std::format(loc_fr, "{:L%a}", weekday(1)); + VERIFY( s == "lun." ); + + std::string_view specs = "aAbBcCdDeFgGhHIjmMpqQrRSTuUVwWxXyYzZ"; + std::string_view my_specs = "aAuw"; + for (char c : specs) + { + char fmt[] = { '{', ':', '%', c, '}' }; + try + { + (void) std::vformat(std::string_view(fmt, 5), + std::make_format_args(weekday(1))); + // The call above should throw for any conversion-spec not in my_specs: + VERIFY(my_specs.find(c) != my_specs.npos); + } + catch (const std::format_error& e) + { + VERIFY(my_specs.find(c) == my_specs.npos); + std::string_view s = e.what(); + // Libstdc++-specific message: + VERIFY(s.find("format argument does not contain the information " + "required by the chrono-specs") != s.npos); + } + } +} + +int main() +{ + test_ostream(); + test_format(); + // TODO: test_parse(); +} diff --git a/libstdc++-v3/testsuite/std/time/year/io.cc b/libstdc++-v3/testsuite/std/time/year/io.cc new file mode 100644 index 00000000000..07316e98aa5 --- /dev/null +++ b/libstdc++-v3/testsuite/std/time/year/io.cc @@ -0,0 +1,89 @@ +// { dg-options "-std=gnu++20" } +// { dg-do run { target c++20 } } +// { dg-require-namedlocale "fr_FR.ISO8859-15" } + +#include +#include +#include + +void +test_ostream() +{ + using std::ostringstream; + using namespace std::chrono; + + ostringstream ss; + for (int y : {-1234, -44, -1, 0, 5, 32, 325, 1066, 2022}) + ss << year(y) << ' '; + VERIFY( ss.str() == "-1234 -0044 -0001 0000 0005 0032 0325 1066 2022 " ); + ss.str(""); + ss << year::min() << ' ' << year::max() << ' ' << --year::min(); + VERIFY( ss.str() == "-32767 32767 -32768 is not a valid year" ); + + ss.str(""); + ss.imbue(std::locale(ISO_8859(15,fr_FR))); + ss << 1789y; + VERIFY( ss.str() == "1789" ); +} + +void +test_format() +{ + using std::chrono::year; + using namespace std::chrono_literals; + + auto s = std::format("{:%y%%%Y%t%C%n}", 2022y); + VERIFY( s == "22%2022\t20\n" ); + auto ws = std::format(L"{:%y%%%Y%t%C%n}", 2023y); + VERIFY( ws == L"23%2023\t20\n" ); + + s = std::format("{:%Y}", --year::min()); + VERIFY( s == "-32768" ); + + s = std::format("{}", --year::min()); // formatted via ostream + VERIFY( s == "-32768 is not a valid year" ); + + s = std::format("{:%y} {:%y}", 1976y, -1976y); + VERIFY( s == "76 76" ); // LWG 3831 + + s = std::format("{0:%EC}{0:%Ey} = {0:%EY}", 1642y); + VERIFY( s == "1642 = 1642" ); + s = std::format("{0:L%EC}{0:L%Ey} = {0:L%EY}", 1642y); + VERIFY( s == "1642 = 1642" ); + s = std::format(std::locale::classic(), "{0:L%EC}{0:L%Ey} = {0:L%EY}", 1642y); + VERIFY( s == "1642 = 1642" ); + + // TODO test "{:L%EC}" with locale that has alternative era rep. + // TODO test "{:L%Ey}" with locale that has alternative year rep. + // TODO test "{:L%EY}" with locale that has alternative year rep. + // TODO test "{:L%Oy}" with locale that has alternative numeric rep. + + std::string_view specs = "aAbBcCdDeFgGhHIjmMpqQrRSTuUVwWxXyYzZ"; + std::string_view my_specs = "CyY"; + for (char c : specs) + { + char fmt[] = { '{', ':', '%', c, '}' }; + try + { + (void) std::vformat(std::string_view(fmt, 5), + std::make_format_args(year(2022))); + // The call above should throw for any conversion-spec not in my_specs: + VERIFY(my_specs.find(c) != my_specs.npos); + } + catch (const std::format_error& e) + { + VERIFY(my_specs.find(c) == my_specs.npos); + std::string_view s = e.what(); + // Libstdc++-specific message: + VERIFY(s.find("format argument does not contain the information " + "required by the chrono-specs") != s.npos); + } + } +} + +int main() +{ + test_ostream(); + test_format(); + // TODO: test_parse(); +} diff --git a/libstdc++-v3/testsuite/std/time/year_month_day/io.cc b/libstdc++-v3/testsuite/std/time/year_month_day/io.cc new file mode 100644 index 00000000000..688885b37a1 --- /dev/null +++ b/libstdc++-v3/testsuite/std/time/year_month_day/io.cc @@ -0,0 +1,121 @@ +// { dg-options "-std=gnu++20" } +// { dg-do run { target c++20 } } +// { dg-require-namedlocale "fr_FR.ISO8859-15" } + +#include +#include +#include + +void +test_ostream() +{ + using std::ostringstream; + using namespace std::chrono; + + ostringstream ss; + ss << 2022y/December/19 << ' ' << 2022y/November/31; + VERIFY( ss.str() == "2022-12-19 2022-11-31 is not a valid date" ); + + ss.str(""); + ss.imbue(std::locale(ISO_8859(15,fr_FR))); + ss << 1789y/July/14; + VERIFY( ss.str() == "1789-07-14" ); +} + +void +test_format() +{ + using std::chrono::year_month_day; + using std::chrono::December; + using std::chrono::January; + using namespace std::chrono_literals; + + auto s = std::format("{:%y%%%Y%t%C%n%j %a %b}", 2022y/December/19); + VERIFY( s == "22%2022\t20\n353 Mon Dec" ); + auto ws = std::format(L"{:%y%%%Y%t%C%n%d}", 2023y/January/32); + VERIFY( ws == L"23%2023\t20\n32" ); + + s = std::format("{:%F} {}", 2023y/January/32, 2023y/January/32); + VERIFY( s == "2023-01-32 2023-01-32 is not a valid date" ); + + s = std::format("{:%C%g-W%V-%u}", 2022y/January/1); + VERIFY( s == "2021-W52-6" ); + s = std::format("{:%G-W%V-%u}", 2022y/January/3); + VERIFY( s == "2022-W01-1" ); + + // %U: Week number for weeks starting on Sunday + s = std::format("Day {:%w (%a) of Week %U of %Y}", 2022y/January/1); + VERIFY( s == "Day 6 (Sat) of Week 00 of 2022" ); + s = std::format("Day {:%w (%a) of Week %U of %Y}", 2022y/January/2); + VERIFY( s == "Day 0 (Sun) of Week 01 of 2022" ); + // %W: Week number for weeks starting on Monday + s = std::format("Day {:%u (%a) of Week %W of %Y}", 2022y/January/2); + VERIFY( s == "Day 7 (Sun) of Week 00 of 2022" ); + s = std::format("Day {:%u (%a) of Week %W of %Y}", 2022y/January/3); + VERIFY( s == "Day 1 (Mon) of Week 01 of 2022" ); + + // %V: ISO week number (ISO 8601). + s = std::format("W{:%V}", 1977y/1/1); + VERIFY( s == "W53" ); + s = std::format("W{:%V}", 1977y/1/2); + VERIFY( s == "W53" ); + s = std::format("W{:%V}", 1977y/12/31); + VERIFY( s == "W52" ); + s = std::format("W{:%V}", 1978y/1/1); + VERIFY( s == "W52" ); + s = std::format("W{:%V}", 1978y/1/2); + VERIFY( s == "W01" ); + s = std::format("W{:%V}", 1978y/12/31); + VERIFY( s == "W52" ); + s = std::format("W{:%V}", 1979y/1/1); + VERIFY( s == "W01" ); + s = std::format("W{:%V}", 1979y/12/30); + VERIFY( s == "W52" ); + s = std::format("W{:%V}", 1979y/12/31); + VERIFY( s == "W01" ); + s = std::format("W{:%V}", 1980y/1/1); + VERIFY( s == "W01" ); + + s = std::format("{:%x}", 2022y/December/19); + VERIFY( s == "12/19/22" ); + s = std::format("{:L%x}", 2022y/December/19); + VERIFY( s == "12/19/22" ); + std::locale loc_fr(ISO_8859(15,fr_FR)); + s = std::format(loc_fr, "{:%x}", 2022y/December/19); + VERIFY( s == "12/19/22" ); + s = std::format(loc_fr, "{:L%x}", 2022y/December/19); + VERIFY( s == "19/12/2022" ); + s = std::format(loc_fr, "{}", 2022y/December/19); + VERIFY( s == "2022-12-19" ); + s = std::format(loc_fr, "{:L%F}", 2022y/December/19); + VERIFY( s == "2022-12-19" ); + + std::string_view specs = "aAbBcCdDeFgGhHIjmMpqQrRSTuUVwWxXyYzZ"; + std::string_view my_specs = "aAbBCdDeFgGhjmuUVwWxyY"; + for (char c : specs) + { + char fmt[] = { '{', ':', '%', c, '}' }; + try + { + (void) std::vformat(std::string_view(fmt, 5), + std::make_format_args(2022y/December/19)); + // The call above should throw for any conversion-spec not in my_specs: + VERIFY(my_specs.find(c) != my_specs.npos); + } + catch (const std::format_error& e) + { + VERIFY(my_specs.find(c) == my_specs.npos); + std::string_view s = e.what(); + // Libstdc++-specific message: + VERIFY(s.find("format argument does not contain the information " + "required by the chrono-specs") != s.npos); + } + } +} + +int main() +{ + test_ostream(); + test_format(); + // TODO: test_parse(); +}