[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.
This commit is contained in:
Nobuyoshi Nakada 2025-02-17 21:46:47 +09:00 committed by Nobuyoshi Nakada
parent bd84c75a01
commit 3f07bc76ff
Notes: git 2025-02-19 09:27:49 +00:00
3 changed files with 60 additions and 10 deletions

8
hash.c
View File

@ -5016,7 +5016,7 @@ env_name(volatile VALUE *s)
static VALUE env_aset(VALUE nm, VALUE val);
static void
reset_by_modified_env(const char *nam)
reset_by_modified_env(const char *nam, const char *val)
{
/*
* ENV['TZ'] = nil has a special meaning.
@ -5025,7 +5025,7 @@ reset_by_modified_env(const char *nam)
* This hack might works only on Linux glibc.
*/
if (ENVMATCH(nam, TZ_ENV)) {
ruby_reset_timezone();
ruby_reset_timezone(val);
}
}
@ -5033,7 +5033,7 @@ static VALUE
env_delete(VALUE name)
{
const char *nam = env_name(name);
reset_by_modified_env(nam);
reset_by_modified_env(nam, NULL);
VALUE val = getenv_with_lock(nam);
if (!NIL_P(val)) {
@ -5439,7 +5439,7 @@ env_aset(VALUE nm, VALUE val)
get_env_ptr(value, val);
ruby_setenv(name, value);
reset_by_modified_env(name);
reset_by_modified_env(name, value);
return val;
}

View File

@ -28,7 +28,10 @@ struct timeval rb_time_timeval(VALUE);
RUBY_SYMBOL_EXPORT_BEGIN
/* time.c (export) */
void ruby_reset_leap_second_info(void);
void ruby_reset_timezone(void);
#ifdef RBIMPL_ATTR_DEPRECATED_INTERNAL_ONLY
RBIMPL_ATTR_DEPRECATED_INTERNAL_ONLY()
#endif
void ruby_reset_timezone(const char *);
RUBY_SYMBOL_EXPORT_END
#endif /* INTERNAL_TIME_H */

57
time.c
View File

@ -45,6 +45,10 @@
#include "ruby/util.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;
@ -703,10 +707,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();
}
@ -1653,11 +1698,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
@ -5863,6 +5906,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");