Subject: Re: bug#11281: DST has not effect on windows XP when system DST adjustment is disabled

From: Kazuhiro Ito <[email-masked]>
Date: Mon, 26 May 2025 22:08:22 +0900

> > Please see the
> > result of 2nd est5edt, where _dstbias is zero and _daylight is
> > non-zero, which causes the issue.  I guess the first -3600 is probably
> > initial value because Microsoft says that initial values are for
> > "PST8PDT".
> 
> I can understand this in your case, since your timezone doesn't
> specify DST.  Can you show me what the GetTimeZoneInformation API
> returns on your system with your default timezone?

I tried with following code.

#include <stdio.h>
#include <windows.h>

void printSYSTEMTIME (const char *name, LPSYSTEMTIME time) {
  printf("%s is %ld, %ld, %ld, %ld, %ld, %ld, %ld, %ld\n",
	 name, time->wYear, time->wMonth, time->wDayOfWeek,
	 time->wDay, time->wHour, time->wMinute, time->wSecond,
	 time->wMilliseconds);
}

int main (int argc, wchar_t* argv[]) {
  TIME_ZONE_INFORMATION timezone;

  DWORD result = GetTimeZoneInformation(&timezone);

  printf ("Return code is %d\n", result);
  printf ("Bias is %ld\n", timezone.Bias);
  wprintf (L"StandardName is %s\n", timezone.StandardName);
  printSYSTEMTIME("StandardDate", &timezone.StandardDate);
  printf ("StandardBias is %ld\n", timezone.StandardBias);
  wprintf (L"DaylightName is %s\n", timezone.DaylightName);
  printSYSTEMTIME("StandardDate", &timezone.DaylightDate);
  printf ("DaylightBias is %ld\n", timezone.DaylightBias);

  return 0;
}

result:
Return code is 0
Bias is -540
StandardName is 東京 (in utf-16-le)
StandardDate is 0, 0, 0, 0, 0, 0, 0, 0
StandardBias is 0
DaylightName is 東京 (in utf-16-le)
StandardDate is 0, 0, 0, 0, 0, 0, 0, 0
DaylightBias is 0

result on Eastern Standard Time:
Return code is 2
Bias is 300
StandardName is 東部標準時 (in utf-16-le)
StandardDate is 0, 11, 0, 1, 2, 0, 0, 0
StandardBias is 0
DaylightName is 東部夏時間 (in utf-16-le)
StandardDate is 0, 3, 0, 2, 2, 0, 0, 0
DaylightBias is -60

> > > Sorry, I don't think I understand: AFAIU, _dstbias can legitimately be
> > > zero in timezones that don't support DST.  If that is true, correcting
> > > a zero-valued _dstbias to 1 hour will cause problems, no?
> > > should also look at the value of _daylight, and only do the _dstbias
> > > correction of _daylight is zero (i.e. no DST in this timezone)?  Or
> > > what am I missing?
> > 
> > I agree with you that _dstbias should be zero when _daylight is zero.
> > However, as I wrote above, _dstbias should not be used when _daylight
> > is zero.  My emacs has set TZ to JST-9 long time and _dstbias has been
> > -3600 according to my test.  It has no problem till now.  So I removed
> > _daylight value check before posting the patch, but If you mind,
> > correcting _dstbias only when _daylight is *NON-ZERO* (not _daylight
> > is zero!) should be okay.
> 
> See above: I think I changed my mind, and we should set _dstbias
> according to the actual value of TZ, and only if TZ is indeed set
> (because I think if TZ is not set, MSVCRT gets the correct value from
> GetTimeZoneInformation).  Can you try that and see if it improves the
> current situation?

Please see attached patch.  I think we don't need to re-parse TZ value
because it is already parsed by tzset and the result is set to
_daylight variable.  In addition, I noticed there is a code that calls
tztest and needs the same workaoround on Windows in lib/time_rz.c,
however I don't know how to apply the change only on Windows.  Please
see the second patch.

--
Kazuhiro Ito
>From 34edab6204b74016ed90ee816bb17ee58f0236b8 Mon Sep 17 00:00:00 2001
From: Kazuhiro Ito <[email-masked]>
Date: Sun, 25 May 2025 20:04:48 +0900
Subject: [PATCH 1/2] Adjust _dstbias value after tzset on Windows

The Windows CRT implementation of 'tzset' does't seem to adjust
_dstbias when timezone is specified by TZ environmental variable.
(Bug#11281)

* src/w32.c (sys_tzset): New function.  Adjust _dstbias value
after tzset.
* src/timefns.c (emacs_localtime_rz): Use it on Windows.
---
 src/timefns.c |  7 ++++++-
 src/w32.c     | 12 +++++++++++-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/timefns.c b/src/timefns.c
index 4d296ff8dcd..d1304328e11 100644
--- a/src/timefns.c
+++ b/src/timefns.c
@@ -42,6 +42,7 @@ Copyright (C) 1985-1987, 1989, 1993-2025 Free Software Foundation, Inc.
 
 #ifdef WINDOWSNT
 extern clock_t sys_clock (void);
+extern void sys_tzset (void);
 #endif
 
 #ifdef HAVE_TIMEZONE_T
@@ -188,7 +189,7 @@ emacs_localtime_rz (timezone_t tz, time_t const *t, struct tm *tm)
      So all Emacs features that repeatedly call time functions (e.g.,
      display-time) are in real danger of missing timezone and DST
      changes.  Calling tzset before each localtime call fixes that.  */
-  tzset ();
+  sys_tzset ();
 #endif
   tm = localtime_rz (tz, t, tm);
   if (!tm && errno == ENOMEM)
@@ -305,7 +306,11 @@ tzlookup (Lisp_Object zone, bool settz)
     {
       block_input ();
       emacs_setenv_TZ (zone_string);
+#ifndef WINDOWSNT
       tzset ();
+#else
+      sys_tzset ();
+#endif
       timezone_t old_tz = local_tz;
       local_tz = new_tz;
       tzfree (old_tz);
diff --git a/src/w32.c b/src/w32.c
index 5de721ad71f..7fcf08d5ba2 100644
--- a/src/w32.c
+++ b/src/w32.c
@@ -84,6 +84,7 @@ #define DEFER_MS_W32_H
 int sys_read (int, char *, unsigned int);
 int sys_write (int, const void *, unsigned int);
 struct tm *sys_localtime (const time_t *);
+void sys_tzset (void);
 /* MinGW64 system headers include string.h too early, causing the
    compiler to emit a warning about sys_strerror having no
    prototype.  */
@@ -10289,6 +10290,15 @@ w32_read_registry (HKEY rootkey, Lisp_Object lkey, Lisp_Object lname)
 }
 
 
+/* The Windows CRT implementation of 'tzset' does't seem to adjust
+   _dstbias when timezone is specified by TZ environmental variable. */
+void sys_tzset (void) {
+  tzset ();
+
+  if(getenv ("TZ"))
+    _dstbias = _daylight ? -3600 : 0;
+}
+
 /* The Windows CRT functions are "optimized for speed", so they don't
    check for timezone and DST changes if they were last called less
    than 1 minute ago (see http://support.microsoft.com/kb/821231).  So
@@ -10298,7 +10308,7 @@ w32_read_registry (HKEY rootkey, Lisp_Object lkey, Lisp_Object lname)
 struct tm *
 sys_localtime (const time_t *t)
 {
-  tzset ();
+  sys_tzset ();
   return localtime (t);
 }
 
-- 
2.45.1

>From 60f07d0d6fc39ded444cc0bb23b75bf3290fa2ad Mon Sep 17 00:00:00 2001
From: Kazuhiro Ito <[email-masked]>
Date: Mon, 26 May 2025 20:52:59 +0900
Subject: [PATCH 2/2] * lib/time_rz.c (change_env): Sample workaround for
 Windows

---
 lib/time_rz.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lib/time_rz.c b/lib/time_rz.c
index 125f4e272d2..1ce6e76b18b 100644
--- a/lib/time_rz.c
+++ b/lib/time_rz.c
@@ -190,6 +190,8 @@ change_env (timezone_t tz)
   if (setenv_TZ (tz->tz_is_set ? tz->abbrs : NULL) != 0)
     return false;
   tzset ();
+  if (getenv("TZ"))
+    _dstbias = _daylight ? -3600 : 0;
   return true;
 }
 
-- 
2.45.1