https://github.com/mordante created https://github.com/llvm/llvm-project/pull/85896
Implements parts of: - P0355 Extending <chrono> to Calendars and Time Zones - P1361 Integration of chrono with text formatting >From 5597a07ac32a21d05b674d767395ee7583d11073 Mon Sep 17 00:00:00 2001 From: Mark de Wever <ko...@xs4all.nl> Date: Sun, 10 Mar 2024 17:49:39 +0100 Subject: [PATCH] [libc++][TZDB] Adds sys_info formatter. Implements parts of: - P0355 Extending <chrono> to Calendars and Time Zones - P1361 Integration of chrono with text formatting --- libcxx/docs/Status/FormatPaper.csv | 2 +- libcxx/include/__chrono/convert_to_tm.h | 5 + libcxx/include/__chrono/formatter.h | 37 +++++ libcxx/include/__chrono/ostream.h | 20 +++ libcxx/include/chrono | 5 + .../time.zone.info.sys/ostream.pass.cpp | 71 +++++++++ .../time/time.syn/formatter.sys_info.pass.cpp | 138 ++++++++++++++++++ .../time.zone.info.sys/ostream.pass.cpp | 48 ++++++ 8 files changed, 325 insertions(+), 1 deletion(-) create mode 100644 libcxx/test/libcxx/time/time.zone/time.zone.info/time.zone.info.sys/ostream.pass.cpp create mode 100644 libcxx/test/std/time/time.syn/formatter.sys_info.pass.cpp create mode 100644 libcxx/test/std/time/time.zone/time.zone.info/time.zone.info.sys/ostream.pass.cpp diff --git a/libcxx/docs/Status/FormatPaper.csv b/libcxx/docs/Status/FormatPaper.csv index 82da54284c7386..32166ec72da753 100644 --- a/libcxx/docs/Status/FormatPaper.csv +++ b/libcxx/docs/Status/FormatPaper.csv @@ -24,7 +24,7 @@ Section,Description,Dependencies,Assignee,Status,First released version `[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::year_month_weekday``",,Mark de Wever,|Complete|,16.0 `[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::year_month_weekday_last``",,Mark de Wever,|Complete|,16.0 `[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::hh_mm_ss<duration<Rep, Period>>``",,Mark de Wever,|Complete|,17.0 -`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::sys_info``",A ``<chrono>`` implementation,Mark de Wever,, +`[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::sys_info``",,Mark de Wever,|Complete|,19.0 `[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::local_info``",A ``<chrono>`` implementation,Mark de Wever,, `[time.syn] <https://wg21.link/time.syn>`_,"Formatter ``chrono::zoned_time<Duration, TimeZonePtr>``",A ``<chrono>`` implementation,Mark de Wever,, diff --git a/libcxx/include/__chrono/convert_to_tm.h b/libcxx/include/__chrono/convert_to_tm.h index 1301cd6f1f1ada..d2c5cf922ba671 100644 --- a/libcxx/include/__chrono/convert_to_tm.h +++ b/libcxx/include/__chrono/convert_to_tm.h @@ -20,6 +20,7 @@ #include <__chrono/month_weekday.h> #include <__chrono/monthday.h> #include <__chrono/statically_widen.h> +#include <__chrono/sys_info.h> #include <__chrono/system_clock.h> #include <__chrono/time_point.h> #include <__chrono/weekday.h> @@ -171,6 +172,10 @@ _LIBCPP_HIDE_FROM_ABI _Tm __convert_to_tm(const _ChronoT& __value) { if (__value.hours().count() > std::numeric_limits<decltype(__result.tm_hour)>::max()) std::__throw_format_error("Formatting hh_mm_ss, encountered an hour overflow"); __result.tm_hour = __value.hours().count(); +# if !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB) + } else if constexpr (same_as<_ChronoT, chrono::sys_info>) { + // Has no time information. +# endif } else static_assert(sizeof(_ChronoT) == 0, "Add the missing type specialization"); diff --git a/libcxx/include/__chrono/formatter.h b/libcxx/include/__chrono/formatter.h index 8b8592041a1fb9..f5474e3ea6d078 100644 --- a/libcxx/include/__chrono/formatter.h +++ b/libcxx/include/__chrono/formatter.h @@ -24,6 +24,7 @@ #include <__chrono/ostream.h> #include <__chrono/parser_std_format_spec.h> #include <__chrono/statically_widen.h> +#include <__chrono/sys_info.h> #include <__chrono/system_clock.h> #include <__chrono/time_point.h> #include <__chrono/weekday.h> @@ -202,6 +203,12 @@ struct _LIBCPP_HIDE_FROM_ABI __time_zone { template <class _Tp> _LIBCPP_HIDE_FROM_ABI __time_zone __convert_to_time_zone([[maybe_unused]] const _Tp& __value) { __time_zone __result; +# if !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB) + if constexpr (same_as<_Tp, chrono::sys_info>) { + __result.__offset = __value.offset; + __result.__abbrev = __value.abbrev; + } +# endif return __result; } @@ -411,6 +418,10 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool __weekday_ok(const _Tp& __value) { return __value.weekday().ok(); else if constexpr (__is_hh_mm_ss<_Tp>) return true; +# if !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB) + else if constexpr (same_as<_Tp, chrono::sys_info>) + return true; +# endif else static_assert(sizeof(_Tp) == 0, "Add the missing type specialization"); } @@ -451,6 +462,10 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool __weekday_name_ok(const _Tp& __value) { return __value.weekday().ok(); else if constexpr (__is_hh_mm_ss<_Tp>) return true; +# if !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB) + else if constexpr (same_as<_Tp, chrono::sys_info>) + return true; +# endif else static_assert(sizeof(_Tp) == 0, "Add the missing type specialization"); } @@ -491,6 +506,10 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool __date_ok(const _Tp& __value) { return __value.ok(); else if constexpr (__is_hh_mm_ss<_Tp>) return true; +# if !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB) + else if constexpr (same_as<_Tp, chrono::sys_info>) + return true; +# endif else static_assert(sizeof(_Tp) == 0, "Add the missing type specialization"); } @@ -531,6 +550,10 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool __month_name_ok(const _Tp& __value) { return __value.month().ok(); else if constexpr (__is_hh_mm_ss<_Tp>) return true; +# if !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB) + else if constexpr (same_as<_Tp, chrono::sys_info>) + return true; +# endif else static_assert(sizeof(_Tp) == 0, "Add the missing type specialization"); } @@ -860,6 +883,20 @@ struct formatter<chrono::hh_mm_ss<_Duration>, _CharT> : public __formatter_chron return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__time); } }; + +# if !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB) +template <__fmt_char_type _CharT> +struct formatter<chrono::sys_info, _CharT> : public __formatter_chrono<_CharT> { +public: + using _Base = __formatter_chrono<_CharT>; + + template <class _ParseContext> + _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) { + return _Base::__parse(__ctx, __format_spec::__fields_chrono, __format_spec::__flags::__time_zone); + } +}; +# endif // !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB) + #endif // if _LIBCPP_STD_VER >= 20 _LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/include/__chrono/ostream.h b/libcxx/include/__chrono/ostream.h index b687ef8059d5f5..68ddb1d6cd2e13 100644 --- a/libcxx/include/__chrono/ostream.h +++ b/libcxx/include/__chrono/ostream.h @@ -19,6 +19,7 @@ #include <__chrono/month_weekday.h> #include <__chrono/monthday.h> #include <__chrono/statically_widen.h> +#include <__chrono/sys_info.h> #include <__chrono/system_clock.h> #include <__chrono/weekday.h> #include <__chrono/year.h> @@ -262,6 +263,25 @@ operator<<(basic_ostream<_CharT, _Traits>& __os, const hh_mm_ss<_Duration> __hms return __os << std::format(__os.getloc(), _LIBCPP_STATICALLY_WIDEN(_CharT, "{:L%T}"), __hms); } +# if !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB) + +template <class _CharT, class _Traits> +_LIBCPP_HIDE_FROM_ABI basic_ostream<_CharT, _Traits>& +operator<<(basic_ostream<_CharT, _Traits>& __os, const sys_info& __info) { + // __info.abbrev is always std::basic_string<char>. + // Since these strings typically are short the conversion should be cheap. + std::basic_string<_CharT> __abbrev{__info.abbrev.begin(), __info.abbrev.end()}; + return __os << std::format( + _LIBCPP_STATICALLY_WIDEN(_CharT, "[{}, {}) {:%T} {:%Q%q} {}"), + __info.begin, + __info.end, + hh_mm_ss{__info.offset}, + __info.save, + __abbrev); +} + +# endif // !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB) + } // namespace chrono #endif // if _LIBCPP_STD_VER >= 20 diff --git a/libcxx/include/chrono b/libcxx/include/chrono index 00b940a6610a3a..c068bbef73245b 100644 --- a/libcxx/include/chrono +++ b/libcxx/include/chrono @@ -733,6 +733,10 @@ struct sys_info { string abbrev; }; +template<class charT, class traits> // C++20 + basic_ostream<charT, traits>& + operator<<(basic_ostream<charT, traits>& os, const sys_info& si); + // 25.10.5, class time_zone // C++20 enum class choose {earliest, latest}; class time_zone { @@ -829,6 +833,7 @@ namespace std { template<class charT> struct formatter<chrono::year_month_weekday_last, charT>; // C++20 template<class Rep, class Period, class charT> struct formatter<chrono::hh_mm_ss<duration<Rep, Period>>, charT>; // C++20 + template<class charT> struct formatter<chrono::sys_info, charT>; // C++20 } // namespace std namespace chrono { diff --git a/libcxx/test/libcxx/time/time.zone/time.zone.info/time.zone.info.sys/ostream.pass.cpp b/libcxx/test/libcxx/time/time.zone/time.zone.info/time.zone.info.sys/ostream.pass.cpp new file mode 100644 index 00000000000000..26a8ddd3bfbad9 --- /dev/null +++ b/libcxx/test/libcxx/time/time.zone/time.zone.info/time.zone.info.sys/ostream.pass.cpp @@ -0,0 +1,71 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: no-localization + +// XFAIL: libcpp-has-no-incomplete-tzdb + +// <chrono> + +// template<class charT, class traits> +// basic_ostream<charT, traits>& +// operator<<(basic_ostream<charT, traits>& os, const sys_info& r); + +// [time.zone.info.sys] +// 7 Effects: Streams out the sys_info object r in an unspecified format. +// 8 Returns: os. +// +// Tests the output produced by this function. + +#include <cassert> +#include <chrono> +#include <memory> +#include <sstream> + +#include "assert_macros.h" +#include "test_macros.h" +#include "make_string.h" +#include "concat_macros.h" + +#define SV(S) MAKE_STRING_VIEW(CharT, S) + +template <class CharT> +static void test(std::basic_string_view<CharT> expected, std::chrono::sys_info&& info) { + std::basic_stringstream<CharT> sstr; + sstr << info; + std::basic_string<CharT> output = sstr.str(); + + TEST_REQUIRE(expected == output, + TEST_WRITE_CONCATENATED("\nExpected output ", expected, "\nActual output ", output, '\n')); +} + +template <class CharT> +static void test() { + using namespace std::literals::chrono_literals; + namespace tz = std::chrono; + + test(SV("[-10484-10-16 15:30:08, 14423-03-17 15:30:07) 00:00:00 0min TZ"), + tz::sys_info{tz::sys_seconds::min(), tz::sys_seconds::max(), 0s, 0min, "TZ"}); + + test(SV("[1970-01-01 00:00:00, 2038-12-31 00:00:00) 12:23:45 -67min DMY"), + tz::sys_info{static_cast<tz::sys_days>(tz::year_month_day{1970y, tz::January, 1d}), + static_cast<tz::sys_days>(tz::year_month_day{2038y, tz::December, 31d}), + 12h + 23min + 45s, + -67min, + "DMY"}); +} + +int main(int, const char**) { + test<char>(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test<wchar_t>(); +#endif + + return 0; +} diff --git a/libcxx/test/std/time/time.syn/formatter.sys_info.pass.cpp b/libcxx/test/std/time/time.syn/formatter.sys_info.pass.cpp new file mode 100644 index 00000000000000..53da20668c64f5 --- /dev/null +++ b/libcxx/test/std/time/time.syn/formatter.sys_info.pass.cpp @@ -0,0 +1,138 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: no-localization +// 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-incomplete-tzdb + +// REQUIRES: locale.fr_FR.UTF-8 +// REQUIRES: locale.ja_JP.UTF-8 + +// <chrono> +// +// template<class charT> struct formatter<chrono::sys_info, charT>; + +#include <chrono> +#include <format> + +#include <cassert> +#include <concepts> +#include <locale> +#include <iostream> +#include <type_traits> + +#include "formatter_tests.h" +#include "make_string.h" +#include "platform_support.h" // locale name macros +#include "test_macros.h" + +template <class CharT> +static void test_no_chrono_specs() { +// This test libc++ specific due to +// [time.zone.info.sys]/7 +// Effects: Streams out the sys_info object r in an unspecified format. +#ifdef _LIBCPP_VERSION + using namespace std::literals::chrono_literals; + namespace tz = std::chrono; + + std::locale::global(std::locale(LOCALE_fr_FR_UTF_8)); + + // Non localized output + + check(SV("[-10484-10-16 15:30:08, 14423-03-17 15:30:07) 00:00:00 0min TZ"), + SV("{}"), + tz::sys_info{tz::sys_seconds::min(), tz::sys_seconds::max(), 0s, 0min, "TZ"}); + + check(SV("[1970-01-01 00:00:00, 2038-12-31 00:00:00) 12:23:45 -67min DMY"), + SV("{}"), + tz::sys_info{static_cast<tz::sys_days>(tz::year_month_day{1970y, tz::January, 1d}), + static_cast<tz::sys_days>(tz::year_month_day{2038y, tz::December, 31d}), + 12h + 23min + 45s, + -67min, + "DMY"}); + + std::locale::global(std::locale::classic()); +#endif // _LIBCPP_VERSION +} + +template <class CharT> +static void test_valid_values() { + using namespace std::literals::chrono_literals; + + constexpr std::basic_string_view<CharT> fmt = SV("{:%%z='%z'%t%%Ez='%Ez'%t%%Oz='%Oz'%t%%Z='%Z'%n}"); + constexpr std::basic_string_view<CharT> 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='-0200'\t%Ez='-02:00'\t%Oz='-02:00'\t%Z='NEG'\n"), + fmt, + std::chrono::sys_info{std::chrono::sys_seconds{0s}, std::chrono::sys_seconds{0s}, -2h, 0min, "NEG"}); + + check(SV("%z='+0000'\t%Ez='+00:00'\t%Oz='+00:00'\t%Z='ZERO'\n"), + fmt, + std::chrono::sys_info{std::chrono::sys_seconds{0s}, std::chrono::sys_seconds{0s}, 0s, 0min, "ZERO"}); + + check(SV("%z='+1115'\t%Ez='+11:15'\t%Oz='+11:15'\t%Z='POS'\n"), + fmt, + std::chrono::sys_info{std::chrono::sys_seconds{0s}, std::chrono::sys_seconds{0s}, 11h + 15min, 0min, "POS"}); + + // Use the global locale (fr_FR) + check(SV("%z='-0200'\t%Ez='-02:00'\t%Oz='-02:00'\t%Z='NEG'\n"), + lfmt, + std::chrono::sys_info{std::chrono::sys_seconds{0s}, std::chrono::sys_seconds{0s}, -2h, 0min, "NEG"}); + + check(SV("%z='+0000'\t%Ez='+00:00'\t%Oz='+00:00'\t%Z='ZERO'\n"), + lfmt, + std::chrono::sys_info{std::chrono::sys_seconds{0s}, std::chrono::sys_seconds{0s}, 0s, 0min, "ZERO"}); + + check(SV("%z='+1115'\t%Ez='+11:15'\t%Oz='+11:15'\t%Z='POS'\n"), + lfmt, + std::chrono::sys_info{std::chrono::sys_seconds{0s}, std::chrono::sys_seconds{0s}, 11h + 15min, 0min, "POS"}); + + // Use supplied locale (ja_JP). + check(loc, + SV("%z='-0200'\t%Ez='-02:00'\t%Oz='-02:00'\t%Z='NEG'\n"), + lfmt, + std::chrono::sys_info{std::chrono::sys_seconds{0s}, std::chrono::sys_seconds{0s}, -2h, 0min, "NEG"}); + + check(loc, + SV("%z='+0000'\t%Ez='+00:00'\t%Oz='+00:00'\t%Z='ZERO'\n"), + lfmt, + std::chrono::sys_info{std::chrono::sys_seconds{0s}, std::chrono::sys_seconds{0s}, 0s, 0min, "ZERO"}); + + check(loc, + SV("%z='+1115'\t%Ez='+11:15'\t%Oz='+11:15'\t%Z='POS'\n"), + lfmt, + std::chrono::sys_info{std::chrono::sys_seconds{0s}, std::chrono::sys_seconds{0s}, 11h + 15min, 0min, "POS"}); + + std::locale::global(std::locale::classic()); +} + +template <class CharT> +static void test() { + test_no_chrono_specs<CharT>(); + test_valid_values<CharT>(); + + check_invalid_types<CharT>({SV("z"), SV("Z"), SV("Ez"), SV("Oz")}, std::chrono::sys_info{}); +} + +int main(int, char**) { + test<char>(); + +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test<wchar_t>(); +#endif + + return 0; +} diff --git a/libcxx/test/std/time/time.zone/time.zone.info/time.zone.info.sys/ostream.pass.cpp b/libcxx/test/std/time/time.zone/time.zone.info/time.zone.info.sys/ostream.pass.cpp new file mode 100644 index 00000000000000..4edbdb065b1c90 --- /dev/null +++ b/libcxx/test/std/time/time.zone/time.zone.info/time.zone.info.sys/ostream.pass.cpp @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: no-localization + +// XFAIL: libcpp-has-no-incomplete-tzdb + +// <chrono> + +// template<class charT, class traits> +// basic_ostream<charT, traits>& +// operator<<(basic_ostream<charT, traits>& os, const sys_info& r); + +// [time.zone.info.sys] +// 7 Effects: Streams out the sys_info object r in an unspecified format. +// 8 Returns: os. +// +// There is a private libc++ test that validates the exact output. + +#include <cassert> +#include <chrono> +#include <memory> +#include <sstream> + +#include "test_macros.h" + +template <class CharT> +static void test() { + std::chrono::sys_info s; + std::basic_ostringstream<CharT> os; + std::basic_ostream<CharT>& result = std::chrono::operator<<(os, s); + assert(std::addressof(result) == std::addressof(os)); +} + +int main(int, const char**) { + test<char>(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test<wchar_t>(); +#endif + + return 0; +} _______________________________________________ llvm-branch-commits mailing list llvm-branch-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits