diff --git a/libcxx/docs/Status/FormatPaper.csv b/libcxx/docs/Status/FormatPaper.csv index 343fa62f13565..beec97b8c0179 100644 --- a/libcxx/docs/Status/FormatPaper.csv +++ b/libcxx/docs/Status/FormatPaper.csv @@ -3,7 +3,7 @@ Section,Description,Dependencies,Assignee,Status,First released version `[time.syn] `_,"Formatter ``chrono::duration``",,Mark de Wever,|Complete|,16 `[time.syn] `_,"Formatter ``chrono::sys_time``",,Mark de Wever,|Complete|,17 `[time.syn] `_,"Formatter ``chrono::utc_time``",A ```` implementation,Mark de Wever,|Complete|,20 -`[time.syn] `_,"Formatter ``chrono::tai_time``",A ```` implementation,Mark de Wever,,, +`[time.syn] `_,"Formatter ``chrono::tai_time``",,Mark de Wever,|Complete|,21 `[time.syn] `_,"Formatter ``chrono::gps_time``",A ```` implementation,Mark de Wever,,, `[time.syn] `_,"Formatter ``chrono::file_time``",,Mark de Wever,|Complete|,17 `[time.syn] `_,"Formatter ``chrono::local_time``",,Mark de Wever,|Complete|,17 diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt index 8dac823503d73..ce805b4eb7b8b 100644 --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -270,6 +270,7 @@ set(files __chrono/steady_clock.h __chrono/sys_info.h __chrono/system_clock.h + __chrono/tai_clock.h __chrono/time_point.h __chrono/time_zone.h __chrono/time_zone_link.h diff --git a/libcxx/include/__chrono/convert_to_tm.h b/libcxx/include/__chrono/convert_to_tm.h index 7d06a38d87f26..d9ccf693160d2 100644 --- a/libcxx/include/__chrono/convert_to_tm.h +++ b/libcxx/include/__chrono/convert_to_tm.h @@ -23,6 +23,7 @@ #include <__chrono/statically_widen.h> #include <__chrono/sys_info.h> #include <__chrono/system_clock.h> +#include <__chrono/tai_clock.h> #include <__chrono/time_point.h> #include <__chrono/utc_clock.h> #include <__chrono/weekday.h> @@ -35,6 +36,7 @@ #include <__config> #include <__format/format_error.h> #include <__memory/addressof.h> +#include <__type_traits/common_type.h> #include <__type_traits/is_convertible.h> #include <__type_traits/is_specialization.h> #include @@ -112,6 +114,16 @@ _LIBCPP_HIDE_FROM_ABI _Tm __convert_to_tm(chrono::utc_time<_Duration> __tp) { return __result; } +template +_LIBCPP_HIDE_FROM_ABI _Tm __convert_to_tm(chrono::tai_time<_Duration> __tp) { + using _Rp = common_type_t<_Duration, chrono::seconds>; + // The time between the TAI epoch (1958-01-01) and UNIX epoch (1970-01-01). + // This avoids leap second conversion when going from TAI to UTC. + // (It also avoids issues when the date is before the UTC epoch.) + constexpr chrono::seconds __offset{4383 * 24 * 60 * 60}; + return std::__convert_to_tm<_Tm>(chrono::sys_time<_Rp>{__tp.time_since_epoch() - __offset}); +} + # endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB # endif // _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM && _LIBCPP_HAS_LOCALIZATION @@ -131,6 +143,8 @@ _LIBCPP_HIDE_FROM_ABI _Tm __convert_to_tm(const _ChronoT& __value) { # if _LIBCPP_HAS_EXPERIMENTAL_TZDB else if constexpr (same_as) return std::__convert_to_tm<_Tm>(__value); + else if constexpr (same_as) + return std::__convert_to_tm<_Tm>(__value); # endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB # endif // _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM && _LIBCPP_HAS_LOCALIZATION else if constexpr (same_as) diff --git a/libcxx/include/__chrono/formatter.h b/libcxx/include/__chrono/formatter.h index d17acd274e4cd..13bcb717291f6 100644 --- a/libcxx/include/__chrono/formatter.h +++ b/libcxx/include/__chrono/formatter.h @@ -31,6 +31,7 @@ # include <__chrono/statically_widen.h> # include <__chrono/sys_info.h> # include <__chrono/system_clock.h> +# include <__chrono/tai_clock.h> # include <__chrono/time_point.h> # include <__chrono/utc_clock.h> # include <__chrono/weekday.h> @@ -232,9 +233,11 @@ _LIBCPP_HIDE_FROM_ABI __time_zone __convert_to_time_zone([[maybe_unused]] const if constexpr (same_as<_Tp, chrono::sys_info>) return {__value.abbrev, __value.offset}; # if _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM + else if constexpr (__is_time_point<_Tp> && requires { requires same_as; }) + return {"TAI", chrono::seconds{0}}; else if constexpr (__is_specialization_v<_Tp, chrono::zoned_time>) return __formatter::__convert_to_time_zone(__value.get_info()); -# endif +# endif // _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM else # endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB return {"UTC", chrono::seconds{0}}; @@ -734,6 +737,17 @@ struct _LIBCPP_TEMPLATE_VIS formatter, _CharT> : pub } }; +template +struct _LIBCPP_TEMPLATE_VIS formatter, _CharT> : public __formatter_chrono<_CharT> { +public: + using _Base _LIBCPP_NODEBUG = __formatter_chrono<_CharT>; + + template + _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) { + return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__clock); + } +}; + # endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB # endif // _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM diff --git a/libcxx/include/__chrono/ostream.h b/libcxx/include/__chrono/ostream.h index ed9ad8e346ba9..b8cd6a4680662 100644 --- a/libcxx/include/__chrono/ostream.h +++ b/libcxx/include/__chrono/ostream.h @@ -26,6 +26,7 @@ # include <__chrono/statically_widen.h> # include <__chrono/sys_info.h> # include <__chrono/system_clock.h> +# include <__chrono/tai_clock.h> # include <__chrono/utc_clock.h> # include <__chrono/weekday.h> # include <__chrono/year.h> @@ -71,6 +72,12 @@ operator<<(basic_ostream<_CharT, _Traits>& __os, const utc_time<_Duration>& __tp return __os << std::format(__os.getloc(), _LIBCPP_STATICALLY_WIDEN(_CharT, "{:L%F %T}"), __tp); } +template +_LIBCPP_HIDE_FROM_ABI basic_ostream<_CharT, _Traits>& +operator<<(basic_ostream<_CharT, _Traits>& __os, const tai_time<_Duration>& __tp) { + return __os << std::format(__os.getloc(), _LIBCPP_STATICALLY_WIDEN(_CharT, "{:L%F %T}"), __tp); +} + # endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB # endif // _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM diff --git a/libcxx/include/__chrono/tai_clock.h b/libcxx/include/__chrono/tai_clock.h new file mode 100644 index 0000000000000..14c8b70a94af5 --- /dev/null +++ b/libcxx/include/__chrono/tai_clock.h @@ -0,0 +1,108 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP___CHRONO_TAI_CLOCK_H +#define _LIBCPP___CHRONO_TAI_CLOCK_H + +#include +// Enable the contents of the header only when libc++ was built with experimental features enabled. +#if _LIBCPP_HAS_EXPERIMENTAL_TZDB + +# include <__assert> +# include <__chrono/duration.h> +# include <__chrono/time_point.h> +# include <__chrono/utc_clock.h> +# include <__config> +# include <__type_traits/common_type.h> + +# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +# endif + +_LIBCPP_PUSH_MACROS +# include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +# if _LIBCPP_STD_VER >= 20 && _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM && _LIBCPP_HAS_LOCALIZATION + +namespace chrono { + +class tai_clock; + +template +using tai_time = time_point; +using tai_seconds = tai_time; + +// [time.clock.tai.overview]/1 +// The clock tai_clock measures seconds since 1958-01-01 00:00:00 and is +// offset 10s ahead of UTC at this date. That is, 1958-01-01 00:00:00 TAI is +// equivalent to 1957-12-31 23:59:50 UTC. Leap seconds are not inserted into +// TAI. Therefore every time a leap second is inserted into UTC, UTC shifts +// another second with respect to TAI. For example by 2000-01-01 there had +// been 22 positive and 0 negative leap seconds inserted so 2000-01-01 +// 00:00:00 UTC is equivalent to 2000-01-01 00:00:32 TAI (22s plus the +// initial 10s offset). +// +// Note this does not specify what the UTC offset before 1958-01-01 00:00:00 +// TAI is, nor does it follow the "real" TAI clock between 1958-01-01 and the +// start of the UTC epoch. So while the member functions are fully specified in +// the standard, they do not technically follow the "real-world" TAI clock with +// 100% accuracy. +// +// https://koka-lang.github.io/koka/doc/std_time_utc.html contains more +// information and references. +class tai_clock { +public: + using rep = utc_clock::rep; + using period = utc_clock::period; + using duration = chrono::duration; + using time_point = chrono::time_point; + static constexpr bool is_steady = false; // The utc_clock is not steady. + + // The static difference between UTC and TAI time. + static constexpr chrono::seconds __offset{378691210}; + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI static time_point now() { return from_utc(utc_clock::now()); } + + template + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI static utc_time> + to_utc(const tai_time<_Duration>& __time) noexcept { + using _Rp = common_type_t<_Duration, seconds>; + _Duration __time_since_epoch = __time.time_since_epoch(); + _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(__time_since_epoch >= utc_time<_Rp>::min().time_since_epoch() + __offset, + "the TAI to UTC conversion would underflow"); + + return utc_time<_Rp>{__time_since_epoch - __offset}; + } + + template + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI static tai_time> + from_utc(const utc_time<_Duration>& __time) noexcept { + using _Rp = common_type_t<_Duration, seconds>; + _Duration __time_since_epoch = __time.time_since_epoch(); + _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(__time_since_epoch <= utc_time<_Rp>::max().time_since_epoch() - __offset, + "the UTC to TAI conversion would overflow"); + + return tai_time<_Rp>{__time_since_epoch + __offset}; + } +}; + +} // namespace chrono + +# endif // _LIBCPP_STD_VER >= 20 && _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM && + // _LIBCPP_HAS_LOCALIZATION + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP_HAS_EXPERIMENTAL_TZDB + +#endif // _LIBCPP___CHRONO_TAI_CLOCK_H diff --git a/libcxx/include/chrono b/libcxx/include/chrono index 10695eea649fb..bd4c98600440c 100644 --- a/libcxx/include/chrono +++ b/libcxx/include/chrono @@ -335,6 +335,34 @@ struct leap_second_info { // C++20 template // C++20 leap_second_info get_leap_second_info(const utc_time& ut); + +// [time.clock.tai], class tai_clock +class tai_clock { // C++20 +public: + using rep = a signed arithmetic type; + using period = ratio; + using duration = chrono::duration; + using time_point = chrono::time_point; + static constexpr bool is_steady = unspecified; + + static time_point now(); + + template + static utc_time> + to_utc(const tai_time& t); + template + static tai_time> + from_utc(const utc_time& t); +}; + +template +using tai_time = time_point; // C++20 +using tai_seconds = tai_time; // C++20 + +template // C++20 + basic_ostream& + operator<<(basic_ostream& os, const tai_time& t); + class file_clock // C++20 { public: @@ -898,6 +926,8 @@ namespace std { struct formatter, charT>; // C++20 template struct formatter, charT>; // C++20 + template + struct formatter, charT>; // C++20 template struct formatter, charT>; // C++20 template @@ -1014,6 +1044,7 @@ constexpr chrono::year operator ""y(unsigned lo # if _LIBCPP_HAS_TIME_ZONE_DATABASE && _LIBCPP_HAS_FILESYSTEM && _LIBCPP_HAS_LOCALIZATION # include <__chrono/leap_second.h> +# include <__chrono/tai_clock.h> # include <__chrono/time_zone.h> # include <__chrono/time_zone_link.h> # include <__chrono/tzdb.h> diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap index 4bae02137b37b..fd39c946b992a 100644 --- a/libcxx/include/module.modulemap +++ b/libcxx/include/module.modulemap @@ -967,6 +967,10 @@ module std [system] { header "__chrono/system_clock.h" export std.chrono.time_point } + module tai_clock { + header "__chrono/tai_clock.h" + export std.chrono.time_point + } module time_point { header "__chrono/time_point.h" } module time_zone_link { header "__chrono/time_zone_link.h" } module time_zone { header "__chrono/time_zone.h" } diff --git a/libcxx/modules/std/chrono.inc b/libcxx/modules/std/chrono.inc index 98f14f716c207..43e8da36e9044 100644 --- a/libcxx/modules/std/chrono.inc +++ b/libcxx/modules/std/chrono.inc @@ -97,13 +97,13 @@ export namespace std { using std::chrono::get_leap_second_info; -# if 0 // [time.clock.tai], class tai_clock using std::chrono::tai_clock; using std::chrono::tai_seconds; using std::chrono::tai_time; +# if 0 // [time.clock.gps], class gps_clock using std::chrono::gps_clock; diff --git a/libcxx/test/libcxx/diagnostics/chrono.nodiscard.verify.cpp b/libcxx/test/libcxx/diagnostics/chrono.nodiscard.verify.cpp index 644c5b598c018..bb40e0cfc4e1b 100644 --- a/libcxx/test/libcxx/diagnostics/chrono.nodiscard.verify.cpp +++ b/libcxx/test/libcxx/diagnostics/chrono.nodiscard.verify.cpp @@ -102,4 +102,15 @@ void test(std::chrono::time_zone tz, std::chrono::time_zone_link link, std::chro zt.get_sys_time(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} zt.get_info(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} } + + { // [time.clock.tai] + // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}} + std::chrono::tai_clock::now(); + + // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}} + std::chrono::tai_clock::to_utc(std::chrono::tai_seconds{}); + + // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}} + std::chrono::tai_clock::from_utc(std::chrono::utc_seconds{}); + } } diff --git a/libcxx/test/libcxx/time/time.clock/time.clock.tai/time.clock.tai.members/assert.from_utc.pass.cpp b/libcxx/test/libcxx/time/time.clock/time.clock.tai/time.clock.tai.members/assert.from_utc.pass.cpp new file mode 100644 index 0000000000000..f04595ce34ceb --- /dev/null +++ b/libcxx/test/libcxx/time/time.clock/time.clock.tai/time.clock.tai.members/assert.from_utc.pass.cpp @@ -0,0 +1,73 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++20 +// UNSUPPORTED: no-filesystem, no-localization, no-tzdb + +// XFAIL: libcpp-has-no-experimental-tzdb +// XFAIL: availability-tzdb-missing + +// REQUIRES: libcpp-hardening-mode={{extensive|debug}} +// REQUIRES: has-unix-headers +// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing + +// +// +// class tai_clock; + +// static tai_time> +// from_utc(const utc_time<_Duration>& t) noexcept; + +#include + +#include "check_assertion.h" + +// The function is specified as +// tai_time>{t.time_since_epoch()} + 378691210s +// When t == t.max() there will be a signed integral overflow (other values too). +int main(int, char**) { + using namespace std::literals::chrono_literals; + constexpr std::chrono::seconds offset{378691210}; + + (void)std::chrono::tai_clock::from_utc(std::chrono::utc_time::max() - offset); + TEST_LIBCPP_ASSERT_FAILURE( + std::chrono::tai_clock::from_utc(std::chrono::utc_time::max() - offset + 1ns), + "the UTC to TAI conversion would overflow"); + + (void)std::chrono::tai_clock::from_utc(std::chrono::utc_time::max() - offset); + TEST_LIBCPP_ASSERT_FAILURE( + std::chrono::tai_clock::from_utc(std::chrono::utc_time::max() - offset + 1us), + "the UTC to TAI conversion would overflow"); + + (void)std::chrono::tai_clock::from_utc(std::chrono::utc_time::max() - offset); + TEST_LIBCPP_ASSERT_FAILURE( + std::chrono::tai_clock::from_utc(std::chrono::utc_time::max() - offset + 1ms), + "the UTC to TAI conversion would overflow"); + + (void)std::chrono::tai_clock::from_utc(std::chrono::utc_seconds::max() - offset); + TEST_LIBCPP_ASSERT_FAILURE(std::chrono::tai_clock::from_utc(std::chrono::utc_seconds::max() - offset + 1s), + "the UTC to TAI conversion would overflow"); + + // The conversion uses `common_type_t` so types "larger" + // than seconds are converted to seconds. Types "larger" than seconds are + // stored in "smaller" intergral and the overflow can never occur. + + // Validate the types can never overflow on all current (and future) supported platforms. + static_assert(std::chrono::utc_time::max() <= std::chrono::utc_seconds::max() - offset); + static_assert(std::chrono::utc_time::max() <= std::chrono::utc_seconds::max() - offset); + static_assert(std::chrono::utc_time::max() <= std::chrono::utc_seconds::max() - offset); + static_assert(std::chrono::utc_time::max() <= std::chrono::utc_seconds::max() - offset); + + // Validate the run-time conversion works. + (void)std::chrono::tai_clock::from_utc(std::chrono::utc_time::max()); + (void)std::chrono::tai_clock::from_utc(std::chrono::utc_time::max()); + (void)std::chrono::tai_clock::from_utc(std::chrono::utc_time::max()); + (void)std::chrono::tai_clock::from_utc(std::chrono::utc_time::max()); + + return 0; +} diff --git a/libcxx/test/libcxx/time/time.clock/time.clock.tai/time.clock.tai.members/assert.to_utc.pass.cpp b/libcxx/test/libcxx/time/time.clock/time.clock.tai/time.clock.tai.members/assert.to_utc.pass.cpp new file mode 100644 index 0000000000000..624c6c4bce4ca --- /dev/null +++ b/libcxx/test/libcxx/time/time.clock/time.clock.tai/time.clock.tai.members/assert.to_utc.pass.cpp @@ -0,0 +1,73 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++20 +// UNSUPPORTED: no-filesystem, no-localization, no-tzdb + +// XFAIL: libcpp-has-no-experimental-tzdb +// XFAIL: availability-tzdb-missing + +// REQUIRES: libcpp-hardening-mode={{extensive|debug}} +// REQUIRES: has-unix-headers +// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing + +// +// +// class tai_clock; + +// static utc_time> +// to_utc(const tai_time<_Duration>& t) noexcept; + +#include + +#include "check_assertion.h" + +// The function is specified as +// utc_time>{t.time_since_epoch()} - 378691210s +// When t == t.min() there will be a signed integral underlow (other values too). +int main(int, char**) { + using namespace std::literals::chrono_literals; + constexpr std::chrono::seconds offset{378691210}; + + (void)std::chrono::tai_clock::to_utc(std::chrono::tai_time::min() + offset); + TEST_LIBCPP_ASSERT_FAILURE( + std::chrono::tai_clock::to_utc(std::chrono::tai_time::min() + offset - 1ns), + "the TAI to UTC conversion would underflow"); + + (void)std::chrono::tai_clock::to_utc(std::chrono::tai_time::min() + offset); + TEST_LIBCPP_ASSERT_FAILURE( + std::chrono::tai_clock::to_utc(std::chrono::tai_time::min() + offset - 1us), + "the TAI to UTC conversion would underflow"); + + (void)std::chrono::tai_clock::to_utc(std::chrono::tai_time::min() + offset); + TEST_LIBCPP_ASSERT_FAILURE( + std::chrono::tai_clock::to_utc(std::chrono::tai_time::min() + offset - 1ms), + "the TAI to UTC conversion would underflow"); + + (void)std::chrono::tai_clock::to_utc(std::chrono::tai_seconds::min() + offset); + TEST_LIBCPP_ASSERT_FAILURE(std::chrono::tai_clock::to_utc(std::chrono::tai_seconds::min() + offset - 1s), + "the TAI to UTC conversion would underflow"); + + // The conversion uses `common_type_t` so types "larger" + // than seconds are converted to seconds. Types "larger" than seconds are + // stored in "smaller" intergral and the underflow can never occur. + + // Validate the types can never underflow on all current (and future) supported platforms. + static_assert(std::chrono::tai_time::min() >= std::chrono::tai_seconds::min() + offset); + static_assert(std::chrono::tai_time::min() >= std::chrono::tai_seconds::min() + offset); + static_assert(std::chrono::tai_time::min() >= std::chrono::tai_seconds::min() + offset); + static_assert(std::chrono::tai_time::min() >= std::chrono::tai_seconds::min() + offset); + + // Validate the run-time conversion works. + (void)std::chrono::tai_clock::to_utc(std::chrono::tai_time::min()); + (void)std::chrono::tai_clock::to_utc(std::chrono::tai_time::min()); + (void)std::chrono::tai_clock::to_utc(std::chrono::tai_time::min()); + (void)std::chrono::tai_clock::to_utc(std::chrono::tai_time::min()); + + return 0; +} diff --git a/libcxx/test/std/time/time.clock/time.clock.tai/tai_time.ostream.pass.cpp b/libcxx/test/std/time/time.clock/time.clock.tai/tai_time.ostream.pass.cpp new file mode 100644 index 0000000000000..e4f953118fd43 --- /dev/null +++ b/libcxx/test/std/time/time.clock/time.clock.tai/tai_time.ostream.pass.cpp @@ -0,0 +1,164 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++20 +// UNSUPPORTED: no-filesystem, no-localization, no-tzdb +// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME + +// TODO FMT This test should not require std::to_chars(floating-point) +// XFAIL: availability-fp_to_chars-missing + +// XFAIL: libcpp-has-no-experimental-tzdb +// XFAIL: availability-tzdb-missing + +// REQUIRES: locale.fr_FR.UTF-8 +// REQUIRES: locale.ja_JP.UTF-8 + +// + +// class tai_clock; + +// template +// basic_ostream& +// operator<<(basic_ostream& os, const tai_time& tp); + +#include +#include +#include +#include + +#include "make_string.h" +#include "platform_support.h" // locale name macros +#include "test_macros.h" + +#define SV(S) MAKE_STRING_VIEW(CharT, S) + +template +static std::basic_string stream_c_locale(std::chrono::tai_time time_point) { + std::basic_stringstream sstr; + sstr << std::fixed << time_point; + return sstr.str(); +} + +template +static std::basic_string stream_fr_FR_locale(std::chrono::tai_time time_point) { + std::basic_stringstream sstr; + const std::locale locale(LOCALE_fr_FR_UTF_8); + sstr.imbue(locale); + sstr << std::fixed << time_point; + return sstr.str(); +} + +template +static std::basic_string stream_ja_JP_locale(std::chrono::tai_time time_point) { + std::basic_stringstream sstr; + const std::locale locale(LOCALE_ja_JP_UTF_8); + sstr.imbue(locale); + sstr << std::fixed << time_point; + return sstr.str(); +} + +template +static void test_c() { + using namespace std::literals::chrono_literals; + namespace cr = std::chrono; + + assert(stream_c_locale(cr::tai_time{946'688'523'123'456'789ns}) == + SV("1988-01-01 01:02:03.123456789")); + assert(stream_c_locale(cr::tai_time{946'688'523'123'456us}) == + SV("1988-01-01 01:02:03.123456")); + + assert(stream_c_locale(cr::tai_time{946'684'822'123ms}) == SV("1988-01-01 00:00:22.123")); + assert(stream_c_locale(cr::tai_seconds{1'234'567'890s}) == SV("1997-02-13 23:31:30")); + assert(stream_c_locale(cr::tai_time{20'576'131min}) == SV("1997-02-13 23:31:00")); + assert(stream_c_locale(cr::tai_time{342'935h}) == SV("1997-02-13 23:00:00")); + + assert(stream_c_locale(cr::tai_time>>{ + cr::duration>{60}}) == SV("1958-01-01 00:02:00")); + assert(stream_c_locale(cr::tai_time>>{ + cr::duration>{3600}}) == SV("1958-01-01 00:30:00.0")); + assert(stream_c_locale(cr::tai_time>>{ + cr::duration>{3600}}) == SV("1958-01-01 00:15:00.00")); + assert(stream_c_locale(cr::tai_time>>{ + cr::duration>{36611}}) == SV("1958-01-01 01:01:01.1")); + assert(stream_c_locale(cr::tai_time>>{ + cr::duration>{12'345'678'9010}}) == SV("1997-02-13 23:31:30.10")); +} + +template +static void test_fr_FR() { + using namespace std::literals::chrono_literals; + namespace cr = std::chrono; + + assert(stream_fr_FR_locale(cr::tai_time{946'688'523'123'456'789ns}) == + SV("1988-01-01 01:02:03,123456789")); + assert(stream_fr_FR_locale(cr::tai_time{946'688'523'123'456us}) == + SV("1988-01-01 01:02:03,123456")); + + assert(stream_fr_FR_locale(cr::tai_time{946'684'822'123ms}) == + SV("1988-01-01 00:00:22,123")); + assert(stream_fr_FR_locale(cr::tai_seconds{1'234'567'890s}) == SV("1997-02-13 23:31:30")); + assert(stream_fr_FR_locale(cr::tai_time{20'576'131min}) == SV("1997-02-13 23:31:00")); + assert(stream_fr_FR_locale(cr::tai_time{342'935h}) == SV("1997-02-13 23:00:00")); + + assert(stream_fr_FR_locale(cr::tai_time>>{ + cr::duration>{60}}) == SV("1958-01-01 00:02:00")); + assert(stream_fr_FR_locale(cr::tai_time>>{ + cr::duration>{3600}}) == SV("1958-01-01 00:30:00,0")); + assert(stream_fr_FR_locale(cr::tai_time>>{ + cr::duration>{3600}}) == SV("1958-01-01 00:15:00,00")); + assert(stream_fr_FR_locale(cr::tai_time>>{ + cr::duration>{36611}}) == SV("1958-01-01 01:01:01,1")); + assert(stream_fr_FR_locale(cr::tai_time>>{ + cr::duration>{12'345'678'9010}}) == SV("1997-02-13 23:31:30,10")); +} + +template +static void test_ja_JP() { + using namespace std::literals::chrono_literals; + namespace cr = std::chrono; + + assert(stream_ja_JP_locale(cr::tai_time{946'688'523'123'456'789ns}) == + SV("1988-01-01 01:02:03.123456789")); + assert(stream_ja_JP_locale(cr::tai_time{946'688'523'123'456us}) == + SV("1988-01-01 01:02:03.123456")); + + assert(stream_ja_JP_locale(cr::tai_time{946'684'822'123ms}) == + SV("1988-01-01 00:00:22.123")); + assert(stream_ja_JP_locale(cr::tai_seconds{1'234'567'890s}) == SV("1997-02-13 23:31:30")); + assert(stream_ja_JP_locale(cr::tai_time{20'576'131min}) == SV("1997-02-13 23:31:00")); + assert(stream_ja_JP_locale(cr::tai_time{342'935h}) == SV("1997-02-13 23:00:00")); + + assert(stream_ja_JP_locale(cr::tai_time>>{ + cr::duration>{60}}) == SV("1958-01-01 00:02:00")); + assert(stream_ja_JP_locale(cr::tai_time>>{ + cr::duration>{3600}}) == SV("1958-01-01 00:30:00.0")); + assert(stream_ja_JP_locale(cr::tai_time>>{ + cr::duration>{3600}}) == SV("1958-01-01 00:15:00.00")); + assert(stream_ja_JP_locale(cr::tai_time>>{ + cr::duration>{36611}}) == SV("1958-01-01 01:01:01.1")); + assert(stream_ja_JP_locale(cr::tai_time>>{ + cr::duration>{12'345'678'9010}}) == SV("1997-02-13 23:31:30.10")); +} + +template +static void test() { + test_c(); + test_fr_FR(); + test_ja_JP(); +} + +int main(int, char**) { + test(); + +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#endif + + return 0; +} diff --git a/libcxx/test/std/time/time.clock/time.clock.tai/time.clock.tai.members/from_utc.pass.cpp b/libcxx/test/std/time/time.clock/time.clock.tai/time.clock.tai.members/from_utc.pass.cpp new file mode 100644 index 0000000000000..0d2d7bec16301 --- /dev/null +++ b/libcxx/test/std/time/time.clock/time.clock.tai/time.clock.tai.members/from_utc.pass.cpp @@ -0,0 +1,161 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++20 +// UNSUPPORTED: no-filesystem, no-localization, no-tzdb + +// XFAIL: libcpp-has-no-experimental-tzdb +// XFAIL: availability-tzdb-missing + +// +// +// class tai_clock; + +// static tai_time> +// from_utc(const utc<_Duration>& __time) noexcept; + +#include +#include +#include + +#include "test_macros.h" +#include "assert_macros.h" +#include "concat_macros.h" + +static void test_known_values() { + namespace cr = std::chrono; + using namespace std::literals::chrono_literals; + constexpr auto unix_to_tai_epoch_offset = cr::sys_days{cr::January / 1 / 1970} - cr::sys_days{cr::January / 1 / 1958}; + + // [time.clock.tai.overview]/1 + // ... 1958-01-01 00:00:00 TAI is equivalent to 1957-12-31 23:59:50 UTC + // ... 2000-01-01 00:00:00 UTC is equivalent to 2000-01-01 00:00:32 TAI + + assert(cr::tai_clock::from_utc(cr::utc_clock::from_sys(cr::sys_days{cr::January / 1 / 1958} - 10s)) == + cr::tai_seconds{0s}); + + assert(cr::tai_clock::from_utc(cr::utc_clock::from_sys(cr::sys_days{cr::January / 1 / 2000})) == + cr::tai_seconds{(cr::sys_days{cr::January / 1 / 2000} + unix_to_tai_epoch_offset).time_since_epoch()} + 32s); +} + +template +static void test_leap_seconds(std::chrono::utc_time utc, + std::chrono::tai_time expected, + std::source_location loc = std::source_location::current()) { + auto tai = std::chrono::tai_clock::from_utc(utc); + TEST_REQUIRE(tai == expected, + TEST_WRITE_CONCATENATED(loc, "\nExpected output ", expected, "\nActual output ", tai, '\n')); +} + +// Tests set if existing database entries at the time of writing. +static void test_transitions() { + using namespace std::literals::chrono_literals; + namespace cr = std::chrono; + + // "sys" is the time of the transition to the next leap second. + // "elapsed" is the number of leap seconds before the transition. + auto test_transition = [](cr::sys_days sys, cr::seconds elapsed) { + constexpr auto unix_to_tai_epoch_offset = + cr::sys_days{cr::January / 1 / 1970} - cr::sys_days{cr::January / 1 / 1958}; + cr::tai_seconds tai{sys.time_since_epoch() + unix_to_tai_epoch_offset + elapsed}; + + test_leap_seconds(cr::utc_clock::from_sys(sys - 1ns), tai - 1ns); + test_leap_seconds(cr::utc_clock::from_sys(sys), tai + 1s); + test_leap_seconds(cr::utc_clock::from_sys(sys) + 1ns, tai + 1s + 1ns); + }; + + // Transitions from the start of UTC. + test_transition(cr::sys_days{cr::July / 1 / 1972}, 10s); + test_transition(cr::sys_days{cr::January / 1 / 1973}, 11s); + test_transition(cr::sys_days{cr::January / 1 / 1974}, 12s); + test_transition(cr::sys_days{cr::January / 1 / 1975}, 13s); + test_transition(cr::sys_days{cr::January / 1 / 1976}, 14s); + test_transition(cr::sys_days{cr::January / 1 / 1977}, 15s); + test_transition(cr::sys_days{cr::January / 1 / 1978}, 16s); + test_transition(cr::sys_days{cr::January / 1 / 1979}, 17s); + test_transition(cr::sys_days{cr::January / 1 / 1980}, 18s); + test_transition(cr::sys_days{cr::July / 1 / 1981}, 19s); + test_transition(cr::sys_days{cr::July / 1 / 1982}, 20s); + test_transition(cr::sys_days{cr::July / 1 / 1983}, 21s); + test_transition(cr::sys_days{cr::July / 1 / 1985}, 22s); + test_transition(cr::sys_days{cr::January / 1 / 1988}, 23s); + test_transition(cr::sys_days{cr::January / 1 / 1990}, 24s); + test_transition(cr::sys_days{cr::January / 1 / 1991}, 25s); + test_transition(cr::sys_days{cr::July / 1 / 1992}, 26s); + test_transition(cr::sys_days{cr::July / 1 / 1993}, 27s); + test_transition(cr::sys_days{cr::July / 1 / 1994}, 28s); + test_transition(cr::sys_days{cr::January / 1 / 1996}, 29s); + test_transition(cr::sys_days{cr::July / 1 / 1997}, 30s); + test_transition(cr::sys_days{cr::January / 1 / 1999}, 31s); + test_transition(cr::sys_days{cr::January / 1 / 2006}, 32s); + test_transition(cr::sys_days{cr::January / 1 / 2009}, 33s); + test_transition(cr::sys_days{cr::July / 1 / 2012}, 34s); + test_transition(cr::sys_days{cr::July / 1 / 2015}, 35s); + test_transition(cr::sys_days{cr::January / 1 / 2017}, 36s); +} + +// Tests whether the return type is the expected type. +static void test_return_type() { + using namespace std::literals::chrono_literals; + namespace cr = std::chrono; + + { + [[maybe_unused]] std::same_as> decltype(auto) _ = + cr::tai_clock::from_utc(cr::utc_time{0ns}); + } + { + [[maybe_unused]] std::same_as> decltype(auto) _ = + cr::tai_clock::from_utc(cr::utc_time{0us}); + } + { + [[maybe_unused]] std::same_as> decltype(auto) _ = + cr::tai_clock::from_utc(cr::utc_time{0ms}); + } + + { + [[maybe_unused]] std::same_as> decltype(auto) _ = + cr::tai_clock::from_utc(cr::utc_time{cr::seconds{0}}); + } + + { + [[maybe_unused]] std::same_as> decltype(auto) _ = + cr::tai_clock::from_utc(cr::utc_time{cr::minutes{0}}); + } + { + [[maybe_unused]] std::same_as> decltype(auto) _ = + cr::tai_clock::from_utc(cr::utc_time{cr::hours{0}}); + } + { + [[maybe_unused]] std::same_as> decltype(auto) _ = + cr::tai_clock::from_utc(cr::utc_time{cr::days{0}}); + } + { + [[maybe_unused]] std::same_as> decltype(auto) _ = + cr::tai_clock::from_utc(cr::utc_time{cr::weeks{0}}); + } + { + [[maybe_unused]] std::same_as> decltype(auto) _ = + cr::tai_clock::from_utc(cr::utc_time{cr::months{0}}); + } + { + [[maybe_unused]] std::same_as> decltype(auto) _ = + cr::tai_clock::from_utc(cr::utc_time{cr::years{0}}); + } +} + +int main(int, const char**) { + using namespace std::literals::chrono_literals; + std::chrono::utc_seconds time = std::chrono::utc_seconds{0s}; + static_assert(noexcept(std::chrono::tai_clock::from_utc(time))); + + test_known_values(); + test_transitions(); + test_return_type(); + + return 0; +} diff --git a/libcxx/test/std/time/time.clock/time.clock.tai/time.clock.tai.members/now.pass.cpp b/libcxx/test/std/time/time.clock/time.clock.tai/time.clock.tai.members/now.pass.cpp new file mode 100644 index 0000000000000..2baef34277023 --- /dev/null +++ b/libcxx/test/std/time/time.clock/time.clock.tai/time.clock.tai.members/now.pass.cpp @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++20 +// UNSUPPORTED: no-filesystem, no-localization, no-tzdb + +// XFAIL: libcpp-has-no-experimental-tzdb +// XFAIL: availability-tzdb-missing + +// +// +// class tai_clock; + +// static time_point now(); + +#include +#include +#include + +int main(int, const char**) { + using clock = std::chrono::tai_clock; + std::same_as decltype(auto) t = clock::now(); + + assert(t >= clock::time_point::min()); + assert(t <= clock::time_point::max()); + + return 0; +} diff --git a/libcxx/test/std/time/time.clock/time.clock.tai/time.clock.tai.members/to_utc.pass.cpp b/libcxx/test/std/time/time.clock/time.clock.tai/time.clock.tai.members/to_utc.pass.cpp new file mode 100644 index 0000000000000..38b50bf275871 --- /dev/null +++ b/libcxx/test/std/time/time.clock/time.clock.tai/time.clock.tai.members/to_utc.pass.cpp @@ -0,0 +1,163 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++20 +// UNSUPPORTED: no-filesystem, no-localization, no-tzdb + +// XFAIL: libcpp-has-no-experimental-tzdb +// XFAIL: availability-tzdb-missing + +// +// +// class tai_clock; + +// static utc_time> +// to_utc(const tai_time<_Duration>& __time) noexcept; + +#include +#include +#include + +#include "test_macros.h" +#include "assert_macros.h" +#include "concat_macros.h" + +static void test_known_values() { + namespace cr = std::chrono; + using namespace std::literals::chrono_literals; + constexpr auto unix_to_tai_epoch_offset = cr::sys_days{cr::January / 1 / 1970} - cr::sys_days{cr::January / 1 / 1958}; + + // [time.clock.tai.overview]/1 + // ... 1958-01-01 00:00:00 TAI is equivalent to 1957-12-31 23:59:50 UTC + // ... 2000-01-01 00:00:00 UTC is equivalent to 2000-01-01 00:00:32 TAI + + assert(cr::tai_clock::to_utc(cr::tai_seconds{0s}) == + cr::utc_clock::from_sys(cr::sys_days{cr::January / 1 / 1958} - 10s)); + + assert(cr::tai_clock::to_utc( + cr::tai_seconds{(cr::sys_days{cr::January / 1 / 2000} + unix_to_tai_epoch_offset).time_since_epoch()} + + 32s) == cr::utc_clock::from_sys(cr::sys_days{cr::January / 1 / 2000})); +} + +template +static void test_leap_seconds(std::chrono::tai_time tai, + std::chrono::utc_time expected, + std::source_location loc = std::source_location::current()) { + auto utc = std::chrono::tai_clock::to_utc(tai); + TEST_REQUIRE(utc == expected, + TEST_WRITE_CONCATENATED(loc, "\nExpected output ", expected, "\nActual output ", utc, '\n')); +} + +// Tests set if existing database entries at the time of writing. +static void test_transitions() { + using namespace std::literals::chrono_literals; + namespace cr = std::chrono; + + // "sys" is the time of the transition to the next leap second. + // "elapsed" is the number of leap seconds before the transition. + auto test_transition = [](cr::sys_days sys, cr::seconds elapsed) { + constexpr auto unix_to_tai_epoch_offset = + cr::sys_days{cr::January / 1 / 1970} - cr::sys_days{cr::January / 1 / 1958}; + cr::tai_seconds tai{sys.time_since_epoch() + unix_to_tai_epoch_offset + elapsed}; + + test_leap_seconds(tai - 1ns, cr::utc_clock::from_sys(sys - 1ns)); + test_leap_seconds(tai + 1s, cr::utc_clock::from_sys(sys)); + test_leap_seconds(tai + 1s + 1ns, cr::utc_clock::from_sys(sys + 1ns)); + }; + + // Transitions from the start of UTC. + test_transition(cr::sys_days{cr::July / 1 / 1972}, 10s); + test_transition(cr::sys_days{cr::January / 1 / 1973}, 11s); + test_transition(cr::sys_days{cr::January / 1 / 1974}, 12s); + test_transition(cr::sys_days{cr::January / 1 / 1975}, 13s); + test_transition(cr::sys_days{cr::January / 1 / 1976}, 14s); + test_transition(cr::sys_days{cr::January / 1 / 1977}, 15s); + test_transition(cr::sys_days{cr::January / 1 / 1978}, 16s); + test_transition(cr::sys_days{cr::January / 1 / 1979}, 17s); + test_transition(cr::sys_days{cr::January / 1 / 1980}, 18s); + test_transition(cr::sys_days{cr::July / 1 / 1981}, 19s); + test_transition(cr::sys_days{cr::July / 1 / 1982}, 20s); + test_transition(cr::sys_days{cr::July / 1 / 1983}, 21s); + test_transition(cr::sys_days{cr::July / 1 / 1985}, 22s); + test_transition(cr::sys_days{cr::January / 1 / 1988}, 23s); + test_transition(cr::sys_days{cr::January / 1 / 1990}, 24s); + test_transition(cr::sys_days{cr::January / 1 / 1991}, 25s); + test_transition(cr::sys_days{cr::July / 1 / 1992}, 26s); + test_transition(cr::sys_days{cr::July / 1 / 1993}, 27s); + test_transition(cr::sys_days{cr::July / 1 / 1994}, 28s); + test_transition(cr::sys_days{cr::January / 1 / 1996}, 29s); + test_transition(cr::sys_days{cr::July / 1 / 1997}, 30s); + test_transition(cr::sys_days{cr::January / 1 / 1999}, 31s); + test_transition(cr::sys_days{cr::January / 1 / 2006}, 32s); + test_transition(cr::sys_days{cr::January / 1 / 2009}, 33s); + test_transition(cr::sys_days{cr::July / 1 / 2012}, 34s); + test_transition(cr::sys_days{cr::July / 1 / 2015}, 35s); + test_transition(cr::sys_days{cr::January / 1 / 2017}, 36s); +} + +// Tests whether the return type is the expected type. +static void test_return_type() { + using namespace std::literals::chrono_literals; + namespace cr = std::chrono; + + { + [[maybe_unused]] std::same_as> decltype(auto) _ = + cr::tai_clock::to_utc(cr::tai_time{0ns}); + } + { + [[maybe_unused]] std::same_as> decltype(auto) _ = + cr::tai_clock::to_utc(cr::tai_time{0us}); + } + { + [[maybe_unused]] std::same_as> decltype(auto) _ = + cr::tai_clock::to_utc(cr::tai_time{0ms}); + } + + { + [[maybe_unused]] std::same_as> decltype(auto) _ = + cr::tai_clock::to_utc(cr::tai_time{cr::seconds{0}}); + } + + { + [[maybe_unused]] std::same_as> decltype(auto) _ = + cr::tai_clock::to_utc(cr::tai_time{cr::minutes{0}}); + } + { + [[maybe_unused]] std::same_as> decltype(auto) _ = + cr::tai_clock::to_utc(cr::tai_time{cr::hours{0}}); + } + { + [[maybe_unused]] std::same_as> decltype(auto) _ = + cr::tai_clock::to_utc(cr::tai_time{cr::days{0}}); + } + { + [[maybe_unused]] std::same_as> decltype(auto) _ = + cr::tai_clock::to_utc(cr::tai_time{cr::weeks{0}}); + } + { + [[maybe_unused]] std::same_as> decltype(auto) _ = + cr::tai_clock::to_utc(cr::tai_time{cr::months{0}}); + } + { + [[maybe_unused]] std::same_as> decltype(auto) _ = + cr::tai_clock::to_utc(cr::tai_time{cr::years{0}}); + } +} + +int main(int, const char**) { + using namespace std::literals::chrono_literals; + + std::chrono::tai_seconds time = std::chrono::tai_seconds{0s}; + static_assert(noexcept(std::chrono::tai_clock::to_utc(time))); + + test_known_values(); + test_transitions(); + test_return_type(); + + return 0; +} diff --git a/libcxx/test/std/time/time.clock/time.clock.tai/types.compile.pass.cpp b/libcxx/test/std/time/time.clock/time.clock.tai/types.compile.pass.cpp new file mode 100644 index 0000000000000..ddb057333f49a --- /dev/null +++ b/libcxx/test/std/time/time.clock/time.clock.tai/types.compile.pass.cpp @@ -0,0 +1,60 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++20 +// UNSUPPORTED: no-filesystem, no-localization, no-tzdb + +// XFAIL: libcpp-has-no-experimental-tzdb +// XFAIL: availability-tzdb-missing + +// + +// class tai_clock { +// public: +// using rep = a signed arithmetic type; +// using period = ratio; +// using duration = chrono::duration; +// using time_point = chrono::time_point; +// static constexpr bool is_steady = unspecified; +// +// ... +// }; +// +// template +// using tai_time = time_point; +// using tai_seconds = tai_time; + +#include +#include +#include + +#include "test_macros.h" + +// class tai_clock +using rep = std::chrono::tai_clock::rep; +using period = std::chrono::tai_clock::period; +using duration = std::chrono::tai_clock::duration; +using time_point = std::chrono::tai_clock::time_point; +constexpr bool is_steady = std::chrono::tai_clock::is_steady; + +// Tests the values. part of them are implementation defined. +LIBCPP_STATIC_ASSERT(std::same_as); +static_assert(std::is_arithmetic_v); +static_assert(std::is_signed_v); + +LIBCPP_STATIC_ASSERT(std::same_as); +static_assert(std::same_as>); + +static_assert(std::same_as>); +static_assert(std::same_as>); +LIBCPP_STATIC_ASSERT(is_steady == false); + +// typedefs +static_assert(std::same_as, std::chrono::time_point>); +static_assert(std::same_as, std::chrono::time_point>); +static_assert(std::same_as>); diff --git a/libcxx/test/std/time/time.syn/formatter.tai_time.pass.cpp b/libcxx/test/std/time/time.syn/formatter.tai_time.pass.cpp new file mode 100644 index 0000000000000..7ca088cc6e8f4 --- /dev/null +++ b/libcxx/test/std/time/time.syn/formatter.tai_time.pass.cpp @@ -0,0 +1,998 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++20 +// UNSUPPORTED: no-filesystem, no-localization, no-tzdb +// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME + +// TODO FMT This test should not require std::to_chars(floating-point) +// XFAIL: availability-fp_to_chars-missing + +// XFAIL: libcpp-has-no-experimental-tzdb +// XFAIL: availability-tzdb-missing + +// REQUIRES: locale.fr_FR.UTF-8 +// REQUIRES: locale.ja_JP.UTF-8 + +// + +// template +// struct formatter, charT>; + +#include +#include + +#include +#include +#include +#include +#include + +#include "formatter_tests.h" +#include "make_string.h" +#include "platform_support.h" // locale name macros +#include "test_macros.h" + +template +static void test_no_chrono_specs() { + using namespace std::literals::chrono_literals; + namespace cr = std::chrono; + + std::locale::global(std::locale(LOCALE_fr_FR_UTF_8)); + + // Non localized output + + // [time.syn] + // using nanoseconds = duration; + // using microseconds = duration; + // using milliseconds = duration; + // using seconds = duration; + // using minutes = duration>; + // using hours = duration>; + check(SV("1413-08-04 22:06:56"), SV("{}"), cr::tai_seconds(-17'179'869'184s)); // Minimum value for 35 bits. + check(SV("1889-12-12 20:45:52"), SV("{}"), cr::tai_seconds(-2'147'483'648s)); + + check(SV("1957-12-31 00:00:00"), SV("{}"), cr::tai_seconds(-24h)); + check(SV("1957-12-31 06:00:00"), SV("{}"), cr::tai_seconds(-18h)); + check(SV("1957-12-31 12:00:00"), SV("{}"), cr::tai_seconds(-12h)); + check(SV("1957-12-31 18:00:00"), SV("{}"), cr::tai_seconds(-6h)); + check(SV("1957-12-31 23:59:59"), SV("{}"), cr::tai_seconds(-1s)); + + check(SV("1958-01-01 00:00:00"), SV("{}"), cr::tai_seconds(0s)); + check(SV("1988-01-01 00:00:00"), SV("{}"), cr::tai_seconds(946'684'800s)); + check(SV("1988-01-01 01:02:03"), SV("{}"), cr::tai_seconds(946'688'523s)); + + check(SV("2026-01-19 03:14:07"), SV("{}"), cr::tai_seconds(2'147'483'647s)); + check(SV("2502-05-30 01:53:03"), SV("{}"), cr::tai_seconds(17'179'869'183s)); // Maximum value for 35 bits. + + check(SV("1988-01-01 01:02:03.123"), SV("{}"), cr::tai_time(946'688'523'123ms)); + + std::locale::global(std::locale::classic()); +} + +template +static void test_valid_values_year() { + using namespace std::literals::chrono_literals; + namespace cr = std::chrono; + + constexpr std::basic_string_view fmt = + SV("{:%%C='%C'%t%%EC='%EC'%t%%y='%y'%t%%Oy='%Oy'%t%%Ey='%Ey'%t%%Y='%Y'%t%%EY='%EY'%n}"); + constexpr std::basic_string_view lfmt = + SV("{:L%%C='%C'%t%%EC='%EC'%t%%y='%y'%t%%Oy='%Oy'%t%%Ey='%Ey'%t%%Y='%Y'%t%%EY='%EY'%n}"); + + const std::locale loc(LOCALE_ja_JP_UTF_8); + std::locale::global(std::locale(LOCALE_fr_FR_UTF_8)); + + // Non localized output using C-locale + check(SV("%C='19'\t%EC='19'\t%y='58'\t%Oy='58'\t%Ey='58'\t%Y='1958'\t%EY='1958'\n"), + fmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(SV("%C='20'\t%EC='20'\t%y='09'\t%Oy='09'\t%Ey='09'\t%Y='2009'\t%EY='2009'\n"), + fmt, + cr::tai_seconds(1'613'259'090s)); // 23:31:30 TAI Friday, 13 February 2009 + + // Use the global locale (fr_FR) + check(SV("%C='19'\t%EC='19'\t%y='58'\t%Oy='58'\t%Ey='58'\t%Y='1958'\t%EY='1958'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(SV("%C='20'\t%EC='20'\t%y='09'\t%Oy='09'\t%Ey='09'\t%Y='2009'\t%EY='2009'\n"), + lfmt, + cr::tai_seconds(1'613'259'090s)); // 23:31:30 TAI Friday, 13 February 2009 + + // Use supplied locale (ja_JP). This locale has a different alternate. +#if defined(_WIN32) || defined(__APPLE__) || defined(_AIX) || defined(__FreeBSD__) + check(loc, + SV("%C='19'\t%EC='19'\t%y='58'\t%Oy='58'\t%Ey='58'\t%Y='1958'\t%EY='1958'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(loc, + SV("%C='20'\t%EC='20'\t%y='09'\t%Oy='09'\t%Ey='09'\t%Y='2009'\t%EY='2009'\n"), + lfmt, + cr::tai_seconds(1'613'259'090s)); // 23:31:30 TAI Friday, 13 February 2009 +#else // defined(_WIN32) || defined(__APPLE__) || defined(_AIX)||defined(__FreeBSD__) + check(loc, + SV("%C='19'\t%EC='昭和'\t%y='58'\t%Oy='五十八'\t%Ey='33'\t%Y='1958'\t%EY='昭和33年'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(loc, + SV("%C='20'\t%EC='平成'\t%y='09'\t%Oy='九'\t%Ey='21'\t%Y='2009'\t%EY='平成21年'\n"), + lfmt, + cr::tai_seconds(1'613'259'090s)); // 23:31:30 TAI Friday, 13 February 2009 +#endif // defined(_WIN32) || defined(__APPLE__) || defined(_AIX)||defined(__FreeBSD__) + + std::locale::global(std::locale::classic()); +} + +template +static void test_valid_values_month() { + using namespace std::literals::chrono_literals; + namespace cr = std::chrono; + + constexpr std::basic_string_view fmt = SV("{:%%b='%b'%t%%h='%h'%t%%B='%B'%t%%m='%m'%t%%Om='%Om'%n}"); + constexpr std::basic_string_view lfmt = SV("{:L%%b='%b'%t%%h='%h'%t%%B='%B'%t%%m='%m'%t%%Om='%Om'%n}"); + + const std::locale loc(LOCALE_ja_JP_UTF_8); + std::locale::global(std::locale(LOCALE_fr_FR_UTF_8)); + + // Non localized output using C-locale + check(SV("%b='Jan'\t%h='Jan'\t%B='January'\t%m='01'\t%Om='01'\n"), + fmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(SV("%b='May'\t%h='May'\t%B='May'\t%m='05'\t%Om='05'\n"), + fmt, + cr::tai_seconds(2'378'691'200s)); // 03:33:20 TAI on Wednesday, 18 May 2033 + + // Use the global locale (fr_FR) +#if defined(__APPLE__) + check(SV("%b='jan'\t%h='jan'\t%B='janvier'\t%m='01'\t%Om='01'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 +#else + check(SV("%b='janv.'\t%h='janv.'\t%B='janvier'\t%m='01'\t%Om='01'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 +#endif + + check(SV("%b='mai'\t%h='mai'\t%B='mai'\t%m='05'\t%Om='05'\n"), + lfmt, + cr::tai_seconds(2'378'691'200s)); // 03:33:20 TAI on Wednesday, 18 May 2033 + + // Use supplied locale (ja_JP). This locale has a different alternate. +#ifdef _WIN32 + check(loc, + SV("%b='1'\t%h='1'\t%B='1月'\t%m='01'\t%Om='01'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(loc, + SV("%b='5'\t%h='5'\t%B='5月'\t%m='05'\t%Om='05'\n"), + lfmt, + cr::tai_seconds(2'378'691'200s)); // 03:33:20 TAI Wednesday, 18 May 2033 +#elif defined(_AIX) // _WIN32 + check(loc, + SV("%b='1月'\t%h='1月'\t%B='1月'\t%m='01'\t%Om='01'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(loc, + SV("%b='5月'\t%h='5月'\t%B='5月'\t%m='05'\t%Om='05'\n"), + lfmt, + cr::tai_seconds(2'378'691'200s)); // 03:33:20 TAI Wednesday, 18 May 2033 +#elif defined(__APPLE__) // _WIN32 + check(loc, + SV("%b=' 1'\t%h=' 1'\t%B='1月'\t%m='01'\t%Om='01'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(loc, + SV("%b=' 5'\t%h=' 5'\t%B='5月'\t%m='05'\t%Om='05'\n"), + lfmt, + cr::tai_seconds(2'378'691'200s)); // 03:33:20 TAI Wednesday, 18 May 2033 +#elif defined(__FreeBSD__) // _WIN32 + check(loc, + SV("%b=' 1月'\t%h=' 1月'\t%B='1月'\t%m='01'\t%Om='01'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(loc, + SV("%b=' 5月'\t%h=' 5月'\t%B='5月'\t%m='05'\t%Om='05'\n"), + lfmt, + cr::tai_seconds(2'378'691'200s)); // 03:33:20 TAI Wednesday, 18 May 2033 +#else // _WIN32 + check(loc, + SV("%b=' 1月'\t%h=' 1月'\t%B='1月'\t%m='01'\t%Om='一'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(loc, + SV("%b=' 5月'\t%h=' 5月'\t%B='5月'\t%m='05'\t%Om='五'\n"), + lfmt, + cr::tai_seconds(2'378'691'200s)); // 03:33:20 TAI Wednesday, 18 May 2033 +#endif // _WIN32 + + std::locale::global(std::locale::classic()); +} + +template +static void test_valid_values_day() { + using namespace std::literals::chrono_literals; + namespace cr = std::chrono; + + constexpr std::basic_string_view fmt = SV("{:%%d='%d'%t%%Od='%Od'%t%%e='%e'%t%%Oe='%Oe'%n}"); + constexpr std::basic_string_view lfmt = SV("{:L%%d='%d'%t%%Od='%Od'%t%%e='%e'%t%%Oe='%Oe'%n}"); + + const std::locale loc(LOCALE_ja_JP_UTF_8); + std::locale::global(std::locale(LOCALE_fr_FR_UTF_8)); + + // Non localized output using C-locale + check(SV("%d='01'\t%Od='01'\t%e=' 1'\t%Oe=' 1'\n"), + fmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(SV("%d='13'\t%Od='13'\t%e='13'\t%Oe='13'\n"), + fmt, + cr::tai_seconds(1'613'259'090s)); // 23:31:30 TAI Friday, 13 February 2009 + + // Use the global locale (fr_FR) + check(SV("%d='01'\t%Od='01'\t%e=' 1'\t%Oe=' 1'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(SV("%d='13'\t%Od='13'\t%e='13'\t%Oe='13'\n"), + lfmt, + cr::tai_seconds(1'613'259'090s)); // 23:31:30 TAI Friday, 13 February 2009 + + // Use the global locale (fr_FR) + check(SV("%d='01'\t%Od='01'\t%e=' 1'\t%Oe=' 1'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + // Use supplied locale (ja_JP). This locale has a different alternate. +#if defined(_WIN32) || defined(__APPLE__) || defined(_AIX) || defined(__FreeBSD__) + check(loc, + SV("%d='01'\t%Od='01'\t%e=' 1'\t%Oe=' 1'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(loc, + SV("%d='13'\t%Od='13'\t%e='13'\t%Oe='13'\n"), + lfmt, + cr::tai_seconds(1'613'259'090s)); // 23:31:30 TAI Friday, 13 February 2009 + + // Use the global locale (fr_FR) + check(SV("%d='01'\t%Od='01'\t%e=' 1'\t%Oe=' 1'\n"), + lfmt, +#else // defined(_WIN32) || defined(__APPLE__) || defined(_AIX) || defined(__FreeBSD__) + check(loc, + SV("%d='01'\t%Od='一'\t%e=' 1'\t%Oe='一'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(loc, + SV("%d='13'\t%Od='十三'\t%e='13'\t%Oe='十三'\n"), + lfmt, + cr::tai_seconds(1'613'259'090s)); // 23:31:30 TAI Friday, 13 February 2009 + +#endif // defined(_WIN32) || defined(__APPLE__) || defined(_AIX) || defined(__FreeBSD__) + + std::locale::global(std::locale::classic()); +} + +template +static void test_valid_values_weekday() { + using namespace std::literals::chrono_literals; + namespace cr = std::chrono; + + constexpr std::basic_string_view fmt = + SV("{:%%a='%a'%t%%A='%A'%t%%u='%u'%t%%Ou='%Ou'%t%%w='%w'%t%%Ow='%Ow'%n}"); + constexpr std::basic_string_view lfmt = + SV("{:L%%a='%a'%t%%A='%A'%t%%u='%u'%t%%Ou='%Ou'%t%%w='%w'%t%%Ow='%Ow'%n}"); + + const std::locale loc(LOCALE_ja_JP_UTF_8); + std::locale::global(std::locale(LOCALE_fr_FR_UTF_8)); + + // Non localized output using C-locale + check(SV("%a='Wed'\t%A='Wednesday'\t%u='3'\t%Ou='3'\t%w='3'\t%Ow='3'\n"), + fmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(SV("%a='Sun'\t%A='Sunday'\t%u='7'\t%Ou='7'\t%w='0'\t%Ow='0'\n"), + fmt, + cr::tai_seconds(4'673'658'495s)); // 06:28:15 TAI on Sunday, 7 February 2106 + + // Use the global locale (fr_FR) +#if defined(__APPLE__) + check(SV("%a='mer'\t%A='Mercredi'\t%u='3'\t%Ou='3'\t%w='3'\t%Ow='3'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(SV("%a='Dim'\t%A='Dimanche'\t%u='7'\t%Ou='7'\t%w='0'\t%Ow='0'\n"), + lfmt, + cr::tai_seconds(4'673'658'495s)); // 06:28:15 TAI on Sunday, 7 February 2106 +#else + check(SV("%a='mer.'\t%A='mercredi'\t%u='3'\t%Ou='3'\t%w='3'\t%Ow='3'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(SV("%a='dim.'\t%A='dimanche'\t%u='7'\t%Ou='7'\t%w='0'\t%Ow='0'\n"), + lfmt, + cr::tai_seconds(4'673'658'495s)); // 06:28:15 TAI on Sunday, 7 February 2106 +#endif + + // Use supplied locale (ja_JP). + // This locale has a different alternate, but not on all platforms +#if defined(_WIN32) || defined(__APPLE__) || defined(_AIX) || defined(__FreeBSD__) + check(loc, + SV("%a='水'\t%A='水曜日'\t%u='3'\t%Ou='3'\t%w='3'\t%Ow='3'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(loc, + SV("%a='日'\t%A='日曜日'\t%u='7'\t%Ou='7'\t%w='0'\t%Ow='0'\n"), + lfmt, + cr::tai_seconds(4'673'658'495s)); // 06:28:15 TAI on Sunday, 7 February 2106 +#else // defined(_WIN32) || defined(__APPLE__) || defined(_AIX) || defined(__FreeBSD__) + check(loc, + SV("%a='水'\t%A='水曜日'\t%u='3'\t%Ou='三'\t%w='3'\t%Ow='三'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(loc, + SV("%a='日'\t%A='日曜日'\t%u='7'\t%Ou='七'\t%w='0'\t%Ow='〇'\n"), + lfmt, + cr::tai_seconds(4'673'658'495s)); // 06:28:15 TAI on Sunday, 7 February 2106 +#endif // defined(_WIN32) || defined(__APPLE__) || defined(_AIX) || defined(__FreeBSD__) + + std::locale::global(std::locale::classic()); +} + +template +static void test_valid_values_day_of_year() { + using namespace std::literals::chrono_literals; + namespace cr = std::chrono; + + constexpr std::basic_string_view fmt = SV("{:%%j='%j'%n}"); + constexpr std::basic_string_view lfmt = SV("{:L%%j='%j'%n}"); + + const std::locale loc(LOCALE_ja_JP_UTF_8); + std::locale::global(std::locale(LOCALE_fr_FR_UTF_8)); + + // Non localized output using C-locale + check(SV("%j='001'\n"), fmt, cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + check(SV("%j='138'\n"), fmt, cr::tai_seconds(2'378'691'200s)); // 03:33:20 TAI Wednesday, 18 May 2033 + + // Use the global locale (fr_FR) + check(SV("%j='001'\n"), lfmt, cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + check(SV("%j='138'\n"), lfmt, cr::tai_seconds(2'378'691'200s)); // 03:33:20 TAI Wednesday, 18 May 2033 + + // Use supplied locale (ja_JP). This locale has a different alternate. + check(loc, SV("%j='001'\n"), lfmt, cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + check(loc, SV("%j='138'\n"), lfmt, cr::tai_seconds(2'378'691'200s)); // 03:33:20 TAI Wednesday, 18 May 2033 + + std::locale::global(std::locale::classic()); +} + +template +static void test_valid_values_week() { + using namespace std::literals::chrono_literals; + namespace cr = std::chrono; + + constexpr std::basic_string_view fmt = SV("{:%%U='%U'%t%%OU='%OU'%t%%W='%W'%t%%OW='%OW'%n}"); + constexpr std::basic_string_view lfmt = SV("{:L%%U='%U'%t%%OU='%OU'%t%%W='%W'%t%%OW='%OW'%n}"); + + const std::locale loc(LOCALE_ja_JP_UTF_8); + std::locale::global(std::locale(LOCALE_fr_FR_UTF_8)); + + // Non localized output using C-locale + check(SV("%U='00'\t%OU='00'\t%W='00'\t%OW='00'\n"), + fmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(SV("%U='20'\t%OU='20'\t%W='20'\t%OW='20'\n"), + fmt, + cr::tai_seconds(2'378'691'200s)); // 03:33:20 TAI Wednesday, 18 May 2033 + + // Use the global locale (fr_FR) + check(SV("%U='00'\t%OU='00'\t%W='00'\t%OW='00'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(SV("%U='20'\t%OU='20'\t%W='20'\t%OW='20'\n"), + lfmt, + cr::tai_seconds(2'378'691'200s)); // 03:33:20 TAI Wednesday, 18 May 2033 + + // Use supplied locale (ja_JP). This locale has a different alternate. +#if defined(_WIN32) || defined(__APPLE__) || defined(_AIX) || defined(__FreeBSD__) + check(loc, + SV("%U='00'\t%OU='00'\t%W='00'\t%OW='00'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(loc, + SV("%U='20'\t%OU='20'\t%W='20'\t%OW='20'\n"), + lfmt, + cr::tai_seconds(2'378'691'200s)); // 03:33:20 TAI Wednesday, 18 May 2033 +#else // defined(_WIN32) || defined(__APPLE__) || defined(_AIX) || defined(__FreeBSD__) + check(loc, + SV("%U='00'\t%OU='〇'\t%W='00'\t%OW='〇'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(loc, + SV("%U='20'\t%OU='二十'\t%W='20'\t%OW='二十'\n"), + lfmt, + cr::tai_seconds(2'378'691'200s)); // 03:33:20 TAI Wednesday, 18 May 2033 +#endif // defined(_WIN32) || defined(__APPLE__) || defined(_AIX) || defined(__FreeBSD__) + std::locale::global(std::locale::classic()); +} + +template +static void test_valid_values_iso_8601_week() { + using namespace std::literals::chrono_literals; + namespace cr = std::chrono; + + constexpr std::basic_string_view fmt = SV("{:%%g='%g'%t%%G='%G'%t%%V='%V'%t%%OV='%OV'%n}"); + constexpr std::basic_string_view lfmt = SV("{:L%%g='%g'%t%%G='%G'%t%%V='%V'%t%%OV='%OV'%n}"); + + const std::locale loc(LOCALE_ja_JP_UTF_8); + std::locale::global(std::locale(LOCALE_fr_FR_UTF_8)); + + // Non localized output using C-locale + check(SV("%g='58'\t%G='1958'\t%V='01'\t%OV='01'\n"), + fmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(SV("%g='09'\t%G='2009'\t%V='07'\t%OV='07'\n"), + fmt, + cr::tai_seconds(1'613'259'090s)); // 23:31:30 TAI Friday, 13 February 2009 + + // Use the global locale (fr_FR) + check(SV("%g='58'\t%G='1958'\t%V='01'\t%OV='01'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(SV("%g='09'\t%G='2009'\t%V='07'\t%OV='07'\n"), + lfmt, + cr::tai_seconds(1'613'259'090s)); // 23:31:30 TAI Friday, 13 February 2009 + + // Use supplied locale (ja_JP). This locale has a different alternate. +#if defined(_WIN32) || defined(__APPLE__) || defined(_AIX) || defined(__FreeBSD__) + check(loc, + SV("%g='58'\t%G='1958'\t%V='01'\t%OV='01'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(loc, + SV("%g='09'\t%G='2009'\t%V='07'\t%OV='07'\n"), + lfmt, + cr::tai_seconds(1'613'259'090s)); // 23:31:30 TAI Friday, 13 February 2009 +#else // defined(_WIN32) || defined(__APPLE__) || defined(_AIX) || defined(__FreeBSD__) + check(loc, + SV("%g='58'\t%G='1958'\t%V='01'\t%OV='一'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(loc, + SV("%g='09'\t%G='2009'\t%V='07'\t%OV='七'\n"), + lfmt, + cr::tai_seconds(1'613'259'090s)); // 23:31:30 TAI Friday, 13 February 2009 +#endif // defined(_WIN32) || defined(__APPLE__) || defined(_AIX) || defined(__FreeBSD__) + + std::locale::global(std::locale::classic()); +} + +template +static void test_valid_values_date() { + using namespace std::literals::chrono_literals; + namespace cr = std::chrono; + + constexpr std::basic_string_view fmt = SV("{:%%D='%D'%t%%F='%F'%t%%x='%x'%t%%Ex='%Ex'%n}"); + constexpr std::basic_string_view lfmt = SV("{:L%%D='%D'%t%%F='%F'%t%%x='%x'%t%%Ex='%Ex'%n}"); + + const std::locale loc(LOCALE_ja_JP_UTF_8); + std::locale::global(std::locale(LOCALE_fr_FR_UTF_8)); + + // Non localized output using C-locale + check(SV("%D='01/01/58'\t%F='1958-01-01'\t%x='01/01/58'\t%Ex='01/01/58'\n"), + fmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(SV("%D='02/13/09'\t%F='2009-02-13'\t%x='02/13/09'\t%Ex='02/13/09'\n"), + fmt, + cr::tai_seconds(1'613'259'090s)); // 23:31:30 TAI Friday, 13 February 2009 + + // Use the global locale (fr_FR) +#if defined(__APPLE__) || defined(__FreeBSD__) + check(SV("%D='01/01/58'\t%F='1958-01-01'\t%x='01.01.1958'\t%Ex='01.01.1958'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(SV("%D='02/13/09'\t%F='2009-02-13'\t%x='13.02.2009'\t%Ex='13.02.2009'\n"), + lfmt, + cr::tai_seconds(1'613'259'090s)); // 23:31:30 TAI Friday, 13 February 2009 +#else + check(SV("%D='01/01/58'\t%F='1958-01-01'\t%x='01/01/1958'\t%Ex='01/01/1958'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(SV("%D='02/13/09'\t%F='2009-02-13'\t%x='13/02/2009'\t%Ex='13/02/2009'\n"), + lfmt, + cr::tai_seconds(1'613'259'090s)); // 23:31:30 TAI Friday, 13 February 2009 +#endif + + // Use supplied locale (ja_JP). This locale has a different alternate. +#if defined(_WIN32) || defined(__APPLE__) || defined(_AIX) || defined(__FreeBSD__) + check(loc, + SV("%D='01/01/58'\t%F='1958-01-01'\t%x='1958/01/01'\t%Ex='1958/01/01'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(loc, + SV("%D='02/13/09'\t%F='2009-02-13'\t%x='2009/02/13'\t%Ex='2009/02/13'\n"), + lfmt, + cr::tai_seconds(1'613'259'090s)); // 23:31:30 TAI Friday, 13 February 2009 +#else // defined(_WIN32) || defined(__APPLE__) || defined(_AIX) || defined(__FreeBSD__) + check(loc, + SV("%D='01/01/58'\t%F='1958-01-01'\t%x='1958年01月01日'\t%Ex='昭和33年01月01日'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(loc, + SV("%D='02/13/09'\t%F='2009-02-13'\t%x='2009年02月13日'\t%Ex='平成21年02月13日'\n"), + lfmt, + cr::tai_seconds(1'613'259'090s)); // 23:31:30 TAI Friday, 13 February 2009 +#endif // defined(_WIN32) || defined(__APPLE__) || defined(_AIX) || defined(__FreeBSD__) + + std::locale::global(std::locale::classic()); +} + +template +static void test_valid_values_time() { + using namespace std::literals::chrono_literals; + namespace cr = std::chrono; + + constexpr std::basic_string_view fmt = SV( + "{:" + "%%H='%H'%t" + "%%OH='%OH'%t" + "%%I='%I'%t" + "%%OI='%OI'%t" + "%%M='%M'%t" + "%%OM='%OM'%t" + "%%S='%S'%t" + "%%OS='%OS'%t" + "%%p='%p'%t" + "%%R='%R'%t" + "%%T='%T'%t" + "%%r='%r'%t" + "%%X='%X'%t" + "%%EX='%EX'%t" + "%n}"); + constexpr std::basic_string_view lfmt = SV( + "{:L" + "%%H='%H'%t" + "%%OH='%OH'%t" + "%%I='%I'%t" + "%%OI='%OI'%t" + "%%M='%M'%t" + "%%OM='%OM'%t" + "%%S='%S'%t" + "%%OS='%OS'%t" + "%%p='%p'%t" + "%%R='%R'%t" + "%%T='%T'%t" + "%%r='%r'%t" + "%%X='%X'%t" + "%%EX='%EX'%t" + "%n}"); + + const std::locale loc(LOCALE_ja_JP_UTF_8); + std::locale::global(std::locale(LOCALE_fr_FR_UTF_8)); + + // Non localized output using C-locale + check(SV("%H='00'\t" + "%OH='00'\t" + "%I='12'\t" + "%OI='12'\t" + "%M='00'\t" + "%OM='00'\t" + "%S='00'\t" + "%OS='00'\t" + "%p='AM'\t" + "%R='00:00'\t" + "%T='00:00:00'\t" + "%r='12:00:00 AM'\t" + "%X='00:00:00'\t" + "%EX='00:00:00'\t" + "\n"), + fmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(SV("%H='23'\t" + "%OH='23'\t" + "%I='11'\t" + "%OI='11'\t" + "%M='31'\t" + "%OM='31'\t" + "%S='30.123'\t" + "%OS='30.123'\t" + "%p='PM'\t" + "%R='23:31'\t" + "%T='23:31:30.123'\t" + "%r='11:31:30 PM'\t" + "%X='23:31:30'\t" + "%EX='23:31:30'\t" + "\n"), + fmt, + cr::tai_time(1'613'259'090'123ms)); // 23:31:30 TAI Friday, 13 February 2009 + // Use the global locale (fr_FR) + check(SV("%H='00'\t" + "%OH='00'\t" + "%I='12'\t" + "%OI='12'\t" + "%M='00'\t" + "%OM='00'\t" + "%S='00'\t" + "%OS='00'\t" +#if defined(_AIX) + "%p='AM'\t" +#else + "%p=''\t" +#endif + "%R='00:00'\t" + "%T='00:00:00'\t" +#ifdef _WIN32 + "%r='00:00:00'\t" +#elif defined(_AIX) + "%r='12:00:00 AM'\t" +#elif defined(__APPLE__) || defined(__FreeBSD__) + "%r=''\t" +#else + "%r='12:00:00 '\t" +#endif + "%X='00:00:00'\t" + "%EX='00:00:00'\t" + "\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(SV("%H='23'\t" + "%OH='23'\t" + "%I='11'\t" + "%OI='11'\t" + "%M='31'\t" + "%OM='31'\t" + "%S='30,123'\t" + "%OS='30,123'\t" +#if defined(_AIX) + "%p='PM'\t" +#else + "%p=''\t" +#endif + "%R='23:31'\t" + "%T='23:31:30,123'\t" +#ifdef _WIN32 + "%r='23:31:30'\t" +#elif defined(_AIX) + "%r='11:31:30 PM'\t" +#elif defined(__APPLE__) || defined(__FreeBSD__) + "%r=''\t" +#else + "%r='11:31:30 '\t" +#endif + "%X='23:31:30'\t" + "%EX='23:31:30'\t" + "\n"), + lfmt, + cr::tai_time(1'613'259'090'123ms)); // 23:31:30 TAI Friday, 13 February 2009 + + // Use supplied locale (ja_JP). This locale has a different alternate. +#if defined(__APPLE__) || defined(_AIX) || defined(_WIN32) || defined(__FreeBSD__) + check(loc, + SV("%H='00'\t" + "%OH='00'\t" + "%I='12'\t" + "%OI='12'\t" + "%M='00'\t" + "%OM='00'\t" + "%S='00'\t" + "%OS='00'\t" +# if defined(__APPLE__) + "%p='AM'\t" +# else + "%p='午前'\t" +# endif + "%R='00:00'\t" + "%T='00:00:00'\t" +# if defined(__APPLE__) || defined(__FreeBSD__) +# if defined(__APPLE__) + "%r='12:00:00 AM'\t" +# else + "%r='12:00:00 午前'\t" +# endif + "%X='00時00分00秒'\t" + "%EX='00時00分00秒'\t" +# elif defined(_WIN32) + "%r='0:00:00'\t" + "%X='0:00:00'\t" + "%EX='0:00:00'\t" +# else + "%r='午前12:00:00'\t" + "%X='00:00:00'\t" + "%EX='00:00:00'\t" +# endif + "\n"), + lfmt, + cr::hh_mm_ss(0s)); + + check(loc, + SV("%H='23'\t" + "%OH='23'\t" + "%I='11'\t" + "%OI='11'\t" + "%M='31'\t" + "%OM='31'\t" + "%S='30.123'\t" + "%OS='30.123'\t" +# if defined(__APPLE__) + "%p='PM'\t" +# else + "%p='午後'\t" +# endif + "%R='23:31'\t" + "%T='23:31:30.123'\t" +# if defined(__APPLE__) || defined(__FreeBSD__) +# if defined(__APPLE__) + "%r='11:31:30 PM'\t" +# else + "%r='11:31:30 午後'\t" +# endif + "%X='23時31分30秒'\t" + "%EX='23時31分30秒'\t" +# elif defined(_WIN32) + "%r='23:31:30'\t" + "%X='23:31:30'\t" + "%EX='23:31:30'\t" +# else + "%r='午後11:31:30'\t" + "%X='23:31:30'\t" + "%EX='23:31:30'\t" +# endif + "\n"), + lfmt, + cr::hh_mm_ss(23h + 31min + 30s + 123ms)); +#else // defined(__APPLE__) || defined(_AIX) || defined(_WIN32) || defined(__FreeBSD__) + check(loc, + SV("%H='00'\t" + "%OH='〇'\t" + "%I='12'\t" + "%OI='十二'\t" + "%M='00'\t" + "%OM='〇'\t" + "%S='00'\t" + "%OS='〇'\t" + "%p='午前'\t" + "%R='00:00'\t" + "%T='00:00:00'\t" + "%r='午前12時00分00秒'\t" + "%X='00時00分00秒'\t" + "%EX='00時00分00秒'\t" + "\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(loc, + SV("%H='23'\t" + "%OH='二十三'\t" + "%I='11'\t" + "%OI='十一'\t" + "%M='31'\t" + "%OM='三十一'\t" + "%S='30.123'\t" + "%OS='三十.123'\t" + "%p='午後'\t" + "%R='23:31'\t" + "%T='23:31:30.123'\t" + "%r='午後11時31分30秒'\t" + "%X='23時31分30秒'\t" + "%EX='23時31分30秒'\t" + "\n"), + lfmt, + cr::tai_time(1'613'259'090'123ms)); // 23:31:30 TAI Friday, 13 February 2009 +#endif // defined(__APPLE__) || defined(_AIX) || defined(_WIN32) || defined(__FreeBSD__) + + std::locale::global(std::locale::classic()); +} + +template +static void test_valid_values_date_time() { + using namespace std::literals::chrono_literals; + namespace cr = std::chrono; + + constexpr std::basic_string_view fmt = SV("{:%%c='%c'%t%%Ec='%Ec'%n}"); + constexpr std::basic_string_view lfmt = SV("{:L%%c='%c'%t%%Ec='%Ec'%n}"); + + const std::locale loc(LOCALE_ja_JP_UTF_8); + std::locale::global(std::locale(LOCALE_fr_FR_UTF_8)); + + // Non localized output using C-locale + check(SV("%c='Wed Jan 1 00:00:00 1958'\t%Ec='Wed Jan 1 00:00:00 1958'\n"), + fmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(SV("%c='Fri Feb 13 23:31:30 2009'\t%Ec='Fri Feb 13 23:31:30 2009'\n"), + fmt, + cr::tai_seconds(1'613'259'090s)); // 23:31:30 TAI Friday, 13 February 2009 + + // Use the global locale (fr_FR) + check( +// https://sourceware.org/bugzilla/show_bug.cgi?id=24054 +#if defined(__powerpc__) && defined(__linux__) + SV("%c='mer. 01 janv. 1958 00:00:00 TAI'\t%Ec='mer. 01 janv. 1958 00:00:00 TAI'\n"), +#elif defined(__GLIBC__) && __GLIBC__ <= 2 && __GLIBC_MINOR__ < 29 + SV("%c='mer. 01 janv. 1958 00:00:00 GMT'\t%Ec='mer. 01 janv. 1958 00:00:00 GMT'\n"), +#elif defined(_AIX) + SV("%c=' 1 janvier 1958 à 00:00:00 TAI'\t%Ec=' 1 janvier 1958 à 00:00:00 TAI'\n"), +#elif defined(__APPLE__) + SV("%c='Jeu 1 jan 00:00:00 1958'\t%Ec='Jeu 1 jan 00:00:00 1958'\n"), +#elif defined(_WIN32) + SV("%c='01/01/1958 00:00:00'\t%Ec='01/01/1958 00:00:00'\n"), +#elif defined(__FreeBSD__) + SV("%c='mer. 1 janv. 00:00:00 1958'\t%Ec='mer. 1 janv. 00:00:00 1958'\n"), +#else + SV("%c='mer. 01 janv. 1958 00:00:00'\t%Ec='mer. 01 janv. 1958 00:00:00'\n"), +#endif + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check( +// https://sourceware.org/bugzilla/show_bug.cgi?id=24054 +#if defined(__powerpc__) && defined(__linux__) + SV("%c='ven. 13 févr. 2009 23:31:30 TAI'\t%Ec='ven. 13 févr. 2009 23:31:30 TAI'\n"), +#elif defined(__GLIBC__) && __GLIBC__ <= 2 && __GLIBC_MINOR__ < 29 + SV("%c='ven. 13 févr. 2009 23:31:30 GMT'\t%Ec='ven. 13 févr. 2009 23:31:30 GMT'\n"), +#elif defined(_AIX) + SV("%c='13 février 2009 à 23:31:30 TAI'\t%Ec='13 février 2009 à 23:31:30 TAI'\n"), +#elif defined(__APPLE__) + SV("%c='Ven 13 fév 23:31:30 2009'\t%Ec='Ven 13 fév 23:31:30 2009'\n"), +#elif defined(_WIN32) + SV("%c='13/02/2009 23:31:30'\t%Ec='13/02/2009 23:31:30'\n"), +#elif defined(__FreeBSD__) + SV("%c='ven. 13 févr. 23:31:30 2009'\t%Ec='ven. 13 févr. 23:31:30 2009'\n"), +#else + SV("%c='ven. 13 févr. 2009 23:31:30'\t%Ec='ven. 13 févr. 2009 23:31:30'\n"), +#endif + lfmt, + cr::tai_seconds(1'613'259'090s)); // 23:31:30 TAI Friday, 13 February 2009 + + // Use supplied locale (ja_JP). This locale has a different alternate.a +#if defined(__APPLE__) || defined(__FreeBSD__) + check(loc, + SV("%c='水 1/ 1 00:00:00 1958'\t%Ec='水 1/ 1 00:00:00 1958'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + check(loc, + SV("%c='金 2/13 23:31:30 2009'\t%Ec='金 2/13 23:31:30 2009'\n"), + lfmt, + cr::tai_seconds(1'613'259'090s)); // 23:31:30 TAI Friday, 13 February 2009 +#elif defined(_AIX) // defined(__APPLE__)|| defined(__FreeBSD__) + check(loc, + SV("%c='1958年01月 1日 00:00:00 TAI'\t%Ec='1958年01月 1日 00:00:00 TAI'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + check(loc, + SV("%c='2009年02月13日 23:31:30 TAI'\t%Ec='2009年02月13日 23:31:30 TAI'\n"), + lfmt, + cr::tai_seconds(1'613'259'090s)); // 23:31:30 TAI Friday, 13 February 2009 +#elif defined(_WIN32) // defined(__APPLE__)|| defined(__FreeBSD__) + check(loc, + SV("%c='1958/01/01 0:00:00'\t%Ec='1958/01/01 0:00:00'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + check(loc, + SV("%c='2009/02/13 23:31:30'\t%Ec='2009/02/13 23:31:30'\n"), + lfmt, + cr::tai_seconds(1'613'259'090s)); // 23:31:30 TAI Friday, 13 February 2009 +#else // defined(__APPLE__)|| defined(__FreeBSD__) + check(loc, + SV("%c='1958年01月01日 00時00分00秒'\t%Ec='昭和33年01月01日 00時00分00秒'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + check(loc, + SV("%c='2009年02月13日 23時31分30秒'\t%Ec='平成21年02月13日 23時31分30秒'\n"), + lfmt, + cr::tai_seconds(1'613'259'090s)); // 23:31:30 TAI Friday, 13 February 2009 +#endif // defined(__APPLE__)|| defined(__FreeBSD__) + + std::locale::global(std::locale::classic()); +} + +template +static void test_valid_values_time_zone() { + using namespace std::literals::chrono_literals; + namespace cr = std::chrono; + + constexpr std::basic_string_view fmt = SV("{:%%z='%z'%t%%Ez='%Ez'%t%%Oz='%Oz'%t%%Z='%Z'%n}"); + constexpr std::basic_string_view lfmt = SV("{:L%%z='%z'%t%%Ez='%Ez'%t%%Oz='%Oz'%t%%Z='%Z'%n}"); + + const std::locale loc(LOCALE_ja_JP_UTF_8); + std::locale::global(std::locale(LOCALE_fr_FR_UTF_8)); + + // Non localized output using C-locale + check(SV("%z='+0000'\t%Ez='+00:00'\t%Oz='+00:00'\t%Z='TAI'\n"), + fmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + // Use the global locale (fr_FR) + check(SV("%z='+0000'\t%Ez='+00:00'\t%Oz='+00:00'\t%Z='TAI'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + // Use supplied locale (ja_JP). + check(loc, + SV("%z='+0000'\t%Ez='+00:00'\t%Oz='+00:00'\t%Z='TAI'\n"), + lfmt, + cr::tai_seconds(0s)); // 00:00:00 TAI Wednesday, 1 January 1958 + + std::locale::global(std::locale::classic()); +} + +template +static void test_valid_values() { + test_valid_values_year(); + test_valid_values_month(); + test_valid_values_day(); + test_valid_values_weekday(); + test_valid_values_day_of_year(); + test_valid_values_week(); + test_valid_values_iso_8601_week(); + test_valid_values_date(); + test_valid_values_time(); + test_valid_values_date_time(); + test_valid_values_time_zone(); +} + +template +static void test() { + using namespace std::literals::chrono_literals; + namespace cr = std::chrono; + + test_no_chrono_specs(); + test_valid_values(); + check_invalid_types( + {SV("a"), SV("A"), SV("b"), SV("B"), SV("c"), SV("C"), SV("d"), SV("D"), SV("e"), SV("F"), SV("g"), + SV("G"), SV("h"), SV("H"), SV("I"), SV("j"), SV("m"), SV("M"), SV("p"), SV("r"), SV("R"), SV("S"), + SV("T"), SV("u"), SV("U"), SV("V"), SV("w"), SV("W"), SV("x"), SV("X"), SV("y"), SV("Y"), SV("z"), + SV("Z"), SV("Ec"), SV("EC"), SV("Ex"), SV("EX"), SV("Ey"), SV("EY"), SV("Ez"), SV("Od"), SV("Oe"), SV("OH"), + SV("OI"), SV("Om"), SV("OM"), SV("OS"), SV("Ou"), SV("OU"), SV("OV"), SV("Ow"), SV("OW"), SV("Oy"), SV("Oz")}, + cr::tai_seconds(0s)); + + check_exception("The format specifier expects a '%' or a '}'", SV("{:A"), cr::tai_seconds(0s)); + check_exception("The chrono specifiers contain a '{'", SV("{:%%{"), cr::tai_seconds(0s)); + check_exception("End of input while parsing a conversion specifier", SV("{:%"), cr::tai_seconds(0s)); + check_exception("End of input while parsing the modifier E", SV("{:%E"), cr::tai_seconds(0s)); + check_exception("End of input while parsing the modifier O", SV("{:%O"), cr::tai_seconds(0s)); + + // Precision not allowed + check_exception("The format specifier expects a '%' or a '}'", SV("{:.3}"), cr::tai_seconds(0s)); +} + +int main(int, char**) { + test(); + +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#endif + + return 0; +} diff --git a/libcxx/test/support/concat_macros.h b/libcxx/test/support/concat_macros.h index d7340b8faf6e5..50b6562197392 100644 --- a/libcxx/test/support/concat_macros.h +++ b/libcxx/test/support/concat_macros.h @@ -11,6 +11,7 @@ #include #include +#include #include "assert_macros.h" #include "test_macros.h" @@ -134,6 +135,10 @@ OutIt test_transcode(InIt first, InIt last, OutIt out_it) { return out_it; } +std::ostream& operator<<(std::ostream& os, const std::source_location& loc) { + return os << loc.file_name() << ':' << loc.line() << ':' << loc.column(); +} + template concept test_streamable = requires(std::stringstream& stream, T&& value) { stream << value; };