On Fri, 21 Mar 2025 at 12:24, Tomasz Kaminski <tkami...@redhat.com> wrote:
>
>
>
> On Thu, Mar 20, 2025 at 10:39 AM Jonathan Wakely <jwak...@redhat.com> wrote:
>>
>> The previous commit fixed most cases of %c formatting, but it
>> incorrectly prints times using the system's local time zone. This only
>> matters if the locale's %c format includes %Z, but some do.
>>
>> To print a correct value for %Z we can set tm.tm_zone to either "UTC" or
>> the abbreviation passed to the formatter in the local-time-format-t
>> structure.
>>
>> For local times with no info and for systems that don't support tm_zone
>> (which is new in POSIX.1-2024) we just set tm_isdst = -1 so that no zone
>> name is printed.
>>
>> In theory, a locale's %c format could use %z which should print a +hhmm
>> offset from UTC. I'm unsure how to control that though. The new
>> tm_gmtoff field in combination with tm_isdst != -1 seems like it should
>> work, but using that without also setting tm_zone causes the system zone
>> to be used for %Z again. That means local_time_format(lt, nullptr, &off)
>> might work for a locale that uses %z but prints the wrong thing for %Z.
>> This commit doesn't set tm_gmtoff even if _M_offset_sec is provided for
>> a local-time-format-t value.
>>
>> libstdc++-v3/ChangeLog:
>>
>>         PR libstdc++/117214
>>         * configure.ac: Use AC_STRUCT_TIMEZONE.
>>         * config.h.in: Regenerate.
>>         * configure: Regenerate.
>>         * include/bits/chrono_io.h (__formatter_chrono::_M_c): Set
>>         tm_isdst and tm_zone.
>>         * testsuite/std/time/format/pr117214.cc: Check %c formatting of
>>         zoned_time and local time.
>> ---
>>
>> Tested x86_64-linux.
>
> The c++ code parts LGTM, however the changes in config.h.in and
> configure went over my head: they seem to check for new members,
> but I cannot confirm if they are correct and according to best practices.

It's all generated by autoconf. The only change is adding
AC_STRUCT_TIMEZONE to configure.ac which is documented at
https://www.gnu.org/software/autoconf/manual/autoconf-2.64/html_node/Particular-Structures.html#Particular-Structures
The rest is just what autoconf generates from that, and so doesn't
need to be reviewed.


>> diff --git a/libstdc++-v3/configure.ac b/libstdc++-v3/configure.ac
>> index a3b257fe652..fe0cdde1f7a 100644
>> --- a/libstdc++-v3/configure.ac
>> +++ b/libstdc++-v3/configure.ac
>> @@ -584,6 +584,8 @@ GLIBCXX_CHECK_FILEBUF_NATIVE_HANDLES
>>  # For std::text_encoding
>>  GLIBCXX_CHECK_TEXT_ENCODING
>>
>> +AC_STRUCT_TIMEZONE
>> +
>>  # Define documentation rules conditionally.
>>
>>  # See if makeinfo has been installed and is modern enough
>> diff --git a/libstdc++-v3/include/bits/chrono_io.h 
>> b/libstdc++-v3/include/bits/chrono_io.h
>> index 86338d48d18..08969166d2f 100644
>> --- a/libstdc++-v3/include/bits/chrono_io.h
>> +++ b/libstdc++-v3/include/bits/chrono_io.h
>> @@ -894,13 +894,42 @@ namespace __format
>>           // %Ec Locale's alternate date and time representation.
>>
>>           using namespace chrono;
>> +         using ::std::chrono::__detail::__utc_leap_second;
>> +         using ::std::chrono::__detail::__local_time_fmt;
>> +
>> +         struct tm __tm{};
>> +
>> +         // Some locales use %Z in their %c format but we don't want 
>> strftime
>> +         // to use the system's local time zone (from /etc/localtime or $TZ)
>> +         // as the output for %Z. Setting tm_isdst to -1 says there is no
>> +         // time zone info available for the time in __tm.
>> +         __tm.tm_isdst = -1;
>> +
>> +#ifdef _GLIBCXX_HAVE_STRUCT_TM_TM_ZONE
>> +         // POSIX.1-2024 adds tm.tm_zone which will be used for %Z.
>> +         if constexpr (__is_time_point_v<_Tp>)
>> +           {
>> +             // One of sys_time, utc_time, or local_time.
>> +             if constexpr (!is_same_v<typename _Tp::clock, local_t>)
>> +               __tm.tm_zone = "UTC";
>
> I have checked that other clocks, like TAI and GPS that user different zone 
> name,
> are formatted using __local_time_fmt.

Thanks!

>>
>> +           }
>> +         else if constexpr (__is_specialization_of<_Tp, __local_time_fmt>)
>> +           {
>> +             // local-time-format-t is used to provide time zone info for
>> +             // one of zoned_time, tai_time, gps_time, or local_time.
>> +             if (__t._M_abbrev)
>> +               __tm.tm_zone = __t._M_abbrev->c_str();
>> +           }
>> +         else
>> +           __tm.tm_zone = "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);
>>
>> -         struct tm __tm{};
>>           __tm.tm_year = (int)__y - 1900;
>>           __tm.tm_yday = (__d - _TDays(__y/January/1)).count();
>>           __tm.tm_mon = (unsigned)__ymd.month() - 1;
>> @@ -909,6 +938,7 @@ namespace __format
>>           __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');
>>         }
>> diff --git a/libstdc++-v3/testsuite/std/time/format/pr117214.cc 
>> b/libstdc++-v3/testsuite/std/time/format/pr117214.cc
>> index e7831832206..87c703dd255 100644
>> --- a/libstdc++-v3/testsuite/std/time/format/pr117214.cc
>> +++ b/libstdc++-v3/testsuite/std/time/format/pr117214.cc
>> @@ -28,7 +28,72 @@ test_c()
>>    }
>>  }
>>
>> +#include <stdlib.h>
>> +#include <time.h>
>> +
>> +extern "C++" int tzset(int);
>> +
>> +template<typename T = void>
>> +void maybe_set_tz()
>> +{
>> +#ifdef _GLIBCXX_HAVE_SETENV
>> +  // True if a function 'T tzset()' has been declared.
>> +  if constexpr (requires (T (*p)()) { p = &tzset; })
>> +  {
>> +    setenv("TZ", "GMT0", 1);
>> +    tzset(); // Set C library's global time zone to GMT0
>> +  }
>> +#endif
>> +}
>> +
>> +void
>> +test_c_zoned()
>> +{
>> +#if _GLIBCXX_USE_CXX11_ABI
>> +  maybe_set_tz();
>> +
>> +  using namespace std::chrono;
>> +  auto time = sys_days(2025y/March/19) + 15h;
>> +  zoned_time z("America/New_York", time);
>> +  auto s = std::format(std::locale("aa_DJ.UTF-8"), "{:L%c}", z);
>> +  VERIFY( s.find("11:00") != s.npos );
>> +
>> +  // We should not print an incorrect time zone.
>> +  VERIFY( s.find("GMT") == s.npos );
>> +  VERIFY( s.find("UTC") == s.npos );
>> +
>> +#ifdef _GLIBCXX_HAVE_STRUCT_TM_TM_ZONE
>> +  // When tm.tm_zone is supported, we do print the correct time zone.
>> +  VERIFY( s.find(z.get_info().abbrev) != s.npos );
>> +#endif
>> +#endif
>> +}
>> +
>> +void
>> +test_c_local()
>> +{
>> +  maybe_set_tz();
>> +
>> +  std::locale("aa_DJ.UTF-8");
>> +  using namespace std::chrono;
>> +  auto time = local_days(2025y/March/19) + 11h;
>> +  auto s = std::format(std::locale("aa_DJ.UTF-8"), "{:L%c}", time);
>> +  // __builtin_printf("%s\n", s.c_str()); return;
>> +  VERIFY( s.find("11:00") != s.npos );
>> +
>> +  // We should not print any time zone.
>> +  VERIFY( s.find("GMT") == s.npos );
>> +  VERIFY( s.find("UTC") == s.npos );
>> +
>> +#if _GLIBCXX_USE_CXX11_ABI
>> +  zoned_time z(system_clock::now());
>> +  VERIFY( s.find(z.get_info().abbrev) == s.npos );
>> +#endif
>> +}
>> +
>>  int main()
>>  {
>>    test_c();
>> +  test_c_zoned();
>> +  test_c_local();
>>  }
>> --
>> 2.48.1
>>

Reply via email to