From f7ec9546ee07298f5d0aed6fc245e2405fe950bc Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 10 Mar 2025 15:48:58 +0900 Subject: merge revision(s) 3f07bc76ff6a11232d9f18e5eaa31835c195e8f0, 34098b669c0cbc024cd08e686891f1dfe0a10aaf: [Backport #21144] [Bug #21144] Win32: Use Windows time zone ID if TZ is not set If the TZ environment variable is not set, the time zone names retrieved from the system are localized for UI display and may vary across editions and language packs for the same time zone. Use the time zone IDs that are invariant across environments instead. [Bug #21144] Win32: Convert the time zone name to the current locale The Windows time zone IDs provided by Microsoft as of 24H1 are ASCII only all, but the API itself is not impossible to set non-ASCII key name. Prefer the current locale encoding for now until we move to UTF-8 including environment variables and command line arguments. --- time.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 5 deletions(-) (limited to 'time.c') diff --git a/time.c b/time.c index 04f6f2afc5..e8815b5e3d 100644 --- a/time.c +++ b/time.c @@ -44,6 +44,10 @@ #include "ruby/encoding.h" #include "timev.h" +#if defined(_WIN32) +# include "timezoneapi.h" /* DYNAMIC_TIME_ZONE_INFORMATION */ +#endif + #include "builtin.h" static ID id_submicro, id_nano_num, id_nano_den, id_offset, id_zone; @@ -701,10 +705,51 @@ static VALUE tm_from_time(VALUE klass, VALUE time); bool ruby_tz_uptodate_p; +#ifdef _WIN32 +enum {tzkey_max = numberof(((DYNAMIC_TIME_ZONE_INFORMATION *)NULL)->TimeZoneKeyName)}; +static struct { + char use_tzkey; + char name[tzkey_max * 4 + 1]; +} w32_tz; + +static char * +get_tzname(int dst) +{ + if (w32_tz.use_tzkey) { + if (w32_tz.name[0]) { + return w32_tz.name; + } + else { + /* + * Use GetDynamicTimeZoneInformation::TimeZoneKeyName, Windows + * time zone ID, which is not localized because it is the key + * for "Dynamic DST" keys under the "Time Zones" registry. + * Available since Windows Vista and Windows Server 2008. + */ + DYNAMIC_TIME_ZONE_INFORMATION tzi; + WCHAR *const wtzkey = tzi.TimeZoneKeyName; + DWORD tzret = GetDynamicTimeZoneInformation(&tzi); + if (tzret != TIME_ZONE_ID_INVALID && *wtzkey) { + int wlen = (int)wcsnlen(wtzkey, tzkey_max); + int clen = WideCharToMultiByte(CP_UTF8, 0, wtzkey, wlen, + w32_tz.name, sizeof(w32_tz.name) - 1, + NULL, NULL); + w32_tz.name[clen] = '\0'; + return w32_tz.name; + } + } + } + return _tzname[_daylight && dst]; +} +#endif + void -ruby_reset_timezone(void) +ruby_reset_timezone(const char *val) { ruby_tz_uptodate_p = false; +#ifdef _WIN32 + w32_tz.use_tzkey = !val || !*val; +#endif ruby_reset_leap_second_info(); } @@ -950,7 +995,13 @@ zone_str(const char *zone) str = rb_usascii_str_new(zone, len); } else { +#ifdef _WIN32 + str = rb_utf8_str_new(zone, len); + /* until we move to UTF-8 on Windows completely */ + str = rb_str_export_locale(str); +#else str = rb_enc_str_new(zone, len, rb_locale_encoding()); +#endif } return rb_fstring(str); } @@ -1652,11 +1703,9 @@ localtime_with_gmtoff_zone(const time_t *t, struct tm *result, long *gmtoff, VAL if (zone) { #if defined(HAVE_TM_ZONE) *zone = zone_str(tm.tm_zone); +#elif defined(_WIN32) + *zone = zone_str(get_tzname(tm.tm_isdst)); #elif defined(HAVE_TZNAME) && defined(HAVE_DAYLIGHT) -# if defined(RUBY_MSVCRT_VERSION) && RUBY_MSVCRT_VERSION >= 140 -# define tzname _tzname -# define daylight _daylight -# endif /* this needs tzset or localtime, instead of localtime_r */ *zone = zone_str(tzname[daylight && tm.tm_isdst]); #else @@ -5807,6 +5856,10 @@ rb_time_zone_abbreviation(VALUE zone, VALUE time) void Init_Time(void) { +#ifdef _WIN32 + ruby_reset_timezone(getenv("TZ")); +#endif + id_submicro = rb_intern_const("submicro"); id_nano_num = rb_intern_const("nano_num"); id_nano_den = rb_intern_const("nano_den"); -- cgit v1.2.3