diff --git a/libcxx/docs/Status/Cxx20Issues.csv b/libcxx/docs/Status/Cxx20Issues.csv --- a/libcxx/docs/Status/Cxx20Issues.csv +++ b/libcxx/docs/Status/Cxx20Issues.csv @@ -175,7 +175,7 @@ "`3253 `__","``basic_syncbuf::basic_syncbuf()``\ should not be explicit","Belfast","","" "`3245 `__","Unnecessary restriction on ``'%p'``\ parse specifier","Belfast","","","|chrono|" "`3244 `__","Constraints for ``Source``\ in |sect|\ [fs.path.req] insufficiently constrainty","Belfast","","" -"`3241 `__","``chrono-spec``\ grammar ambiguity in |sect|\ [time.format]","Belfast","","","|chrono| |format|" +"`3241 `__","``chrono-spec``\ grammar ambiguity in |sect|\ [time.format]","Belfast","|Complete|","16.0","|chrono| |format|" "`3257 `__","Missing feature testing macro update from P0858","Belfast","","" "`3256 `__","Feature testing macro for ``constexpr``\ algorithms","Belfast","|Complete|","13.0" "`3273 `__","Specify ``weekday_indexed``\ to range of ``[0, 7]``\ ","Belfast","","","|chrono|" diff --git a/libcxx/docs/Status/Cxx20Papers.csv b/libcxx/docs/Status/Cxx20Papers.csv --- a/libcxx/docs/Status/Cxx20Papers.csv +++ b/libcxx/docs/Status/Cxx20Papers.csv @@ -114,7 +114,7 @@ "`P1207R4 `__","LWG","Movability of Single-pass Iterators","Cologne","|Complete|","15.0" "`P1208R6 `__","LWG","Adopt source_location for C++20","Cologne","","" "`P1355R2 `__","LWG","Exposing a narrow contract for ceil2","Cologne","|Complete|","9.0" -"`P1361R2 `__","LWG","Integration of chrono with text formatting","Cologne","","" +"`P1361R2 `__","LWG","Integration of chrono with text formatting","Cologne","|In Progress|","" "`P1423R3 `__","LWG","char8_t backward compatibility remediation","Cologne","|Complete|","15.0" "`P1424R1 `__","LWG","'constexpr' feature macro concerns","Cologne","Superseded by `P1902 `__","" "`P1466R3 `__","LWG","Miscellaneous minor fixes for chrono","Cologne","","" diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -204,15 +204,20 @@ __charconv/to_chars_result.h __chrono/calendar.h __chrono/convert_to_timespec.h + __chrono/convert_to_tm.h __chrono/day.h __chrono/duration.h __chrono/file_clock.h + __chrono/formatter.h __chrono/hh_mm_ss.h __chrono/high_resolution_clock.h __chrono/literals.h __chrono/month.h __chrono/month_weekday.h __chrono/monthday.h + __chrono/ostream.h + __chrono/parser_std_format_spec.h + __chrono/statically_widen.h __chrono/steady_clock.h __chrono/system_clock.h __chrono/time_point.h diff --git a/libcxx/include/__chrono/convert_to_tm.h b/libcxx/include/__chrono/convert_to_tm.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__chrono/convert_to_tm.h @@ -0,0 +1,45 @@ +// -*- 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_CONVERT_TO_TM_H +#define _LIBCPP___CHRONO_CONVERT_TO_TM_H + +#include <__chrono/day.h> +#include <__concepts/same_as.h> +#include <__config> +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER > 17 + +template +_LIBCPP_HIDE_FROM_ABI tm __convert_to_tm(const _Tp& __value) { + tm __result = {}; +# ifdef __GLIBC__ + __result.tm_zone = "UTC"; +# endif + + if constexpr (same_as<_Tp, chrono::day>) + __result.tm_mday = static_cast(__value); + else + static_assert(sizeof(_Tp) == 0, "Add the missing type specialization"); + + return __result; +} + +#endif //if _LIBCPP_STD_VER > 17 + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___CHRONO_CONVERT_TO_TM_H diff --git a/libcxx/include/__chrono/formatter.h b/libcxx/include/__chrono/formatter.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__chrono/formatter.h @@ -0,0 +1,153 @@ +// -*- 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_FORMATTER_H +#define _LIBCPP___CHRONO_FORMATTER_H + +#include <__chrono/convert_to_tm.h> +#include <__chrono/day.h> +#include <__chrono/parser_std_format_spec.h> +#include <__config> +#include <__format/concepts.h> +#include <__format/format_parse_context.h> +#include <__format/formatter.h> +#include <__format/formatter_output.h> +#include <__format/parser_std_format_spec.h> +#include +#include +#include +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT) + +namespace __formatter { + +/// Formats a time based on a tm struct. +/// +/// This formatter passes the formatting to time_put which uses strftime. When +/// the value is outside the valid range it's unspecified what strftime will +/// output. For example weekday 8 can print 1 when the day is processed modulo +/// 7 since that handles the Sunday for 0-based weekday. It can also print 8 if +/// 7 is handled as a special case. +/// +/// The Standard doesn't specify what to do in this case so the result depends +/// on the result of the underlying code. +/// +/// \pre When the (abbreviated) weekday or month name are used, the caller +/// validates whether the value is valid. So the caller handles that +/// requirement of Table 97: Meaning of conversion specifiers +/// [tab:time.format.spec]. +/// +/// When no chrono-specs are provided it uses the stream formatter. + +template +_LIBCPP_HIDE_FROM_ABI void __format_chrono_using_chrono_specs( + const _Tp& __value, basic_stringstream<_CharT>& __sstr, basic_string_view<_CharT> __chrono_specs) { + tm __t = std::__convert_to_tm(__value); + const auto& __facet = std::use_facet>(__sstr.getloc()); + for (auto __it = __chrono_specs.begin(); __it != __chrono_specs.end(); ++__it) { + if (*__it == _CharT('%')) { + auto __s = __it; + ++__it; + // We only handle the types that can't be directly handled by time_put. + // (as an optimization n, t, and % are also handled directly.) + switch (*__it) { + case _CharT('n'): + __sstr << _CharT('\n'); + break; + case _CharT('t'): + __sstr << _CharT('\t'); + break; + case _CharT('%'): + __sstr << *__it; + break; + + case _CharT('O'): + ++__it; + [[fallthrough]]; + default: + __facet.put({__sstr}, __sstr, _CharT(' '), std::addressof(__t), __s, __it + 1); + break; + } + } else { + __sstr << *__it; + } + } +} + +template +_LIBCPP_HIDE_FROM_ABI auto +__format_chrono(const _Tp& __value, + auto& __ctx, + __format_spec::__parsed_specifications<_CharT> __specs, + basic_string_view<_CharT> __chrono_specs) -> decltype(__ctx.out()) { + basic_stringstream<_CharT> __sstr; + // [time.format]/2 + // 2.1 - the "C" locale if the L option is not present in chrono-format-spec, otherwise + // 2.2 - the locale passed to the formatting function if any, otherwise + // 2.3 - the global locale. + // Note that the __ctx's locale() call does 2.2 and 2.3. + if (__specs.__chrono_.__locale_specific_form_) + __sstr.imbue(__ctx.locale()); + else + __sstr.imbue(locale::classic()); + + if (__chrono_specs.empty()) + __sstr << __value; + else + __formatter::__format_chrono_using_chrono_specs(__value, __sstr, __chrono_specs); + + // TODO FMT Use the stringstream's view after P0408R7 has been implemented. + basic_string<_CharT> __str = __sstr.str(); + return __formatter::__write_string(basic_string_view<_CharT>{__str}, __ctx.out(), __specs); +} + +} // namespace __formatter + +template <__fmt_char_type _CharT> +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT __formatter_chrono { +public: + _LIBCPP_HIDE_FROM_ABI constexpr auto __parse( + basic_format_parse_context<_CharT>& __parse_ctx, __format_spec::__fields __fields, __format_spec::__flags __flags) + -> decltype(__parse_ctx.begin()) { + return __parser_.__parse(__parse_ctx, __fields, __flags); + } + + template + _LIBCPP_HIDE_FROM_ABI auto format(const _Tp& __value, auto& __ctx) const -> decltype(__ctx.out()) const { + return __formatter::__format_chrono( + __value, __ctx, __parser_.__parser_.__get_parsed_chrono_specifications(__ctx), __parser_.__chrono_specs_); + } + + __format_spec::__parser_chrono<_CharT> __parser_; +}; + +template <__fmt_char_type _CharT> +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter + : public __formatter_chrono<_CharT> { +public: + using _Base = __formatter_chrono<_CharT>; + + _LIBCPP_HIDE_FROM_ABI constexpr auto parse(basic_format_parse_context<_CharT>& __parse_ctx) + -> decltype(__parse_ctx.begin()) { + return _Base::__parse(__parse_ctx, __format_spec::__fields_chrono, __format_spec::__flags::__day); + } +}; + +#endif //if _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT) + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___CHRONO_FORMATTER_H diff --git a/libcxx/include/__chrono/ostream.h b/libcxx/include/__chrono/ostream.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__chrono/ostream.h @@ -0,0 +1,49 @@ +// -*- 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_OSTREAM_H +#define _LIBCPP___CHRONO_OSTREAM_H + +#include <__chrono/day.h> +#include <__chrono/statically_widen.h> +#include <__config> +#include +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT) + +namespace chrono { + +template +_LIBCPP_HIDE_FROM_ABI _LIBCPP_AVAILABILITY_FORMAT basic_ostream<_CharT, _Traits>& +operator<<(basic_ostream<_CharT, _Traits>& __os, const day& __d) { + return __os + << (__d.ok() + ? std::format(_LIBCPP_STATICALLY_WIDEN(_CharT, "{:%d}"), __d) + // Note this error differs from the wording of the Standard. The + // Standard wording doesn't work well on AIX or Windows. There + // the formatted day seems to be either modulo 100 or completely + // omitted. Judging by the wording this is valid. + // TODO FMT Write a paper of file an LWG issue. + : std::format(_LIBCPP_STATICALLY_WIDEN(_CharT, "{:02} is not a valid day"), static_cast(__d))); +} + +} // namespace chrono + +#endif //if _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT) + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___CHRONO_OSTREAM_H diff --git a/libcxx/include/__chrono/parser_std_format_spec.h b/libcxx/include/__chrono/parser_std_format_spec.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__chrono/parser_std_format_spec.h @@ -0,0 +1,401 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP___CHRONO_PARSER_STD_FORMAT_SPEC_H +#define _LIBCPP___CHRONO_PARSER_STD_FORMAT_SPEC_H + +#include <__config> +#include <__format/concepts.h> +#include <__format/format_error.h> +#include <__format/format_parse_context.h> +#include <__format/formatter_string.h> +#include <__format/parser_std_format_spec.h> +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT) + +namespace __format_spec { + +// By not placing this constant in the formatter class it's not duplicated for char and wchar_t +inline constexpr __fields __fields_chrono{.__locale_specific_form_ = true, .__type_ = false}; + +/// Flags available or required in a chrono type. +/// +/// The caller of the chrono formatter lists the types it has available and the +/// validation tests whether the requested type spec (e.g. %M) is available in +/// the formatter. +/// When the type in the chrono-format-spec isn't present in the data a +/// \ref format_error is thrown. +enum class __flags { + __second = 0x1, + __minute = 0x2, + __hour = 0x4, + __time = __hour | __minute | __second, + + __day = 0x8, + __month = 0x10, + __year = 0x20, + + __weekday = 0x40, + + __month_day = __day | __month, + __month_weekday = __weekday | __month, + __year_month = __month | __year, + __date = __day | __month | __year | __weekday, + + __date_time = __date | __time, + + __duration = 0x80 | __time, + + __time_zone = 0x100, + + __clock = __date_time | __time_zone +}; + +_LIBCPP_HIDE_FROM_ABI constexpr __flags operator&(__flags __lhs, __flags __rhs) { + return static_cast<__flags>(static_cast(__lhs) & static_cast(__rhs)); +} + +_LIBCPP_HIDE_FROM_ABI constexpr void __validate_second(__flags __flags) { + if ((__flags & __flags::__second) != __flags::__second) + std::__throw_format_error("The supplied date time doesn't contain a second"); +} + +_LIBCPP_HIDE_FROM_ABI constexpr void __validate_minute(__flags __flags) { + if ((__flags & __flags::__minute) != __flags::__minute) + std::__throw_format_error("The supplied date time doesn't contain a minute"); +} + +_LIBCPP_HIDE_FROM_ABI constexpr void __validate_hour(__flags __flags) { + if ((__flags & __flags::__hour) != __flags::__hour) + std::__throw_format_error("The supplied date time doesn't contain an hour"); +} + +_LIBCPP_HIDE_FROM_ABI constexpr void __validate_time(__flags __flags) { + if ((__flags & __flags::__time) != __flags::__time) + std::__throw_format_error("The supplied date time doesn't contain a time"); +} + +_LIBCPP_HIDE_FROM_ABI constexpr void __validate_day(__flags __flags) { + if ((__flags & __flags::__day) != __flags::__day) + std::__throw_format_error("The supplied date time doesn't contain a day"); +} + +_LIBCPP_HIDE_FROM_ABI constexpr void __validate_month(__flags __flags) { + if ((__flags & __flags::__month) != __flags::__month) + std::__throw_format_error("The supplied date time doesn't contain a month"); +} + +_LIBCPP_HIDE_FROM_ABI constexpr void __validate_year(__flags __flags) { + if ((__flags & __flags::__year) != __flags::__year) + std::__throw_format_error("The supplied date time doesn't contain a year"); +} + +_LIBCPP_HIDE_FROM_ABI constexpr void __validate_date(__flags __flags) { + if ((__flags & __flags::__date) != __flags::__date) + std::__throw_format_error("The supplied date time doesn't contain a date"); +} + +_LIBCPP_HIDE_FROM_ABI constexpr void __validate_date_or_duration(__flags __flags) { + if (((__flags & __flags::__date) != __flags::__date) && ((__flags & __flags::__duration) != __flags::__duration)) + std::__throw_format_error("The supplied date time doesn't contain a date or duration"); +} + +_LIBCPP_HIDE_FROM_ABI constexpr void __validate_date_time(__flags __flags) { + if ((__flags & __flags::__date_time) != __flags::__date_time) + std::__throw_format_error("The supplied date time doesn't contain a date and time"); +} + +_LIBCPP_HIDE_FROM_ABI constexpr void __validate_weekday(__flags __flags) { + if ((__flags & __flags::__weekday) != __flags::__weekday) + std::__throw_format_error("The supplied date time doesn't contain a weekday"); +} + +_LIBCPP_HIDE_FROM_ABI constexpr void __validate_duration(__flags __flags) { + if ((__flags & __flags::__duration) != __flags::__duration) + std::__throw_format_error("The supplied date time doesn't contain a duration"); +} + +_LIBCPP_HIDE_FROM_ABI constexpr void __validate_time_zone(__flags __flags) { + if ((__flags & __flags::__time_zone) != __flags::__time_zone) + std::__throw_format_error("The supplied date time doesn't contain a time zone"); +} + +template +class _LIBCPP_TEMPLATE_VIS __parser_chrono { +public: + _LIBCPP_HIDE_FROM_ABI constexpr auto + __parse(basic_format_parse_context<_CharT>& __parse_ctx, __fields __fields, __flags __flags) + -> decltype(__parse_ctx.begin()) { + const _CharT* __begin = __parser_.__parse(__parse_ctx, __fields); + const _CharT* __end = __parse_ctx.end(); + if (__begin == __end) + return __begin; + + const _CharT* __last = __parse_chrono_specs(__begin, __end, __flags); + __chrono_specs_ = basic_string_view<_CharT>{__begin, __last}; + + return __last; + } + + __parser<_CharT> __parser_; + basic_string_view<_CharT> __chrono_specs_; + +private: + _LIBCPP_HIDE_FROM_ABI constexpr const _CharT* + __parse_chrono_specs(const _CharT* __begin, const _CharT* __end, __flags __flags) { + _LIBCPP_ASSERT(__begin != __end, + "When called with an empty input the function will cause " + "undefined behavior by evaluating data not in the input"); + + if (*__begin != _CharT('%') && *__begin != _CharT('}')) + std::__throw_format_error("Expected '%' or '}' in the chrono format-string"); + + do { + switch (*__begin) { + case _CharT('{'): + std::__throw_format_error("The chrono-specs contains a '{'"); + + case _CharT('}'): + return __begin; + + case _CharT('%'): + __parse_conversion_spec(__begin, __end, __flags); + [[fallthrough]]; + + default: + // All other literals + ++__begin; + } + + } while (__begin != __end && *__begin != _CharT('}')); + + return __begin; + } + + /// \pre *__begin == '%' + /// \post __begin points at the end parsed conversion-spec + _LIBCPP_HIDE_FROM_ABI constexpr void + __parse_conversion_spec(const _CharT*& __begin, const _CharT* __end, __flags __flags) { + ++__begin; + if (__begin == __end) + std::__throw_format_error("End of input while parsing the modifier chrono conversion-spec"); + + switch (*__begin) { + case _CharT('n'): + case _CharT('t'): + case _CharT('%'): + break; + + case _CharT('S'): + __format_spec::__validate_second(__flags); + break; + + case _CharT('M'): + __format_spec::__validate_minute(__flags); + break; + + case _CharT('p'): // TODO FMT does the formater require an hour or a time? + case _CharT('H'): + case _CharT('I'): + __validate_hour(__flags); + break; + + case _CharT('r'): + case _CharT('R'): + case _CharT('T'): + case _CharT('X'): + __format_spec::__validate_time(__flags); + break; + + case _CharT('d'): + case _CharT('e'): + __format_spec::__validate_day(__flags); + break; + + case _CharT('b'): + case _CharT('h'): + case _CharT('B'): + __parser_.__month_name_ = true; + [[fallthrough]]; + case _CharT('m'): + __format_spec::__validate_month(__flags); + break; + + case _CharT('y'): + case _CharT('C'): + case _CharT('Y'): + __format_spec::__validate_year(__flags); + break; + + case _CharT('j'): + __format_spec::__validate_date_or_duration(__flags); + break; + + case _CharT('g'): + case _CharT('x'): + case _CharT('D'): + case _CharT('F'): + case _CharT('G'): + case _CharT('U'): + case _CharT('V'): + case _CharT('W'): + __format_spec::__validate_date(__flags); + break; + + case _CharT('c'): + __format_spec::__validate_date_time(__flags); + break; + + case _CharT('a'): + case _CharT('A'): + __parser_.__weekday_name_ = true; + [[fallthrough]]; + case _CharT('u'): + case _CharT('w'): + __format_spec::__validate_weekday(__flags); + break; + + case _CharT('q'): + case _CharT('Q'): + __format_spec::__validate_duration(__flags); + break; + + case _CharT('E'): + __parse_modifier_E(__begin, __end, __flags); + break; + + case _CharT('O'): + __parse_modifier_O(__begin, __end, __flags); + break; + + case _CharT('z'): + case _CharT('Z'): + // Currently there's no time zone information. However some clocks have a + // hard-coded "time zone", for these clocks the information can be used. + // TODO FMT implement time zones. + __format_spec::__validate_time_zone(__flags); + break; + + default: // unknown type; + std::__throw_format_error("The date time type specifier is invalid"); + } + } + + /// \pre *__begin == 'E' + /// \post __begin is incremented by one. + _LIBCPP_HIDE_FROM_ABI constexpr void + __parse_modifier_E(const _CharT*& __begin, const _CharT* __end, __flags __flags) { + ++__begin; + if (__begin == __end) + std::__throw_format_error("End of input while parsing the modifier E"); + + switch (*__begin) { + case _CharT('X'): + __format_spec::__validate_time(__flags); + break; + + case _CharT('y'): + case _CharT('C'): + case _CharT('Y'): + __format_spec::__validate_year(__flags); + break; + + case _CharT('x'): + __format_spec::__validate_date(__flags); + break; + + case _CharT('c'): + __format_spec::__validate_date_time(__flags); + break; + + case _CharT('z'): + // Currently there's no time zone information. However some clocks have a + // hard-coded "time zone", for these clocks the information can be used. + // TODO FMT implement time zones. + __format_spec::__validate_time_zone(__flags); + break; + + default: + std::__throw_format_error("The date time type specifier for modifier E is invalid"); + } + } + + /// \pre *__begin == 'O' + /// \post __begin is incremented by one. + _LIBCPP_HIDE_FROM_ABI constexpr void + __parse_modifier_O(const _CharT*& __begin, const _CharT* __end, __flags __flags) { + ++__begin; + if (__begin == __end) + std::__throw_format_error("End of input while parsing the modifier O"); + + switch (*__begin) { + case _CharT('S'): + __format_spec::__validate_second(__flags); + break; + + case _CharT('M'): + __format_spec::__validate_minute(__flags); + break; + + case _CharT('I'): + case _CharT('H'): + __format_spec::__validate_hour(__flags); + break; + + case _CharT('d'): + case _CharT('e'): + __format_spec::__validate_day(__flags); + break; + + case _CharT('m'): + __format_spec::__validate_month(__flags); + break; + + case _CharT('y'): + __format_spec::__validate_year(__flags); + break; + + case _CharT('U'): + case _CharT('V'): + case _CharT('W'): + __format_spec::__validate_date(__flags); + break; + + case _CharT('u'): + case _CharT('w'): + __format_spec::__validate_weekday(__flags); + break; + + case _CharT('z'): + // Currently there's no time zone information. However some clocks have a + // hard-coded "time zone", for these clocks the information can be used. + // TODO FMT implement time zones. + __format_spec::__validate_time_zone(__flags); + break; + + default: + std::__throw_format_error("The date time type specifier for modifier O is invalid"); + } + } +}; + +} // namespace __format_spec + +#endif //_LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT) + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___CHRONO_PARSER_STD_FORMAT_SPEC_H diff --git a/libcxx/include/__chrono/statically_widen.h b/libcxx/include/__chrono/statically_widen.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__chrono/statically_widen.h @@ -0,0 +1,52 @@ +// -*- 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_STATICALLY_WIDEN_H +#define _LIBCPP___CHRONO_STATICALLY_WIDEN_H + +// Implements the STATICALLY-WIDEN exposition-only function. ([time.general]/2) + +#include <__concepts/same_as.h> +#include <__config> +#include <__format/concepts.h> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER > 17 + +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS +template <__fmt_char_type _CharT> +consteval const _CharT* __statically_widen(const char* __str, const wchar_t* __wstr) { + if constexpr (same_as<_CharT, char>) + return __str; + else + return __wstr; +} +# define _LIBCPP_STATICALLY_WIDEN(_CharT, __str) ::std::__statically_widen<_CharT>(__str, L##__str) +# else // _LIBCPP_HAS_NO_WIDE_CHARACTERS + +// Without this indirection the unit test test/libcxx/modules_include.sh.cpp +// fails for the CI build "No wide characters". This seems like a bug. +// TODO FMT investigate why this is needed. +template <__fmt_char_type _CharT> +consteval const _CharT* __statically_widen(const char* __str) { + return __str; +} +# define _LIBCPP_STATICALLY_WIDEN(_CharT, __str) ::std::__statically_widen<_CharT>(__str) +# endif // _LIBCPP_HAS_NO_WIDE_CHARACTERS + +#endif //if _LIBCPP_STD_VER > 17 + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___CHRONO_STATICALLY_WIDEN_H diff --git a/libcxx/include/__format/parser_std_format_spec.h b/libcxx/include/__format/parser_std_format_spec.h --- a/libcxx/include/__format/parser_std_format_spec.h +++ b/libcxx/include/__format/parser_std_format_spec.h @@ -176,6 +176,7 @@ struct __chrono { __alignment __alignment_ : 3; + bool __locale_specific_form_ : 1; bool __weekday_name_ : 1; bool __month_name_ : 1; }; @@ -288,12 +289,22 @@ _LIBCPP_HIDE_FROM_ABI __parsed_specifications<_CharT> __get_parsed_std_specifications(auto& __ctx) const { return __parsed_specifications<_CharT>{ - .__std_ = - __std{.__alignment_ = __alignment_, - .__sign_ = __sign_, - .__alternate_form_ = __alternate_form_, - .__locale_specific_form_ = __locale_specific_form_, - .__type_ = __type_}, + .__std_ = __std{.__alignment_ = __alignment_, + .__sign_ = __sign_, + .__alternate_form_ = __alternate_form_, + .__locale_specific_form_ = __locale_specific_form_, + .__type_ = __type_}, + .__width_{__get_width(__ctx)}, + .__precision_{__get_precision(__ctx)}, + .__fill_{__fill_}}; + } + + _LIBCPP_HIDE_FROM_ABI __parsed_specifications<_CharT> __get_parsed_chrono_specifications(auto& __ctx) const { + return __parsed_specifications<_CharT>{ + .__chrono_ = __chrono{.__alignment_ = __alignment_, + .__locale_specific_form_ = __locale_specific_form_, + .__weekday_name_ = __weekday_name_, + .__month_name_ = __month_name_}, .__width_{__get_width(__ctx)}, .__precision_{__get_precision(__ctx)}, .__fill_{__fill_}}; diff --git a/libcxx/include/chrono b/libcxx/include/chrono --- a/libcxx/include/chrono +++ b/libcxx/include/chrono @@ -332,6 +332,9 @@ constexpr day operator+(const days& x, const day& y) noexcept; constexpr day operator-(const day& x, const days& y) noexcept; constexpr days operator-(const day& x, const day& y) noexcept; +template + basic_ostream& + operator<<(basic_ostream& os, const day& d); // 25.8.4, class month // C++20 class month; @@ -613,7 +616,13 @@ bool operator>(const time_zone& x, const time_zone& y) noexcept; bool operator<=(const time_zone& x, const time_zone& y) noexcept; bool operator>=(const time_zone& x, const time_zone& y) noexcept; +} // chrono + +namespace std { + template struct formatter; // C++20 +} // namespace std +namespace chrono { // calendrical constants inline constexpr last_spec last{}; // C++20 inline constexpr chrono::weekday Sunday{0}; // C++20 @@ -663,6 +672,7 @@ #include <__assert> // all public C++ headers provide the assertion handler #include <__chrono/calendar.h> #include <__chrono/convert_to_timespec.h> +#include <__chrono/convert_to_tm.h> #include <__chrono/day.h> #include <__chrono/duration.h> #include <__chrono/file_clock.h> @@ -686,6 +696,14 @@ // standard-mandated includes #include +#if !defined(_LIBCPP_HAS_NO_LOCALIZATION) && !defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT) && _LIBCPP_STD_VER > 17 +# include <__chrono/formatter.h> +# include <__chrono/ostream.h> +# include <__chrono/parser_std_format_spec.h> +# include <__chrono/statically_widen.h> +#endif + + #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) # pragma GCC system_header #endif diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in --- a/libcxx/include/module.modulemap.in +++ b/libcxx/include/module.modulemap.in @@ -492,29 +492,43 @@ export * module __chrono { - module calendar { private header "__chrono/calendar.h" } - module convert_to_timespec { private header "__chrono/convert_to_timespec.h" } - module day { private header "__chrono/day.h" } - module duration { private header "__chrono/duration.h" } - module file_clock { private header "__chrono/file_clock.h" } - module hh_mm_ss { private header "__chrono/hh_mm_ss.h" } - module high_resolution_clock { + module calendar { private header "__chrono/calendar.h" } + module convert_to_timespec { private header "__chrono/convert_to_timespec.h" } + module convert_to_tm { private header "__chrono/convert_to_tm.h" } + module day { private header "__chrono/day.h" } + module duration { private header "__chrono/duration.h" } + module file_clock { private header "__chrono/file_clock.h" } + module formatter { + @requires_LIBCXX_ENABLE_LOCALIZATION@ + private header "__chrono/formatter.h" + } + module hh_mm_ss { private header "__chrono/hh_mm_ss.h" } + module high_resolution_clock { private header "__chrono/high_resolution_clock.h" export steady_clock export system_clock } - module literals { private header "__chrono/literals.h" } - module month { private header "__chrono/month.h" } - module month_weekday { private header "__chrono/month_weekday.h" } - module monthday { private header "__chrono/monthday.h" } - module steady_clock { private header "__chrono/steady_clock.h" } - module system_clock { private header "__chrono/system_clock.h" } - module time_point { private header "__chrono/time_point.h" } - module weekday { private header "__chrono/weekday.h" } - module year { private header "__chrono/year.h" } - module year_month { private header "__chrono/year_month.h" } - module year_month_day { private header "__chrono/year_month_day.h" } - module year_month_weekday { private header "__chrono/year_month_weekday.h" } + module literals { private header "__chrono/literals.h" } + module month { private header "__chrono/month.h" } + module month_weekday { private header "__chrono/month_weekday.h" } + module monthday { private header "__chrono/monthday.h" } + module ostream { + @requires_LIBCXX_ENABLE_LOCALIZATION@ + private header "__chrono/ostream.h" + } + module parser_std_format_spec { + @requires_LIBCXX_ENABLE_LOCALIZATION@ + private header "__chrono/parser_std_format_spec.h" + } + module statically_widen { private header "__chrono/statically_widen.h" } + module steady_clock { private header "__chrono/steady_clock.h" } + module system_clock { private header "__chrono/system_clock.h" } + module time_point { private header "__chrono/time_point.h" } + module weekday { private header "__chrono/weekday.h" } + module year { private header "__chrono/year.h" } + module year_month { private header "__chrono/year_month.h" } + module year_month_day { private header "__chrono/year_month_day.h" } + module year_month_weekday { private header "__chrono/year_month_weekday.h" } } } module codecvt { diff --git a/libcxx/test/libcxx/private_headers.verify.cpp b/libcxx/test/libcxx/private_headers.verify.cpp --- a/libcxx/test/libcxx/private_headers.verify.cpp +++ b/libcxx/test/libcxx/private_headers.verify.cpp @@ -238,15 +238,20 @@ #include <__charconv/to_chars_result.h> // expected-error@*:* {{use of private header from outside its module: '__charconv/to_chars_result.h'}} #include <__chrono/calendar.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/calendar.h'}} #include <__chrono/convert_to_timespec.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/convert_to_timespec.h'}} +#include <__chrono/convert_to_tm.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/convert_to_tm.h'}} #include <__chrono/day.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/day.h'}} #include <__chrono/duration.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/duration.h'}} #include <__chrono/file_clock.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/file_clock.h'}} +#include <__chrono/formatter.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/formatter.h'}} #include <__chrono/hh_mm_ss.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/hh_mm_ss.h'}} #include <__chrono/high_resolution_clock.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/high_resolution_clock.h'}} #include <__chrono/literals.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/literals.h'}} #include <__chrono/month.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/month.h'}} #include <__chrono/month_weekday.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/month_weekday.h'}} #include <__chrono/monthday.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/monthday.h'}} +#include <__chrono/ostream.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/ostream.h'}} +#include <__chrono/parser_std_format_spec.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/parser_std_format_spec.h'}} +#include <__chrono/statically_widen.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/statically_widen.h'}} #include <__chrono/steady_clock.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/steady_clock.h'}} #include <__chrono/system_clock.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/system_clock.h'}} #include <__chrono/time_point.h> // expected-error@*:* {{use of private header from outside its module: '__chrono/time_point.h'}} diff --git a/libcxx/test/libcxx/transitive_includes/cxx20/expected.chrono b/libcxx/test/libcxx/transitive_includes/cxx20/expected.chrono --- a/libcxx/test/libcxx/transitive_includes/cxx20/expected.chrono +++ b/libcxx/test/libcxx/transitive_includes/cxx20/expected.chrono @@ -1,10 +1,51 @@ +algorithm +array +atomic +bit +bitset +cctype +cerrno +charconv climits cmath compare +concepts +cstdarg cstddef cstdint +cstdio +cstdlib +cstring ctime +cwchar +cwctype +exception +format +functional +initializer_list +ios +iosfwd +istream +iterator limits +locale +memory +mutex +new +optional +ostream ratio +sstream +stdexcept +streambuf +string +string_view +system_error +tuple type_traits +typeinfo +unordered_map +utility +variant +vector version diff --git a/libcxx/test/libcxx/transitive_includes/cxx2b/expected.chrono b/libcxx/test/libcxx/transitive_includes/cxx2b/expected.chrono --- a/libcxx/test/libcxx/transitive_includes/cxx2b/expected.chrono +++ b/libcxx/test/libcxx/transitive_includes/cxx2b/expected.chrono @@ -1,10 +1,44 @@ +array +atomic +bit +bitset +cctype +cerrno +charconv climits cmath compare +concepts +cstdarg cstddef cstdint +cstdio +cstdlib +cstring ctime +cwchar +cwctype +exception +format +initializer_list +ios +iosfwd +istream limits +locale +memory +mutex +new +optional +ostream ratio +sstream +stdexcept +streambuf +string +string_view +system_error +tuple type_traits +typeinfo version diff --git a/libcxx/test/libcxx/utilities/format/format.formatter/format.formatter.spec/formattable.compile.pass.cpp b/libcxx/test/libcxx/utilities/format/format.formatter/format.formatter.spec/formattable.compile.pass.cpp --- a/libcxx/test/libcxx/utilities/format/format.formatter/format.formatter.spec/formattable.compile.pass.cpp +++ b/libcxx/test/libcxx/utilities/format/format.formatter/format.formatter.spec/formattable.compile.pass.cpp @@ -122,6 +122,16 @@ // enabled. template void test_P1361() { +// The chrono formatters require localization support. +// [time.format]/7 +// If the chrono-specs is omitted, the chrono object is formatted as if by +// streaming it to std::ostringstream os with the formatting +// locale imbued and copying os.str() through the output iterator of the +// context with additional padding and adjustments as specified by the format +// specifiers. +// In libc++ std:::ostringstream requires localization support. +#ifndef TEST_HAS_NO_LOCALIZATION + assert_is_not_formattable(); assert_is_not_formattable, CharT>(); @@ -131,7 +141,7 @@ assert_is_not_formattable, CharT>(); assert_is_not_formattable, CharT>(); - assert_is_not_formattable(); + assert_is_formattable(); assert_is_not_formattable(); assert_is_not_formattable(); @@ -156,6 +166,8 @@ //assert_is_formattable(); //assert_is_formattable(); + +#endif // TEST_HAS_NO_LOCALIZATION } // Tests for P1636 Formatters for library types diff --git a/libcxx/test/std/time/time.cal/time.cal.day/time.cal.day.nonmembers/ostream.pass.cpp b/libcxx/test/std/time/time.cal/time.cal.day/time.cal.day.nonmembers/ostream.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/time/time.cal/time.cal.day/time.cal.day.nonmembers/ostream.pass.cpp @@ -0,0 +1,90 @@ +//===----------------------------------------------------------------------===// +// +// 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: libcpp-has-no-localization +// UNSUPPORTED: libcpp-has-no-incomplete-format + +// TODO FMT Investigate Windows issues. +// UNSUPPORTED msvc, target={{.+}}-windows-gnu + +// REQUIRES: locale.fr_FR.UTF-8 +// REQUIRES: locale.ja_JP.UTF-8 + +// +// class day; + +// template +// basic_ostream& +// operator<<(basic_ostream& os, const day& d); + +#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::day day) { + std::basic_stringstream sstr; + sstr << day; + return sstr.str(); +} + +template +static std::basic_string stream_fr_FR_locale(std::chrono::day day) { + std::basic_stringstream sstr; + const std::locale locale(LOCALE_fr_FR_UTF_8); + sstr.imbue(locale); + sstr << day; + return sstr.str(); +} + +template +static std::basic_string stream_ja_JP_locale(std::chrono::day day) { + std::basic_stringstream sstr; + const std::locale locale(LOCALE_ja_JP_UTF_8); + sstr.imbue(locale); + sstr << day; + return sstr.str(); +} + +template +static void test() { + using namespace std::literals::chrono_literals; + + assert(stream_c_locale(0d) == SV("00 is not a valid day")); + assert(stream_c_locale(1d) == SV("01")); + assert(stream_c_locale(31d) == SV("31")); + assert(stream_c_locale(255d) == SV("255 is not a valid day")); + + assert(stream_fr_FR_locale(0d) == SV("00 is not a valid day")); + assert(stream_fr_FR_locale(1d) == SV("01")); + assert(stream_fr_FR_locale(31d) == SV("31")); + assert(stream_fr_FR_locale(255d) == SV("255 is not a valid day")); + + assert(stream_ja_JP_locale(0d) == SV("00 is not a valid day")); + assert(stream_ja_JP_locale(1d) == SV("01")); + assert(stream_ja_JP_locale(31d) == SV("31")); + assert(stream_ja_JP_locale(255d) == SV("255 is not a valid day")); +} + +int main(int, char**) { + using namespace std::literals::chrono_literals; + + test(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#endif + + return 0; +} diff --git a/libcxx/test/std/time/time.syn/formatter.day.pass.cpp b/libcxx/test/std/time/time.syn/formatter.day.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/time/time.syn/formatter.day.pass.cpp @@ -0,0 +1,127 @@ +//===----------------------------------------------------------------------===// +// 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: libcpp-has-no-localization +// UNSUPPORTED: libcpp-has-no-incomplete-format + +// TODO FMT Investigate Windows issues. +// UNSUPPORTED: msvc, target={{.+}}-windows-gnu + +// REQUIRES: locale.fr_FR.UTF-8 +// REQUIRES: locale.ja_JP.UTF-8 + +// + +// template struct formatter; + +#include +#include + +#include +#include +#include +#include +#include + +#include "formatter_tests.h" +#include "make_string.h" +#include "platform_support.h" // locale name macros +#include "string_literal.h" +#include "test_macros.h" + +template +static void test_no_chrono_specs() { + using namespace std::literals::chrono_literals; + + // Valid day + check.template operator()<"{}">(SV("01"), 1d); + check.template operator()<"{:*^4}">(SV("*01*"), 1d); + check.template operator()<"{:*>3}">(SV("*01"), 1d); + + // Invalid day + check.template operator()<"{}">(SV("00 is not a valid day"), 0d); + check.template operator()<"{:*^23}">(SV("*00 is not a valid day*"), 0d); +} + +template +static void test_valid_values() { + using namespace std::literals::chrono_literals; + + constexpr string_literal fmt{"{:%%d='%d'%t%%Od='%Od'%t%%e='%e'%t%%Oe='%Oe'%n}"}; + constexpr string_literal lfmt{"{: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.template operator()(SV("%d='00'\t%Od='00'\t%e=' 0'\t%Oe=' 0'\n"), 0d); + check.template operator()(SV("%d='01'\t%Od='01'\t%e=' 1'\t%Oe=' 1'\n"), 1d); + check.template operator()(SV("%d='31'\t%Od='31'\t%e='31'\t%Oe='31'\n"), 31d); +#if defined(_AIX) + check.template operator()(SV("%d='55'\t%Od='55'\t%e='55'\t%Oe='55'\n"), 255d); +#else + check.template operator()(SV("%d='255'\t%Od='255'\t%e='255'\t%Oe='255'\n"), 255d); +#endif + + // Use the global locale (fr_FR) + check.template operator()(SV("%d='00'\t%Od='00'\t%e=' 0'\t%Oe=' 0'\n"), 0d); + check.template operator()(SV("%d='01'\t%Od='01'\t%e=' 1'\t%Oe=' 1'\n"), 1d); + check.template operator()(SV("%d='31'\t%Od='31'\t%e='31'\t%Oe='31'\n"), 31d); +#if defined(_AIX) + check.template operator()(SV("%d='55'\t%Od='55'\t%e='55'\t%Oe='55'\n"), 255d); +#else + check.template operator()(SV("%d='255'\t%Od='255'\t%e='255'\t%Oe='255'\n"), 255d); +#endif + + // Use supplied locale (ja_JP). This locale has a different alternate on some platforms. +#if defined(__APPLE__) || defined(_AIX) + lcheck.template operator()(loc, SV("%d='00'\t%Od='00'\t%e=' 0'\t%Oe=' 0'\n"), 0d); + lcheck.template operator()(loc, SV("%d='01'\t%Od='01'\t%e=' 1'\t%Oe=' 1'\n"), 1d); + lcheck.template operator()(loc, SV("%d='31'\t%Od='31'\t%e='31'\t%Oe='31'\n"), 31d); +# if defined(_AIX) + check.template operator()(SV("%d='55'\t%Od='55'\t%e='55'\t%Oe='55'\n"), 255d); +# else + check.template operator()(SV("%d='255'\t%Od='255'\t%e='255'\t%Oe='255'\n"), 255d); +# endif +#else // defined(__APPLE__) || defined(_AIX) + lcheck.template operator()(loc, SV("%d='00'\t%Od='〇'\t%e=' 0'\t%Oe='〇'\n"), 0d); + lcheck.template operator()(loc, SV("%d='01'\t%Od='一'\t%e=' 1'\t%Oe='一'\n"), 1d); + lcheck.template operator()(loc, SV("%d='31'\t%Od='三十一'\t%e='31'\t%Oe='三十一'\n"), 31d); + lcheck.template operator()(loc, SV("%d='255'\t%Od='255'\t%e='255'\t%Oe='255'\n"), 255d); +#endif // defined(__APPLE__) || defined(_AIX) + + std::locale::global(std::locale::classic()); +} + +template +static void test() { + using namespace std::literals::chrono_literals; + + test_no_chrono_specs(); + test_valid_values(); + check_invalid_types({SV("d"), SV("e"), SV("Od"), SV("Oe")}, 0d); + + check_exception("Expected '%' or '}' in the chrono format-string", SV("{:A"), 0d); + check_exception("The chrono-specs contains a '{'", SV("{:%%{"), 0d); + check_exception("End of input while parsing the modifier chrono conversion-spec", SV("{:%"), 0d); + check_exception("End of input while parsing the modifier E", SV("{:%E"), 0d); + check_exception("End of input while parsing the modifier O", SV("{:%O"), 0d); + + // Precision not allowed + check_exception("Expected '%' or '}' in the chrono format-string", SV("{:.3}"), 0d); +} + +int main(int, char**) { + test(); + +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#endif + + return 0; +} diff --git a/libcxx/test/std/time/time.syn/formatter_tests.h b/libcxx/test/std/time/time.syn/formatter_tests.h new file mode 100644 --- /dev/null +++ b/libcxx/test/std/time/time.syn/formatter_tests.h @@ -0,0 +1,269 @@ +//===----------------------------------------------------------------------===// +// 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 TEST_STD_TIME_TIME_SYN_FORMATTER_TESTS_H +#define TEST_STD_TIME_TIME_SYN_FORMATTER_TESTS_H + +#include "make_string.h" +#include "string_literal.h" + +#include +#include +#include +#include +#include + +#define STR(S) MAKE_STRING(CharT, S) +#define SV(S) MAKE_STRING_VIEW(CharT, S) + +#ifndef TEST_HAS_NO_WIDE_CHARACTERS +template +using format_context = std::conditional_t, std::format_context, std::wformat_context>; +#else +template +using format_context = std::format_context; +#endif + +inline constexpr auto check = []( + std::basic_string_view expected, const Args&... args) constexpr { + std::basic_string out = std::format(fmt.template sv(), args...); + if constexpr (std::same_as) + if (out != expected) + std::cerr << "\nFormat string " << fmt.template sv() << "\nExpected output " << expected + << "\nActual output " << out << '\n'; + assert(out == expected); +}; + +inline constexpr auto lcheck = []( + const std::locale& loc, std::basic_string_view expected, const Args&... args) constexpr { + std::basic_string out = std::format(loc, fmt.template sv(), args...); + if constexpr (std::same_as) + if (out != expected) + std::cerr << "\nFormat string " << fmt.template sv() << "\nExpected output " << expected + << "\nActual output " << out << '\n'; + assert(out == expected); +}; + +inline constexpr auto check_exception = + []( + [[maybe_unused]] std::string_view what, + [[maybe_unused]] std::basic_string_view fmt, + [[maybe_unused]] const Args&... args) { +#ifndef TEST_HAS_NO_EXCEPTIONS + try { + TEST_IGNORE_NODISCARD std::vformat(fmt, std::make_format_args>(args...)); + if constexpr (std::same_as) + std::cerr << "\nFormat string " << fmt << "\nDidn't throw an exception.\n"; + assert(false); + } catch (const std::format_error& e) { +# if defined(_LIBCPP_VERSION) + if constexpr (std::same_as) + if (e.what() != what) + std::cerr << "\nFormat string " << fmt << "\nExpected exception " << what << "\nActual exception " + << e.what() << '\n'; + assert(e.what() == what); +# endif + return; + } + assert(false); +#endif + }; + +template +void check_invalid_type(const std::set>& valid_types, + std::string_view what, + std::basic_string type, + const T& arg) { + std::basic_string fmt{STR("{:%") + type + STR("}")}; + + if (valid_types.contains(std::basic_string_view{type})) { +#ifndef TEST_HAS_NO_EXCEPTIONS + try { +#endif + TEST_IGNORE_NODISCARD std::vformat( + std::basic_string_view{fmt}, std::make_format_args>(arg)); +#ifndef TEST_HAS_NO_EXCEPTIONS + } catch (const std::format_error& e) { +# if defined(_LIBCPP_VERSION) + if constexpr (std::same_as) + std::cerr << "\nFormat string " << fmt << "\nUnexpected exception " << e.what() << '\n'; +# endif + assert(false); + } +#endif + } else { + check_exception(what, std::basic_string_view{fmt}, arg); + } +} + +template +void check_invalid_types(const std::set>& valid_types, const T& arg) { + check_invalid_type(valid_types, "The supplied date time doesn't contain a weekday", STR("a"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a weekday", STR("A"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a month", STR("b"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a month", STR("B"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a date and time", STR("c"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a year", STR("C"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a day", STR("d"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a date", STR("D"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a day", STR("e"), arg); + // E - the modifier is checked separately + check_invalid_type(valid_types, "The date time type specifier is invalid", STR("f"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a date", STR("F"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a date", STR("g"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a date", STR("G"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a month", STR("h"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain an hour", STR("H"), arg); + check_invalid_type(valid_types, "The date time type specifier is invalid", STR("i"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain an hour", STR("I"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a date or duration", STR("j"), arg); + check_invalid_type(valid_types, "The date time type specifier is invalid", STR("J"), arg); + check_invalid_type(valid_types, "The date time type specifier is invalid", STR("k"), arg); + check_invalid_type(valid_types, "The date time type specifier is invalid", STR("K"), arg); + check_invalid_type(valid_types, "The date time type specifier is invalid", STR("l"), arg); + check_invalid_type(valid_types, "The date time type specifier is invalid", STR("L"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a month", STR("m"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a minute", STR("M"), arg); + // n - valid + check_invalid_type(valid_types, "The date time type specifier is invalid", STR("N"), arg); + check_invalid_type(valid_types, "The date time type specifier is invalid", STR("o"), arg); + // O - the modifier is checked separately + check_invalid_type(valid_types, "The supplied date time doesn't contain an hour", STR("p"), arg); + check_invalid_type(valid_types, "The date time type specifier is invalid", STR("P"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a duration", STR("q"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a duration", STR("Q"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a time", STR("r"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a time", STR("R"), arg); + check_invalid_type(valid_types, "The date time type specifier is invalid", STR("s"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a second", STR("S"), arg); + // t - valid + check_invalid_type(valid_types, "The supplied date time doesn't contain a time", STR("T"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a weekday", STR("u"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a date", STR("U"), arg); + check_invalid_type(valid_types, "The date time type specifier is invalid", STR("v"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a date", STR("V"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a weekday", STR("w"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a date", STR("W"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a date", STR("x"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a time", STR("X"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a year", STR("y"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a year", STR("y"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a time zone", STR("z"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a time zone", STR("Z"), arg); + + // *** E modifier + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("Ea"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("EA"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("Eb"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("EB"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a date and time", STR("Ec"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a year", STR("EC"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("Ed"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("ED"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("Ee"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("EE"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("Ef"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("EF"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("Eg"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("EG"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("Eh"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("EH"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("Ei"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("EI"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("Ej"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("EJ"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("Ek"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("EK"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("El"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("EL"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("Em"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("EM"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("En"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("EN"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("Eo"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("EO"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("Ep"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("EP"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("Eq"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("EQ"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("Er"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("ER"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("Es"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("ES"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("Et"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("ET"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("Eu"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("EU"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("Ev"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("EV"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("Ew"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("EW"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a date", STR("Ex"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a time", STR("EX"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a year", STR("Ey"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a year", STR("EY"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a time zone", STR("Ez"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("EZ"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier E is invalid", STR("E%"), arg); + + // *** O modifier + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("Oa"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("OA"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("Ob"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("OB"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("Oc"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("OC"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a day", STR("Od"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("OD"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a day", STR("Oe"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("OE"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("Of"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("OF"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("Og"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("OG"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("Oh"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain an hour", STR("OH"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("Oi"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain an hour", STR("OI"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("Oj"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("OJ"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("Ok"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("OK"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("Ol"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("OL"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a month", STR("Om"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a minute", STR("OM"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("On"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("ON"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("Oo"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("OO"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("Op"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("OP"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("Oq"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("OQ"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("Or"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("OR"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("Os"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a second", STR("OS"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("Ot"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("OT"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a weekday", STR("Ou"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a date", STR("OU"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("Ov"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a date", STR("OV"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a weekday", STR("Ow"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a date", STR("OW"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("Ox"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("OX"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a year", STR("Oy"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("OY"), arg); + check_invalid_type(valid_types, "The supplied date time doesn't contain a time zone", STR("Oz"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("OZ"), arg); + check_invalid_type(valid_types, "The date time type specifier for modifier O is invalid", STR("O%"), arg); +} + +#endif // TEST_STD_TIME_TIME_SYN_FORMATTER_TESTS_H diff --git a/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/types.compile.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/types.compile.pass.cpp --- a/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/types.compile.pass.cpp +++ b/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/types.compile.pass.cpp @@ -164,6 +164,16 @@ // enabled. template void test_P1361() { +// The chrono formatters require localization support. +// [time.format]/7 +// If the chrono-specs is omitted, the chrono object is formatted as if by +// streaming it to std::ostringstream os with the formatting +// locale imbued and copying os.str() through the output iterator of the +// context with additional padding and adjustments as specified by the format +// specifiers. +// In libc++ std:::ostringstream requires localization support. +#ifndef TEST_HAS_NO_LOCALIZATION + assert_formatter_is_disabled(); assert_formatter_is_disabled, CharT>(); @@ -173,7 +183,7 @@ assert_formatter_is_disabled, CharT>(); assert_formatter_is_disabled, CharT>(); - assert_formatter_is_disabled(); + assert_formatter_is_enabled(); assert_formatter_is_disabled(); assert_formatter_is_disabled(); @@ -198,6 +208,8 @@ //assert_formatter_is_enabled(); //assert_formatter_is_enabled(); + +#endif // TEST_HAS_NO_LOCALIZATION } // Tests for P1636 Formatters for library types diff --git a/libcxx/utils/ci/run-buildbot b/libcxx/utils/ci/run-buildbot --- a/libcxx/utils/ci/run-buildbot +++ b/libcxx/utils/ci/run-buildbot @@ -187,10 +187,12 @@ # Depends on LC_COLLATE set at the top of this script. ! grep -rn '[^ -~]' libcxx/include libcxx/src libcxx/test libcxx/benchmarks \ --exclude '*.dat' \ - --exclude 'std_format_spec_string_unicode.bench.cpp' \ + --exclude 'format_tests.h' \ + --exclude 'formatter.day.pass.cpp' \ --exclude 'grep.pass.cpp' \ --exclude 'locale-specific_form.pass.cpp' \ - --exclude 'format_tests.h' || false + --exclude 'std_format_spec_string_unicode.bench.cpp' \ + || false # Reject code with trailing whitespace ! grep -rn '[[:blank:]]$' libcxx/include libcxx/src libcxx/test libcxx/benchmarks || false