https://gcc.gnu.org/g:dbf94a19f29ae4733fd788d295ab3bc2a8652258
commit r16-4074-gdbf94a19f29ae4733fd788d295ab3bc2a8652258 Author: Tomasz Kamiński <tkami...@redhat.com> Date: Tue Sep 23 13:56:42 2025 +0200 libstdc++: Reflect operator<< constraints in formatter for local_time. 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<sys_time>::__defSpec. (formatter<chrono::sys_time<_Duration>, _CharT>::parse): Simplify condition in if contexpr. (formatter<chrono::sys_time<_Duration>, _CharT>::__stream_insertable): Remove. (formatter<chrono::sys_time<_Duration>, _CharT>::__defSpec) (formatter<chrono::local_time<_Duration>, _CharT>::__defSpec): Compute using __formatter_duration::_S_spec_for_tp. (forrmatter<chrono::sys_time<_Duration>, _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 <jwak...@redhat.com> Signed-off-by: Tomasz Kamiński <tkami...@redhat.com> Diff: --- 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 0a6a3a5ce5a2..79a44d128b12 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<typename _Duration> + 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<typename _Out> @@ -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<typename _Out> @@ -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 ef1b19d688c7..a20c074018e8 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<Dur2>& tp) using decadays = duration<days::rep, std::ratio_multiply<std::deca, days::period>>; using kilodays = duration<days::rep, std::ratio_multiply<std::kilo, days::period>>; -template<typename CharT, typename Clock> +template<typename CharT, typename Clock, bool CustomizedOstream> void -test_time_point(bool daysAsTime) +test_time_point() { std::basic_string<CharT> res; const auto lt = local_days(2024y/March/22) + 13h + 24min + 54s + 111222333ns; - auto strip_time = [daysAsTime](std::basic_string_view<CharT> sv) - { return daysAsTime ? sv : sv.substr(0, 10); }; + auto strip_time = [](std::basic_string_view<CharT> sv) + { return CustomizedOstream ? sv.substr(0, 10) : sv; }; verify( wall_cast<Clock, nanoseconds>(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<Clock, kilodays>(lt), strip_time(WIDEN("2022-01-08 00:00:00")) ); + + if constexpr (!CustomizedOstream) + { + verify( wall_cast<Clock, duration<double>>(lt), + WIDEN("2024-03-22 13:24:54") ); + verify( wall_cast<Clock, years>(lt), + WIDEN("2024-01-01 02:16:48") ); + } + else + { + test_no_empty_spec<CharT, time_point<Clock, duration<double>>>(); + test_no_empty_spec<CharT, time_point<Clock, years>>(); + } } template<typename CharT> @@ -776,20 +789,18 @@ template<typename CharT> void test_time_points() { - test_time_point<CharT, local_t>(false); - test_time_point<CharT, system_clock>(false); - test_time_point<CharT, utc_clock>(true); - test_time_point<CharT, tai_clock>(true); - test_time_point<CharT, gps_clock>(true); - test_time_point<CharT, file_clock>(true); + test_time_point<CharT, local_t, true>(); + test_time_point<CharT, system_clock, true>(); + test_time_point<CharT, utc_clock, false>(); + test_time_point<CharT, tai_clock, false>(); + test_time_point<CharT, gps_clock, false>(); + test_time_point<CharT, file_clock, false>(); test_leap_second<CharT>(); #if _GLIBCXX_USE_CXX11_ABI || !_GLIBCXX_USE_DUAL_ABI test_zoned_time<CharT>(); #endif test_local_time_format<CharT>(); - test_no_empty_spec<CharT, sys_time<years>>(); - test_no_empty_spec<CharT, sys_time<duration<float>>>(); } #if _GLIBCXX_USE_CXX11_ABI || !_GLIBCXX_USE_DUAL_ABI