Similarly to issue reported for %c in PR117214, the format string for locale specific time (%r, %X) and date (%x) representations may contain specifiers not accepted by chrono-spec, leading to exception being thrown. This happened for following conversion specifier and locale combinations: * %r, %X for aa_DJ.UTF-8, ar_SA.UTF-8 * %x for ca_AD.UTF-8, my_MM.UTF-8
This fix follows approach from r15-8490-gc24a1d5, and uses time_put to emit localized date format. The existing _M_c is reworked to handle all locale dependent conversion specifies, by accepting them as argument. It is also renamed to _M_c_r_x_X. PR libstdc++/120648 libstdc++-v3/ChangeLog: * include/bits/chrono_io.h (__formatter_chrono::_M_format_to): Handle %c, %r, %x and %X by passing them to _M_c_r_x_X. (__formatter_chrono::_M_c_r_x_X): Reworked from _M_c. (__formatter_chrono::_M_c): Renamed into above. (__formatter_chrono::_M_r, __formatter_chrono::_M_x) (__formatter_chrono::_M_X): Removed. * testsuite/std/time/format/pr117214.cc: New tests for %r, %x, %X with date, time and durations. --- Testing on x86_64-linux. Alll std/time* tests passed. OK for trunk if all tests passes? libstdc++-v3/include/bits/chrono_io.h | 128 +++++------------- .../testsuite/std/time/format/pr117214.cc | 43 +++++- 2 files changed, 72 insertions(+), 99 deletions(-) diff --git a/libstdc++-v3/include/bits/chrono_io.h b/libstdc++-v3/include/bits/chrono_io.h index 81ec4414f48..abbf4efcc3b 100644 --- a/libstdc++-v3/include/bits/chrono_io.h +++ b/libstdc++-v3/include/bits/chrono_io.h @@ -688,7 +688,10 @@ namespace __format __out = _M_b_B(__t, std::move(__out), __fc, __c == 'B'); break; case 'c': - __out = _M_c(__t, std::move(__out), __fc, __mod == 'E'); + case 'r': + case 'x': + case 'X': + __out = _M_c_r_x_X(__t, std::move(__out), __fc, __c, __mod); break; case 'C': case 'y': @@ -739,9 +742,6 @@ namespace __format __throw_format_error("chrono format error: argument is " "not a duration"); break; - case 'r': - __out = _M_r(__t, __print_sign(), __fc); - break; case 'R': case 'T': __out = _M_R_T(__t, __print_sign(), __fc, __c == 'T'); @@ -759,12 +759,6 @@ namespace __format __out = _M_U_V_W(__t, std::move(__out), __fc, __c, __mod == 'O'); break; - case 'x': - __out = _M_x(__t, std::move(__out), __fc, __mod == 'E'); - break; - case 'X': - __out = _M_X(__t, __print_sign(), __fc, __mod == 'E'); - break; case 'z': __out = _M_z(__t, std::move(__out), __fc, (bool)__mod); break; @@ -954,11 +948,16 @@ namespace __format template<typename _Tp, typename _FormatContext> typename _FormatContext::iterator - _M_c(const _Tp& __t, typename _FormatContext::iterator __out, - _FormatContext& __ctx, bool __mod = false) const + _M_c_r_x_X(const _Tp& __t, typename _FormatContext::iterator __out, + _FormatContext& __ctx, _CharT __conv, _CharT __mod) const { // %c Locale's date and time representation. // %Ec Locale's alternate date and time representation. + // %r Locale's 12-hour clock time. + // %x Locale's date rep + // %Ex Locale's alternative date representation. + // %X Locale's time rep + // %EX Locale's alternative time representation. using namespace chrono; using ::std::chrono::__detail::__utc_leap_second; @@ -992,23 +991,30 @@ namespace __format __tm.tm_zone = const_cast<char*>("UTC"); #endif - auto __d = _S_days(__t); // Either sys_days or local_days. - using _TDays = decltype(__d); - const year_month_day __ymd(__d); - const auto __y = __ymd.year(); - const auto __hms = _S_hms(__t); + if (__conv == 'c' || __conv == 'x') + { + auto __d = _S_days(__t); // Either sys_days or local_days. + using _TDays = decltype(__d); + const year_month_day __ymd(__d); + const auto __y = __ymd.year(); + + __tm.tm_year = (int)__y - 1900; + __tm.tm_yday = (__d - _TDays(__y/January/1)).count(); + __tm.tm_mon = (unsigned)__ymd.month() - 1; + __tm.tm_mday = (unsigned)__ymd.day(); + __tm.tm_wday = weekday(__d).c_encoding(); + } + + if (__conv != 'x') + { + const auto __hms = _S_hms(__t); + __tm.tm_hour = __hms.hours().count(); + __tm.tm_min = __hms.minutes().count(); + __tm.tm_sec = __hms.seconds().count(); + } - __tm.tm_year = (int)__y - 1900; - __tm.tm_yday = (__d - _TDays(__y/January/1)).count(); - __tm.tm_mon = (unsigned)__ymd.month() - 1; - __tm.tm_mday = (unsigned)__ymd.day(); - __tm.tm_wday = weekday(__d).c_encoding(); - __tm.tm_hour = __hms.hours().count(); - __tm.tm_min = __hms.minutes().count(); - __tm.tm_sec = __hms.seconds().count(); - - return _M_locale_fmt(std::move(__out), _M_locale(__ctx), __tm, 'c', - __mod ? 'E' : '\0'); + return _M_locale_fmt(std::move(__out), _M_locale(__ctx), __tm, + __conv, __mod); } template<typename _Tp, typename _FormatContext> @@ -1347,25 +1353,6 @@ namespace __format // %Q handled in _M_format - template<typename _Tp, typename _FormatContext> - typename _FormatContext::iterator - _M_r(const _Tp& __tt, typename _FormatContext::iterator __out, - _FormatContext& __ctx) const - { - // %r locale's 12-hour clock time. - auto __t = _S_floor_seconds(__tt); - locale __loc = _M_locale(__ctx); - const auto& __tp = use_facet<__timepunct<_CharT>>(__loc); - const _CharT* __ampm_fmt; - __tp._M_am_pm_format(&__ampm_fmt); - basic_string<_CharT> __fmt(_S_empty_spec); - __fmt.insert(1u, 1u, _S_colon); - __fmt.insert(2u, __ampm_fmt); - using _FmtStr = _Runtime_format_string<_CharT>; - return _M_write(std::move(__out), __loc, - std::format(__loc, _FmtStr(__fmt), __t)); - } - template<typename _Tp, typename _FormatContext> typename _FormatContext::iterator _M_R_T(const _Tp& __t, typename _FormatContext::iterator __out, @@ -1545,53 +1532,6 @@ namespace __format return __format::__write(std::move(__out), __sv); } - template<typename _Tp, typename _FormatContext> - typename _FormatContext::iterator - _M_x(const _Tp& __t, typename _FormatContext::iterator __out, - _FormatContext& __ctx, bool __mod = false) const - { - // %x Locale's date rep - // %Ex Locale's alternative date representation. - locale __loc = _M_locale(__ctx); - const auto& __tp = use_facet<__timepunct<_CharT>>(__loc); - const _CharT* __date_reps[2]; - __tp._M_date_formats(__date_reps); - const _CharT* __rep = __date_reps[__mod]; - if (!*__rep) - return _M_D(__t, std::move(__out), __ctx); - - basic_string<_CharT> __fmt(_S_empty_spec); - __fmt.insert(1u, 1u, _S_colon); - __fmt.insert(2u, __rep); - using _FmtStr = _Runtime_format_string<_CharT>; - return _M_write(std::move(__out), __loc, - std::format(__loc, _FmtStr(__fmt), __t)); - } - - template<typename _Tp, typename _FormatContext> - typename _FormatContext::iterator - _M_X(const _Tp& __tt, typename _FormatContext::iterator __out, - _FormatContext& __ctx, bool __mod = false) const - { - // %X Locale's time rep - // %EX Locale's alternative time representation. - auto __t = _S_floor_seconds(__tt); - locale __loc = _M_locale(__ctx); - const auto& __tp = use_facet<__timepunct<_CharT>>(__loc); - const _CharT* __time_reps[2]; - __tp._M_time_formats(__time_reps); - const _CharT* __rep = __time_reps[__mod]; - if (!*__rep) - return _M_R_T(__t, std::move(__out), __ctx, true); - - basic_string<_CharT> __fmt(_S_empty_spec); - __fmt.insert(1u, 1u, _S_colon); - __fmt.insert(2u, __rep); - using _FmtStr = _Runtime_format_string<_CharT>; - return _M_write(std::move(__out), __loc, - std::format(__loc, _FmtStr(__fmt), __t)); - } - template<typename _Tp, typename _FormatContext> typename _FormatContext::iterator _M_z(const _Tp& __t, typename _FormatContext::iterator __out, diff --git a/libstdc++-v3/testsuite/std/time/format/pr117214.cc b/libstdc++-v3/testsuite/std/time/format/pr117214.cc index 87c703dd255..79109c39151 100644 --- a/libstdc++-v3/testsuite/std/time/format/pr117214.cc +++ b/libstdc++-v3/testsuite/std/time/format/pr117214.cc @@ -7,10 +7,14 @@ #include <chrono> #include <locale> +#include <span> #include <testsuite_hooks.h> + +template<typename ChronoType> void -test_c() +test_locale_formats(const ChronoType& t, + std::span<const char* const> test_specifiers) { const char *test_locales[] = { "aa_DJ.UTF-8", @@ -19,15 +23,44 @@ test_c() "az_IR.UTF-8", "my_MM.UTF-8", }; - std::chrono::sys_seconds t{std::chrono::seconds{1}}; + auto format_args = std::make_format_args(t); for (auto locale_name : test_locales) { - auto s = std::format(std::locale(locale_name), "{:L%c}", t); - VERIFY( !s.empty() ); + std::locale loc(locale_name); + for (auto specifier : test_specifiers) + { + auto s = std::vformat(loc, specifier, format_args); + VERIFY( !s.empty() ); + } } } +void +test_locale_formats() +{ + using namespace std::chrono; + + const char* test_specifiers[] = { + "{:L%x}", "{:L%Ex}", + "{:L%c}", "{:L%Ec}", + "{:L%X}", "{:L%EX}", + "{:L%r}", + }; + auto date_time_specifiers = std::span(test_specifiers); + auto date_specifiers = date_time_specifiers.subspan(0, 2); + auto time_specifiers = date_time_specifiers.subspan(4); + + auto ymd = 2020y/November/12d; + test_locale_formats(ymd, date_specifiers); + + auto tod = 25h + 10min + 12s; + test_locale_formats(tod, time_specifiers); + + auto tp = sys_days(ymd) + tod; + test_locale_formats(tp, date_time_specifiers); +} + #include <stdlib.h> #include <time.h> @@ -93,7 +126,7 @@ test_c_local() int main() { - test_c(); + test_locale_formats(); test_c_zoned(); test_c_local(); } -- 2.49.0