On Wed, 19 Mar 2025 at 13:09, Jonathan Wakely <[email protected]> wrote:
>
> On Sat, 1 Mar 2025 at 05:24, XU Kailiang <[email protected]> wrote:
> >
> > Formatting a time point with %c was implemented by calling
> > std::vprint_to with format string constructed from locale's D_T_FMT
> > string, but in some locales this string does not compliant to
> > chrono-specs. So just use _M_locale_fmt to avoid this problem.
>
> I realised there's a problem with this solution, which is that time
> zone information is ignored by _M_locale_fmt.
>
> For example:
>
> #include <print>
> #include <chrono>
> #include <locale>
>
> int main()
> {
> std::locale::global(std::locale("aa_DJ.UTF-8"));
Ah, we're also missing __os.imbue(__loc) in _M_locale_fmt, which is
necessary to use the formatting locale properly, and not rely on
std::locale::global having been changed (as your testcase does).
> using namespace std::chrono;
> auto time = sys_days(2025y/March/19) + 6h;
> std::chrono::zoned_time z("America/New_York", time);
> std::println("{:L%c}", z);
> }
>
> This prints:
> Arb 19 Cig 2025 2:00:00 saaku GMT
>
> This is very wrong - it's printing the local time in New York, 2am,
> but showing "GMT" (because that's my system's time zone). This is
> because the C library only considers the global zone (set from
> /etc/localtime, or $TZ, or the thread-unsafe tzset function) and
> ignores the time zone present in the zoned_time.
>
> We can convert a zoned_time to UTC by applying the zone's offset, then
> use localtime (or localtime_r if available) to convert that to a
> struct tm with a broken down time in the system's local time zone,
> then _M_locale_fmt can print it.
>
> So something like:
>
> using namespace chrono;
> using ::std::chrono::__detail::__local_time_fmt;
>
> struct tm __tm{};
> struct tm* __tm_ok = nullptr;
>
> if constexpr (__is_specialization_of<_Tp, __local_time_fmt>)
> {
> if (__t._M_offset_sec)
> {
> auto __ut = __t._M_time - *__t._M_offset_sec;
> auto __us = chrono::round<seconds>(__ut);
> time_t __tt = __us.time_since_epoch().count();
> #if _GLIBCXX_HAVE_LOCALTIME_R
> __tm_ok = ::localtime_r(&__tt, &__tm);
> #else
> if (__tm_ok = ::localtime(&__tt))
> __builtin_memcpy(&__tm, __tm_ok, sizeof(struct tm));
> #endif
> }
> }
>
> if (!__tm_ok)
> {
> auto __d = _S_days(__t);
>
> This needs some configure changes to detect localtime_r, so I'll make
> that work as a follow-up patch.
>
>
> >
> > libstdc++-v3/ChangeLog:
> >
> > PR libstdc++/117214
> > * include/bits/chrono_io.h (__formatter_chrono::_M_c): use
> > _M_locale_fmt to format %c time point.
> > * testsuite/std/time/format/pr117214.cc: New test.
> >
> > Signed-off-by: XU Kailiang <[email protected]>
> > ---
> > libstdc++-v3/include/bits/chrono_io.h | 35 ++++++++++---------
> > .../testsuite/std/time/format/pr117214.cc | 32 +++++++++++++++++
> > 2 files changed, 51 insertions(+), 16 deletions(-)
> > create mode 100644 libstdc++-v3/testsuite/std/time/format/pr117214.cc
> >
> > diff --git a/libstdc++-v3/include/bits/chrono_io.h
> > b/libstdc++-v3/include/bits/chrono_io.h
> > index 8c026586d4c..569f20376b1 100644
> > --- a/libstdc++-v3/include/bits/chrono_io.h
> > +++ b/libstdc++-v3/include/bits/chrono_io.h
> > @@ -887,27 +887,30 @@ namespace __format
> >
> > template<typename _Tp, typename _FormatContext>
> > typename _FormatContext::iterator
> > - _M_c(const _Tp& __tt, typename _FormatContext::iterator __out,
> > + _M_c(const _Tp& __t, typename _FormatContext::iterator __out,
> > _FormatContext& __ctx, bool __mod = false) const
> > {
> > // %c Locale's date and time representation.
> > // %Ec Locale's alternate date and time representation.
> >
> > - basic_string<_CharT> __fmt;
> > - auto __t = _S_floor_seconds(__tt);
> > - locale __loc = _M_locale(__ctx);
> > - const auto& __tp = use_facet<__timepunct<_CharT>>(__loc);
> > - const _CharT* __formats[2];
> > - __tp._M_date_time_formats(__formats);
> > - if (*__formats[__mod]) [[likely]]
> > - {
> > - __fmt = _GLIBCXX_WIDEN("{:L}");
> > - __fmt.insert(3u, __formats[__mod]);
> > - }
> > - else
> > - __fmt = _GLIBCXX_WIDEN("{:L%a %b %e %T %Y}");
> > - return std::vformat_to(std::move(__out), __loc, __fmt,
> > -
> > std::make_format_args<_FormatContext>(__t));
> > + using namespace chrono;
> > + auto __d = _S_days(__t);
> > + using _TDays = decltype(__d);
> > + const auto __ymd = _S_date(__d);
>
> There's no need to use _S_date(__d) here, because we know that __d is
> either sys_days or local_days, so this can be just:
>
> const year_month_day __ymd(__d);
>
> > + const auto __y = __ymd.year();
> > + const auto __hms = _S_hms(__t);
> > +
> > + struct tm __tm{};
> > + __tm.tm_year = (int)__y - 1900;
> > + __tm.tm_yday = (__d - _TDays(__y/January/1)).count();
> > + __tm.tm_mon = (unsigned)_S_month(__t) - 1;
>
> This would call _S_date(__t).month() but we can use __ymd.month().
>
> > + __tm.tm_mday = (unsigned)_S_day(__t);
>
> And this would call _S_date(__t).day() but we can use __ymd.day().
>
> > + __tm.tm_wday = _S_weekday(__t).c_encoding();
>
> And this would call weekday(_S_days(__t)) so we can use weekday(__d).
>
> > + __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');
> > }
> >
> > template<typename _Tp, typename _FormatContext>
> > diff --git a/libstdc++-v3/testsuite/std/time/format/pr117214.cc
> > b/libstdc++-v3/testsuite/std/time/format/pr117214.cc
> > new file mode 100644
> > index 00000000000..5b36edadfa0
> > --- /dev/null
> > +++ b/libstdc++-v3/testsuite/std/time/format/pr117214.cc
> > @@ -0,0 +1,32 @@
> > +// { dg-do run { target c++20 } }
> > +// { dg-require-namedlocale "aa_DJ.UTF-8" }
> > +// { dg-require-namedlocale "ar_SA.UTF-8" }
> > +// { dg-require-namedlocale "ca_AD.UTF-8" }
> > +// { dg-require-namedlocale "az_IR.UTF-8" }
> > +// { dg-require-namedlocale "my_MM.UTF-8" }
> > +
> > +#include <chrono>
> > +#include <locale>
> > +#include <testsuite_hooks.h>
> > +
> > +void
> > +test_c()
> > +{
> > + const char *test_locales[] = {
> > + "aa_DJ.UTF-8",
> > + "ar_SA.UTF-8",
> > + "ca_AD.UTF-8",
> > + "az_IR.UTF-8",
> > + "my_MM.UTF-8",
> > + };
> > + for (auto locale_name : test_locales)
> > + {
> > + std::locale::global(std::locale(locale_name));
> > + VERIFY( !std::format("{:L%c}", std::chrono::sys_seconds()).empty() );
> > + }
> > +}
> > +
> > +int main()
> > +{
> > + test_c();
> > +}
> > --
> > 2.48.1
> >