From dbf94a19f29ae4733fd788d295ab3bc2a8652258 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tomasz=20Kami=C5=84ski?= Date: Tue, 23 Sep 2025 13:56:42 +0200 Subject: [PATCH] libstdc++: Reflect operator<< constraints in formatter for local_time. MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit The r16-3996-gdc78d691c5e5f7 commit (resolution of LWG4257) constrained the operator<< for local_time, but didn't update the corresponding formatter. This meant it didn't conditionally support formatting with an empty format spec ("{}"), which is defined in terms of operator<<. This patch addresses that by initializing __defSpec for the local_time formatter in the same manner as it's done for sys_time. This functionality is extracted to the _S_spec_for_tp function of __formatter_duration. As formatting of local_time is defined and constrained in terms of operator<< for sys_time, we can check the viability of the ostream operator for sys_time in both cases. As default _M_chrono_spec may now be empty for local_time, the parse method now checks if it was supplied in the format string, similarly to sys_time. The condition for performing runtime check is expressed directly by checking if a empty default is provided. This avoids the need to access the value of __stream_insertable outside of the __defSpec computation. As a note, despite their similar behavior, formatters sys_time and local_time cannot be easily defined in terms of each other, as sys_time provides time zone information while local_time does not. libstdc++-v3/ChangeLog: * include/bits/chrono_io.h (__formatter_duration::_S_spec_for_tp): Extracted from defition of formatter::__defSpec. (formatter, _CharT>::parse): Simplify condition in if contexpr. (formatter, _CharT>::__stream_insertable): Remove. (formatter, _CharT>::__defSpec) (formatter, _CharT>::__defSpec): Compute using __formatter_duration::_S_spec_for_tp. (forrmatter, _CharT>::parse): Check if parse _M_chrono_spec * testsuite/std/time/format/empty_spec.cc: Extend tests for floating point and other non-streamable durations (years). Reviewed-by: Jonathan Wakely Signed-off-by: Tomasz Kamiński --- libstdc++-v3/include/bits/chrono_io.h | 59 ++++++++++--------- .../testsuite/std/time/format/empty_spec.cc | 35 +++++++---- 2 files changed, 54 insertions(+), 40 deletions(-) diff --git a/libstdc++-v3/include/bits/chrono_io.h b/libstdc++-v3/include/bits/chrono_io.h index 0a6a3a5ce5a..79a44d128b1 100644 --- a/libstdc++-v3/include/bits/chrono_io.h +++ b/libstdc++-v3/include/bits/chrono_io.h @@ -1929,6 +1929,24 @@ namespace __format return __res; }; + template + static consteval + _ChronoSpec<_CharT> + _S_spec_for_tp() + { + using enum _ChronoParts; + // streaming of local_time is defined in terms of sys_time + constexpr bool __stream_insertable = + requires (basic_ostream<_CharT>& __os, chrono::sys_time<_Duration> __t) + { __os << __t; }; + if constexpr (!__stream_insertable) + return _S_spec_for<_Duration>(_None); + else if constexpr (is_convertible_v<_Duration, chrono::days>) + return _S_spec_for<_Duration>(_Date); + else + return _S_spec_for<_Duration>(_DateTime); + } + using __formatter_chrono<_CharT>::__formatter_chrono; using __formatter_chrono<_CharT>::_M_spec; @@ -2912,12 +2930,12 @@ namespace __format parse(basic_format_parse_context<_CharT>& __pc) { using enum __format::_ChronoParts; - auto __next + auto __res = _M_f.template _M_parse<_Duration>(__pc, _ZonedDateTime, __defSpec); - if constexpr (!__stream_insertable) + if constexpr (__defSpec._M_chrono_specs.empty()) if (_M_f._M_spec._M_chrono_specs.empty()) __format::__invalid_chrono_spec(); // chrono-specs can't be empty - return __next; + return __res; } template @@ -2935,21 +2953,8 @@ namespace __format } private: - static constexpr bool __stream_insertable - = requires (basic_ostream<_CharT>& __os, - chrono::sys_time<_Duration> __t) { __os << __t; }; - - static constexpr __format::_ChronoSpec<_CharT> __defSpec = [] - { - using enum __format::_ChronoParts; - __format::_ChronoParts __needed = _DateTime; - if constexpr (!__stream_insertable) - __needed = _None; - else if constexpr (is_convertible_v<_Duration, chrono::days>) - __needed = _Date; - return __format::__formatter_duration<_CharT>:: - template _S_spec_for<_Duration>(__needed); - }(); + static constexpr __format::_ChronoSpec<_CharT> __defSpec = + __format::__formatter_duration<_CharT>::template _S_spec_for_tp<_Duration>(); __format::__formatter_duration<_CharT> _M_f{__defSpec}; }; @@ -3110,7 +3115,12 @@ namespace __format parse(basic_format_parse_context<_CharT>& __pc) { using enum __format::_ChronoParts; - return _M_f.template _M_parse<_Duration>(__pc, _DateTime, __defSpec); + auto __res + = _M_f.template _M_parse<_Duration>(__pc, _DateTime, __defSpec); + if constexpr (__defSpec._M_chrono_specs.empty()) + if (_M_f._M_spec._M_chrono_specs.empty()) + __format::__invalid_chrono_spec(); // chrono-specs can't be empty + return __res; } template @@ -3126,15 +3136,8 @@ namespace __format } private: - static constexpr __format::_ChronoSpec<_CharT> __defSpec = [] - { - using enum __format::_ChronoParts; - __format::_ChronoParts __needed = _DateTime; - if constexpr (is_convertible_v<_Duration, chrono::days>) - __needed = _Date; - return __format::__formatter_duration<_CharT>:: - template _S_spec_for<_Duration>(__needed); - }(); + static constexpr __format::_ChronoSpec<_CharT> __defSpec = + __format::__formatter_duration<_CharT>::template _S_spec_for_tp<_Duration>(); __format::__formatter_duration<_CharT> _M_f{__defSpec}; }; diff --git a/libstdc++-v3/testsuite/std/time/format/empty_spec.cc b/libstdc++-v3/testsuite/std/time/format/empty_spec.cc index ef1b19d688c..a20c074018e 100644 --- a/libstdc++-v3/testsuite/std/time/format/empty_spec.cc +++ b/libstdc++-v3/testsuite/std/time/format/empty_spec.cc @@ -653,15 +653,15 @@ wall_cast(const local_time& tp) using decadays = duration>; using kilodays = duration>; -template +template void -test_time_point(bool daysAsTime) +test_time_point() { std::basic_string res; const auto lt = local_days(2024y/March/22) + 13h + 24min + 54s + 111222333ns; - auto strip_time = [daysAsTime](std::basic_string_view sv) - { return daysAsTime ? sv : sv.substr(0, 10); }; + auto strip_time = [](std::basic_string_view sv) + { return CustomizedOstream ? sv.substr(0, 10) : sv; }; verify( wall_cast(lt), WIDEN("2024-03-22 13:24:54.111222333") ); @@ -681,6 +681,19 @@ test_time_point(bool daysAsTime) strip_time(WIDEN("2024-03-18 00:00:00")) ); verify( wall_cast(lt), strip_time(WIDEN("2022-01-08 00:00:00")) ); + + if constexpr (!CustomizedOstream) + { + verify( wall_cast>(lt), + WIDEN("2024-03-22 13:24:54") ); + verify( wall_cast(lt), + WIDEN("2024-01-01 02:16:48") ); + } + else + { + test_no_empty_spec>>(); + test_no_empty_spec>(); + } } template @@ -776,20 +789,18 @@ template void test_time_points() { - test_time_point(false); - test_time_point(false); - test_time_point(true); - test_time_point(true); - test_time_point(true); - test_time_point(true); + test_time_point(); + test_time_point(); + test_time_point(); + test_time_point(); + test_time_point(); + test_time_point(); test_leap_second(); #if _GLIBCXX_USE_CXX11_ABI || !_GLIBCXX_USE_DUAL_ABI test_zoned_time(); #endif test_local_time_format(); - test_no_empty_spec>(); - test_no_empty_spec>>(); } #if _GLIBCXX_USE_CXX11_ABI || !_GLIBCXX_USE_DUAL_ABI -- 2.47.3