diff options
author | Jonathan Wakely <jwakely@redhat.com> | 2024-01-21 18:16:14 +0000 |
---|---|---|
committer | Jonathan Wakely <jwakely@redhat.com> | 2024-01-21 22:15:06 +0000 |
commit | 7431fcea6b72beb54abb1932c254ac0e76bd0bde (patch) | |
tree | 8225909fd4326ec917fb9c71c95720a7f7d7c507 /libstdc++-v3/include | |
parent | fba15da8fa518bfb8d3e061795bc3ca2dea01d27 (diff) | |
download | gcc-7431fcea6b72beb54abb1932c254ac0e76bd0bde.zip gcc-7431fcea6b72beb54abb1932c254ac0e76bd0bde.tar.gz gcc-7431fcea6b72beb54abb1932c254ac0e76bd0bde.tar.bz2 |
libstdc++: Fix std::format for floating-point chrono::time_point [PR113500]
Currently trying to use std::format with certain specializations of
std::chrono::time_point is ill-formed, due to one member function of the
__formatter_chrono type which tries to write a time_point to an ostream.
For sys_time<floating-point> or sys_time with a period greater than days
there is no operator<< that can be used.
That operator<< is only needed when using an empty chrono-specs in the
format string, like "{}", but the ill-formed expression gives an error
even if not actually used. This means it's not possible to format some
other specializations of chrono::time_point, even when using a non-empty
chrono-specs.
This fixes it by avoiding using 'os << t' for all chrono::time_point
specializations, and instead using std::format("{:L%F %T}", t). So that
we continue to reject std::format("{}", sys_time{1.0s}) a check for
empty chrono-specs is added to the formatter<sys_time<D>, C>
specialization.
While testing this I noticed that the output for %S with a
floating-point duration was incorrect, as the subseconds part was being
appended to the seconds without a decimal point, and without the correct
number of leading zeros.
libstdc++-v3/ChangeLog:
PR libstdc++/113500
* include/bits/chrono_io.h (__formatter_chrono::_M_S): Fix
printing of subseconds with floating-point rep.
(__formatter_chrono::_M_format_to_ostream): Do not write
time_point specializations directly to the ostream.
(formatter<chrono::sys_time<D>, C>::parse): Do not allow an
empty chrono-spec if the type fails to meet the constraints for
writing to an ostream with operator<<.
* testsuite/std/time/clock/file/io.cc: Check formatting
non-integral times with empty chrono-specs.
* testsuite/std/time/clock/gps/io.cc: Likewise.
* testsuite/std/time/clock/utc/io.cc: Likewise.
* testsuite/std/time/hh_mm_ss/io.cc: Likewise.
Diffstat (limited to 'libstdc++-v3/include')
-rw-r--r-- | libstdc++-v3/include/bits/chrono_io.h | 71 |
1 files changed, 51 insertions, 20 deletions
diff --git a/libstdc++-v3/include/bits/chrono_io.h b/libstdc++-v3/include/bits/chrono_io.h index 7b5876b..82f2d39e 100644 --- a/libstdc++-v3/include/bits/chrono_io.h +++ b/libstdc++-v3/include/bits/chrono_io.h @@ -681,6 +681,7 @@ namespace __format return __fc.locale(); } + // Format for empty chrono-specs, e.g. "{}" (C++20 [time.format] p6). // TODO: consider moving body of every operator<< into this function // and use std::format("{}", t) to implement those operators. That // would avoid std::format("{}", t) calling operator<< which calls @@ -702,6 +703,22 @@ namespace __format if constexpr (__is_specialization_of<_Tp, __utc_leap_second>) __os << __t._M_date << ' ' << __t._M_time; + else if constexpr (chrono::__is_time_point_v<_Tp>) + { + // Need to be careful here because not all specializations + // of chrono::sys_time can be written to an ostream. + // For the specializations of time_point that can be + // formatted with an empty chrono-specs, either it's a + // sys_time with period greater or equal to days: + if constexpr (is_convertible_v<_Tp, chrono::sys_days>) + __os << _S_date(__t); + else // Or it's formatted as "{:L%F %T}": + { + auto __days = chrono::floor<chrono::days>(__t); + __os << chrono::year_month_day(__days) << ' ' + << chrono::hh_mm_ss(__t - __days); + } + } else { if constexpr (chrono::__is_duration_v<_Tp>) @@ -1122,39 +1139,43 @@ namespace __format 'S', 'O'); } - __out = __format::__write(std::move(__out), - _S_two_digits(__hms.seconds().count())); - if constexpr (__hms.fractional_width != 0) + if constexpr (__hms.fractional_width == 0) + __out = __format::__write(std::move(__out), + _S_two_digits(__hms.seconds().count())); + else { locale __loc = _M_locale(__ctx); + auto __s = __hms.seconds(); auto __ss = __hms.subseconds(); using rep = typename decltype(__ss)::rep; if constexpr (is_floating_point_v<rep>) { + chrono::duration<rep> __fs = __s + __ss; __out = std::format_to(std::move(__out), __loc, - _GLIBCXX_WIDEN("{:.{}Lg}"), - __ss.count(), - __hms.fractional_width); - } - else if constexpr (is_integral_v<rep>) - { - const auto& __np - = use_facet<numpunct<_CharT>>(__loc); - __out = std::format_to(std::move(__out), - _GLIBCXX_WIDEN("{}{:0{}}"), - __np.decimal_point(), - __ss.count(), + _GLIBCXX_WIDEN("{:#0{}.{}Lf}"), + __fs.count(), + 3 + __hms.fractional_width, __hms.fractional_width); } else { const auto& __np = use_facet<numpunct<_CharT>>(__loc); + __out = __format::__write(std::move(__out), + _S_two_digits(__s.count())); *__out++ = __np.decimal_point(); - auto __str = std::format(_S_empty_spec, __ss.count()); - __out = std::format_to(_GLIBCXX_WIDEN("{:0>{}s}"), - __str, - __hms.fractional_width); + if constexpr (is_integral_v<rep>) + __out = std::format_to(std::move(__out), + _GLIBCXX_WIDEN("{:0{}}"), + __ss.count(), + __hms.fractional_width); + else + { + auto __str = std::format(_S_empty_spec, __ss.count()); + __out = std::format_to(_GLIBCXX_WIDEN("{:0>{}s}"), + __str, + __hms.fractional_width); + } } } return __out; @@ -1911,7 +1932,13 @@ namespace __format template<typename _ParseContext> constexpr typename _ParseContext::iterator parse(_ParseContext& __pc) - { return _M_f._M_parse(__pc, __format::_ZonedDateTime); } + { + auto __next = _M_f._M_parse(__pc, __format::_ZonedDateTime); + if constexpr (!__stream_insertable) + if (_M_f._M_spec._M_chrono_specs.empty()) + __format::__invalid_chrono_spec(); // chrono-specs can't be empty + return __next; + } template<typename _FormatContext> typename _FormatContext::iterator @@ -1920,6 +1947,10 @@ namespace __format { return _M_f._M_format(__t, __fc); } private: + static constexpr bool __stream_insertable + = requires (basic_ostream<_CharT>& __os, + chrono::sys_time<_Duration> __t) { __os << __t; }; + __format::__formatter_chrono<_CharT> _M_f; }; |