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

From: Kazuhiro Ito <[email-masked]>
Date: Sun, 25 May 2025 21:03:43 +0900

> > ;; After setting timezone without DST in the Windows Control Panel
> > $ TZ= emacs -Q
> > 
> > (let ((time (encode-time '(0 0 12 20 5 2025 nil nil t))))
> >   (setenv "TZ" "JST-9")
> >   (decode-time time "est5edt"))
> > 
> > -> unexpected result
> > 
> > So, following code would fail too.
> > 
> > (let ((time (encode-time '(0 0 12 20 5 2025 nil nil t))))
> >   ;; After setting timezone without DST in the Windows Control Panel
> >   (setenv "TZ" nil)
> >   (setenv "TZ" "JST-9")
> >   (decode-time time "est5edt"))
> > 
> > -> unexpected result
> > 
> > > That is, as long as you tell MSVCRT that
> > > your current timezone doesn't use DST, the conversion is correct.
> > 
> > As I wrote above, this problem occurs after you once call `(setenv
> > "TZ" nil)' under setting that system timezone is non-DST
> > timezone.  If you don't set TZ to nil, conversion would always
> > succeed.
> > 
> > $ TZ=est5edt emacs -Q
> > 
> > (let ((time (encode-time '(0 0 12 20 5 2025 nil nil t))))
> >   (setenv "TZ" "JST-9t")
> >   (decode-time time "est5edt"))
> > 
> > -> expected result
> > 
> > (let ((time (encode-time '(0 0 12 20 5 2025 nil nil t))))
> >   (setenv "TZ" "pst8pdt")
> >   (decode-time time "est5edt"))
> > 
> > -> expected result
> 
> Thanks.  Volunteers are welcome to investigate how MSVCRT works and
> why this sometimes causes incorrect results, because I've exhausted my
> knowledge of this dark corner.

I've found the fix.
I wrote tiny test program.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

static void show_globals () {
  printf("_daylight = %d\n" , _daylight );
  printf("_timezone = %ld\n", _timezone );
  printf("_tzname[0] = %s\n", _tzname[0]);
  printf("_dstbias = %d\n\n", _dstbias );
}

int main(void) {
  putenv("TZ=JST-9");
  _tzset();
  printf("JST-9\n");
  show_globals();

  putenv("TZ=est5edt");
  _tzset();
  printf("est5edt\n");
  show_globals();

  putenv("TZ=");
  _tzset();
  printf("System timezone\n");
  show_globals();

  putenv("TZ=est5edt");
  _tzset();
  printf("est5edt (2nd)\n");
  show_globals();

  return 0;
}

Result is here.

> JST-9
> _daylight = 0
> _timezone = -32400
> _tzname[0] = JST
> _dstbias = -3600
> est5edt
> _daylight = 101
> _timezone = 18000
> _tzname[0] = est
> _dstbias = -3600
> System timezone
> _daylight = 0
> _timezone = -32400
> _tzname[0] = 東京 (標準時)
> _dstbias = 0
> est5edt (2nd)
> _daylight = 101
> _timezone = 18000
> _tzname[0] = est
> _dstbias = 0

When I set timezone to system timezone without DST, _dstbias global
variable is set to 0.  But its value is never changed by setting
timezone with TZ environmental variable and affects calculation UTC
offset of DST.  To change _dstbias value to -3600 fixes the issue.

There may be another issue about timezones where DST shift is not one
hour.  Wikipedia says the Lord Howe Island is so.  However I apologize
for overlooking Emacs on Windows users live there.

Tiny patch is attached.

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

tzset of Windows CRT may set _dstbias to 0, it causes wrong result
of UTC offset calculation for DST timezone specified by timezone
name.  Set its value to -3600, suitable value for US's rule, if
tzset set it to 0. (Bug#11281)

* src/w32.c (sys_localtime): As above.
* src/timefns.c (emacs_localtime_rz, tzlookup): As above.
---
 src/timefns.c | 12 ++++++++++++
 src/w32.c     |  5 +++++
 2 files changed, 17 insertions(+)

diff --git a/src/timefns.c b/src/timefns.c
index 4d296ff8dcd..50de4de5799 100644
--- a/src/timefns.c
+++ b/src/timefns.c
@@ -189,6 +189,11 @@ emacs_localtime_rz (timezone_t tz, time_t const *t, struct tm *tm)
      display-time) are in real danger of missing timezone and DST
      changes.  Calling tzset before each localtime call fixes that.  */
   tzset ();
+  /* tzset of Windows CRT may set _dstbias to 0, it causes wrong result
+     of UTC offset calculation for DST timezone specified by timezone
+     name. */
+  if (!_dstbias)
+    _dstbias = -3600;
 #endif
   tm = localtime_rz (tz, t, tm);
   if (!tm && errno == ENOMEM)
@@ -306,6 +311,13 @@ tzlookup (Lisp_Object zone, bool settz)
       block_input ();
       emacs_setenv_TZ (zone_string);
       tzset ();
+#ifdef WINDOWSNT
+      /* tzset of Windows CRT may set _dstbias to 0, it causes wrong result
+	 of UTC offset calculation for DST timezone specified by timezone
+	 name. */
+      if (!_dstbias)
+	_dstbias = -3600;
+#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..c8ffa977829 100644
--- a/src/w32.c
+++ b/src/w32.c
@@ -10299,6 +10299,11 @@ w32_read_registry (HKEY rootkey, Lisp_Object lkey, Lisp_Object lname)
 sys_localtime (const time_t *t)
 {
   tzset ();
+  /* tzset of Windows CRT may set _dstbias to 0, it causes wrong result
+     of UTC offset calculation for DST timezone specified by timezone
+     name. */
+  if (!_dstbias)
+    _dstbias = -3600;
   return localtime (t);
 }
 
-- 
2.45.1