On Fri, 13 Jun 2025 at 14:17, Tomasz Kamiński <tkami...@redhat.com> wrote:
>
> 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?

Consolidating all these locale-specific formats into one function
makes sense, thanks.

OK for trunk, and we should consider backporting too (but maybe not
immediately).



>
>  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
>

Reply via email to