The TZ environment problem on Windows (i.e. where a TZ value set by Cygwin
affects native Windows binaries and makes their localtime function break)
hit me again. This time in GNU clisp.

Why did it appear again? We have the workarounds in the localtime and
mktime functions already? Yes, but the localtime_r function is affected
as well, and while it _looks_ like localtime_r should work, because it
merely invokes localtime (see lib/time_r.c), actually it doesn't.

To catch these mistakes, I'm adding unit tests.


2026-10-01  Bruno Haible  <[email protected]>

        mktime: Add tests.
        * tests/test-mktime-w32.c: New file.
        * modules/mktime-tests: New file.

2026-10-01  Bruno Haible  <[email protected]>

        time_r tests: Add more tests.
        * tests/test-localtime_r-w32.c: New file.
        * modules/time_r-tests (Files): Add it.
        (Depends-on): Add test-xfail.
        (Makefile.am): Arrange to build and run test-localtime_r-w32.

        time_r: Work around localtime_r problem on native Windows.
        * m4/time_r.m4 (gl_TIME_R): On native Windows, set REPLACE_LOCALTIME_R
        to 1 always.
        * modules/time_r (Depends-on): Add localtime.
        * doc/posix-functions/localtime_r.texi: Mention localtime_r problem on
        native Windows.
        * doc/posix-functions/gmtime_r.texi: Update.

2026-10-01  Bruno Haible  <[email protected]>

        localtime: Add tests.
        * tests/test-localtime-w32.c: New file.
        * modules/localtime-tests: New file.

>From 21eabef694f99bc34504a4ac6faef264c851b0b3 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Thu, 1 Oct 2026 13:32:16 +0200
Subject: [PATCH 1/4] localtime: Add tests.

* tests/test-localtime-w32.c: New file.
* modules/localtime-tests: New file.
---
 ChangeLog                  |   6 ++
 modules/localtime-tests    |  14 +++++
 tests/test-localtime-w32.c | 120 +++++++++++++++++++++++++++++++++++++
 3 files changed, 140 insertions(+)
 create mode 100644 modules/localtime-tests
 create mode 100644 tests/test-localtime-w32.c

diff --git a/ChangeLog b/ChangeLog
index 0a99336be0..913e512562 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2026-10-01  Bruno Haible  <[email protected]>
+
+	localtime: Add tests.
+	* tests/test-localtime-w32.c: New file.
+	* modules/localtime-tests: New file.
+
 2026-09-25  Collin Funk  <[email protected]>
 
 	canonicalize: Fix a missing check for symlink loops.
diff --git a/modules/localtime-tests b/modules/localtime-tests
new file mode 100644
index 0000000000..86734330c6
--- /dev/null
+++ b/modules/localtime-tests
@@ -0,0 +1,14 @@
+Files:
+tests/test-localtime-w32.c
+tests/macros.h
+
+Depends-on:
+test-xfail
+
+configure.ac:
+
+Makefile.am:
+if OS_IS_NATIVE_WINDOWS
+TESTS += test-localtime-w32
+check_PROGRAMS += test-localtime-w32
+endif
diff --git a/tests/test-localtime-w32.c b/tests/test-localtime-w32.c
new file mode 100644
index 0000000000..f746dd9289
--- /dev/null
+++ b/tests/test-localtime-w32.c
@@ -0,0 +1,120 @@
+/* Test localtime() on native Windows.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <[email protected]>, 2026.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include <time.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define WIN32_LEAN_AND_MEAN  /* avoid including junk */
+#include <windows.h>
+
+#include "macros.h"
+
+int
+main (void)
+{
+  /* Check that when the TZ environment variable is set to a value understood
+     by Cygwin but not by native Windows, the localtime function is still
+     consistent with the Windows API functions that uses the time zone settings
+     from the System Settings / Control Panel.  */
+
+  /* Set the TZ environment variable.
+     Documentation:
+     <https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setenvironmentvariable>
+     <https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/putenv-wputenv>  */
+  if (!SetEnvironmentVariable ("TZ", "Europe/Berlin"))
+    return 2;
+  if (_putenv ("TZ=Europe/Berlin"))
+    return 2;
+
+  /* Pick a specific time point, not too far away in the past.  */
+  int hours_since_1970_01_01 = 497000; /* 2026-09-12 08:00:00 UTC */
+  time_t seconds_since_1970_01_01 = hours_since_1970_01_01 * 3600;
+
+  /* Convert it to local time through the Windows API functions.  */
+  long long hundrednanos_since_1601_01_01 =
+    (seconds_since_1970_01_01 + 134774LL * 24LL * 3600LL) * 10000000LL;
+  FILETIME filetime =
+    { (DWORD) hundrednanos_since_1601_01_01,
+      (DWORD) (hundrednanos_since_1601_01_01 >> 32)
+    };
+
+  /* Convert like GNU clisp does.  */
+  SYSTEMTIME lsystime1;
+  {
+    FILETIME lfiletime;
+    /* Documentation:
+       <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-filetimetolocalfiletime>  */
+    if (!FileTimeToLocalFileTime (&filetime, &lfiletime))
+      return 3;
+    /* Documentation:
+       <https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-filetimetosystemtime>  */
+    if (!FileTimeToSystemTime (&lfiletime, &lsystime1))
+      return 4;
+  }
+  printf ("Windows API 1: %04d-%02d-%02d %02d:%02d:%02d\n",
+          lsystime1.wYear, lsystime1.wMonth, lsystime1.wDay,
+          lsystime1.wHour, lsystime1.wMinute, lsystime1.wSecond);
+
+  /* Convert like
+     <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-filetimetolocalfiletime>
+     suggests.  */
+  SYSTEMTIME lsystime2;
+  {
+    SYSTEMTIME systime;
+    /* Documentation:
+       <https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-filetimetosystemtime>  */
+    if (!FileTimeToSystemTime (&filetime, &systime))
+      return 5;
+    /* Documentation:
+       <https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-systemtimetotzspecificlocaltime>  */
+    if (!SystemTimeToTzSpecificLocalTime (NULL, &systime, &lsystime2))
+      return 6;
+  }
+  printf ("Windows API 2: %04d-%02d-%02d %02d:%02d:%02d\n",
+          lsystime2.wYear, lsystime2.wMonth, lsystime2.wDay,
+          lsystime2.wHour, lsystime2.wMinute, lsystime2.wSecond);
+
+  /* Both methods should be equivalent.  */
+  if (lsystime1.wHour != lsystime2.wHour)
+    return 7;
+
+  /* Now see whether localtime is consistent with that.  */
+  /* Documentation:
+     <https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/localtime-localtime32-localtime64>  */
+  struct tm *lm = localtime (&seconds_since_1970_01_01);
+  if (lm == NULL)
+    return 8;
+  printf ("localtime:     %04d-%02d-%02d %02d:%02d:%02d (DST=%d)\n",
+          lm->tm_year + 1900, lm->tm_mon + 1, lm->tm_mday,
+          lm->tm_hour, lm->tm_min, lm->tm_sec,
+          !!lm->tm_isdst);
+  fflush (stdout);
+  ASSERT (lm->tm_year + 1900 == lsystime1.wYear);
+  ASSERT (lm->tm_mon + 1 == lsystime1.wMonth);
+  ASSERT (lm->tm_mday == lsystime1.wDay);
+  ASSERT (lm->tm_hour == lsystime1.wHour);
+  ASSERT (lm->tm_min == lsystime1.wMinute);
+  ASSERT (lm->tm_sec == lsystime1.wSecond);
+
+  return test_exit_status;
+}
-- 
2.53.0

>From c30f8e79104b40721028f262a1a68dbd3a9192ea Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Thu, 1 Oct 2026 14:03:44 +0200
Subject: [PATCH 2/4] time_r: Work around localtime_r problem on native
 Windows.

* m4/time_r.m4 (gl_TIME_R): On native Windows, set REPLACE_LOCALTIME_R
to 1 always.
* modules/time_r (Depends-on): Add localtime.
* doc/posix-functions/localtime_r.texi: Mention localtime_r problem on
native Windows.
* doc/posix-functions/gmtime_r.texi: Update.
---
 ChangeLog                            | 10 ++++++++++
 doc/posix-functions/gmtime_r.texi    |  2 +-
 doc/posix-functions/localtime_r.texi |  5 ++++-
 m4/time_r.m4                         |  9 ++++++++-
 modules/time_r                       |  1 +
 5 files changed, 24 insertions(+), 3 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 913e512562..cd040df53f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,13 @@
+2026-10-01  Bruno Haible  <[email protected]>
+
+	time_r: Work around localtime_r problem on native Windows.
+	* m4/time_r.m4 (gl_TIME_R): On native Windows, set REPLACE_LOCALTIME_R
+	to 1 always.
+	* modules/time_r (Depends-on): Add localtime.
+	* doc/posix-functions/localtime_r.texi: Mention localtime_r problem on
+	native Windows.
+	* doc/posix-functions/gmtime_r.texi: Update.
+
 2026-10-01  Bruno Haible  <[email protected]>
 
 	localtime: Add tests.
diff --git a/doc/posix-functions/gmtime_r.texi b/doc/posix-functions/gmtime_r.texi
index 6933b8dd78..f7151dcea4 100644
--- a/doc/posix-functions/gmtime_r.texi
+++ b/doc/posix-functions/gmtime_r.texi
@@ -11,7 +11,7 @@
 @itemize
 @item
 This function is missing on some platforms:
-mingw, MSVC 14.
+old mingw, MSVC 14.
 @item
 This function is not declared unless @code{_REENTRANT} is defined,
 on some platforms:
diff --git a/doc/posix-functions/localtime_r.texi b/doc/posix-functions/localtime_r.texi
index 3531af97be..c2637dd85b 100644
--- a/doc/posix-functions/localtime_r.texi
+++ b/doc/posix-functions/localtime_r.texi
@@ -11,11 +11,14 @@
 @itemize
 @item
 This function is missing on some platforms:
-mingw, MSVC 14.
+old mingw, MSVC 14.
 @item
 This function is not declared unless @code{_REENTRANT} is defined,
 on some platforms:
 HP-UX 11.
+@item
+On native Windows platforms (mingw), this function works incorrectly
+when the environment variable @env{TZ} has been set by Cygwin.
 @end itemize
 
 Portability problems not fixed by Gnulib:
diff --git a/m4/time_r.m4 b/m4/time_r.m4
index 59477e3bec..5b4bb2afe4 100644
--- a/m4/time_r.m4
+++ b/m4/time_r.m4
@@ -1,5 +1,5 @@
 # time_r.m4
-# serial 1
+# serial 2
 dnl Copyright (C) 2003, 2006-2026 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -12,6 +12,7 @@
 
 AC_DEFUN([gl_TIME_R],
 [
+  AC_REQUIRE([AC_CANONICAL_HOST])
   dnl Persuade glibc and Solaris <time.h> to declare localtime_r.
   AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
 
@@ -92,6 +93,12 @@ AC_DEFUN([gl_TIME_R]
       REPLACE_LOCALTIME_R=1
     fi
   fi
+  dnl This inline function definition also has the effect that time_r.o
+  dnl might get created during the build but then get ignored at link time.
+  dnl Force its inclusion at link time.
+  case "$host_os" in
+    mingw* | windows*) REPLACE_LOCALTIME_R=1 ;;
+  esac
 ])
 
 # Prerequisites of lib/time_r.c.
diff --git a/modules/time_r b/modules/time_r
index 7d5fddd1f2..a64864b926 100644
--- a/modules/time_r
+++ b/modules/time_r
@@ -8,6 +8,7 @@ m4/time_r.m4
 Depends-on:
 extensions
 time-h
+localtime       [test $HAVE_LOCALTIME_R = 0 || test $REPLACE_LOCALTIME_R = 1]
 
 configure.ac:
 gl_TIME_R
-- 
2.53.0

>From 904543d2f5d30dd3c4162415a10c3e1985842847 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Thu, 1 Oct 2026 14:05:57 +0200
Subject: [PATCH 3/4] time_r tests: Add more tests.

* tests/test-localtime_r-w32.c: New file.
* modules/time_r-tests (Files): Add it.
(Depends-on): Add test-xfail.
(Makefile.am): Arrange to build and run test-localtime_r-w32.
---
 ChangeLog                    |   6 ++
 modules/time_r-tests         |   7 +++
 tests/test-localtime_r-w32.c | 118 +++++++++++++++++++++++++++++++++++
 3 files changed, 131 insertions(+)
 create mode 100644 tests/test-localtime_r-w32.c

diff --git a/ChangeLog b/ChangeLog
index cd040df53f..baa0953592 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 2026-10-01  Bruno Haible  <[email protected]>
 
+	time_r tests: Add more tests.
+	* tests/test-localtime_r-w32.c: New file.
+	* modules/time_r-tests (Files): Add it.
+	(Depends-on): Add test-xfail.
+	(Makefile.am): Arrange to build and run test-localtime_r-w32.
+
 	time_r: Work around localtime_r problem on native Windows.
 	* m4/time_r.m4 (gl_TIME_R): On native Windows, set REPLACE_LOCALTIME_R
 	to 1 always.
diff --git a/modules/time_r-tests b/modules/time_r-tests
index 20010d23c8..3324fa2afc 100644
--- a/modules/time_r-tests
+++ b/modules/time_r-tests
@@ -4,6 +4,7 @@ tests/test-gmtime_r.c
 tests/test-gmtime_r-mt.c
 tests/test-localtime_r.c
 tests/test-localtime_r-mt.c
+tests/test-localtime_r-w32.c
 tests/macros.h
 
 Depends-on:
@@ -11,6 +12,7 @@ setenv
 thread
 nanosleep
 streq
+test-xfail
 
 configure.ac:
 gl_TM_GMTOFF
@@ -24,3 +26,8 @@ check_PROGRAMS += \
   test-localtime_r test-localtime_r-mt
 test_gmtime_r_mt_LDADD = $(LDADD) $(LIBMULTITHREAD) $(NANOSLEEP_LIB)
 test_localtime_r_mt_LDADD = $(LDADD) $(LIBMULTITHREAD) $(NANOSLEEP_LIB)
+
+if OS_IS_NATIVE_WINDOWS
+TESTS += test-localtime_r-w32
+check_PROGRAMS += test-localtime_r-w32
+endif
diff --git a/tests/test-localtime_r-w32.c b/tests/test-localtime_r-w32.c
new file mode 100644
index 0000000000..d84a15a63b
--- /dev/null
+++ b/tests/test-localtime_r-w32.c
@@ -0,0 +1,118 @@
+/* Test localtime_r() on native Windows.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <[email protected]>, 2026.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include <time.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define WIN32_LEAN_AND_MEAN  /* avoid including junk */
+#include <windows.h>
+
+#include "macros.h"
+
+int
+main (void)
+{
+  /* Check that when the TZ environment variable is set to a value understood
+     by Cygwin but not by native Windows, the localtime_r function is still
+     consistent with the Windows API functions that uses the time zone settings
+     from the System Settings / Control Panel.  */
+
+  /* Set the TZ environment variable.
+     Documentation:
+     <https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setenvironmentvariable>
+     <https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/putenv-wputenv>  */
+  if (!SetEnvironmentVariable ("TZ", "Europe/Berlin"))
+    return 2;
+  if (_putenv ("TZ=Europe/Berlin"))
+    return 2;
+
+  /* Pick a specific time point, not too far away in the past.  */
+  int hours_since_1970_01_01 = 497000; /* 2026-09-12 08:00:00 UTC */
+  time_t seconds_since_1970_01_01 = hours_since_1970_01_01 * 3600;
+
+  /* Convert it to local time through the Windows API functions.  */
+  long long hundrednanos_since_1601_01_01 =
+    (seconds_since_1970_01_01 + 134774LL * 24LL * 3600LL) * 10000000LL;
+  FILETIME filetime =
+    { (DWORD) hundrednanos_since_1601_01_01,
+      (DWORD) (hundrednanos_since_1601_01_01 >> 32)
+    };
+
+  /* Convert like GNU clisp does.  */
+  SYSTEMTIME lsystime1;
+  {
+    FILETIME lfiletime;
+    /* Documentation:
+       <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-filetimetolocalfiletime>  */
+    if (!FileTimeToLocalFileTime (&filetime, &lfiletime))
+      return 3;
+    /* Documentation:
+       <https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-filetimetosystemtime>  */
+    if (!FileTimeToSystemTime (&lfiletime, &lsystime1))
+      return 4;
+  }
+  printf ("Windows API 1: %04d-%02d-%02d %02d:%02d:%02d\n",
+          lsystime1.wYear, lsystime1.wMonth, lsystime1.wDay,
+          lsystime1.wHour, lsystime1.wMinute, lsystime1.wSecond);
+
+  /* Convert like
+     <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-filetimetolocalfiletime>
+     suggests.  */
+  SYSTEMTIME lsystime2;
+  {
+    SYSTEMTIME systime;
+    /* Documentation:
+       <https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-filetimetosystemtime>  */
+    if (!FileTimeToSystemTime (&filetime, &systime))
+      return 5;
+    /* Documentation:
+       <https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-systemtimetotzspecificlocaltime>  */
+    if (!SystemTimeToTzSpecificLocalTime (NULL, &systime, &lsystime2))
+      return 6;
+  }
+  printf ("Windows API 2: %04d-%02d-%02d %02d:%02d:%02d\n",
+          lsystime2.wYear, lsystime2.wMonth, lsystime2.wDay,
+          lsystime2.wHour, lsystime2.wMinute, lsystime2.wSecond);
+
+  /* Both methods should be equivalent.  */
+  if (lsystime1.wHour != lsystime2.wHour)
+    return 7;
+
+  /* Now see whether localtime_r is consistent with that.  */
+  struct tm lm;
+  if (localtime_r (&seconds_since_1970_01_01, &lm) == NULL)
+    return 8;
+  printf ("localtime_r:   %04d-%02d-%02d %02d:%02d:%02d (DST=%d)\n",
+          lm.tm_year + 1900, lm.tm_mon + 1, lm.tm_mday,
+          lm.tm_hour, lm.tm_min, lm.tm_sec,
+          !!lm.tm_isdst);
+  fflush (stdout);
+  ASSERT (lm.tm_year + 1900 == lsystime1.wYear);
+  ASSERT (lm.tm_mon + 1 == lsystime1.wMonth);
+  ASSERT (lm.tm_mday == lsystime1.wDay);
+  ASSERT (lm.tm_hour == lsystime1.wHour);
+  ASSERT (lm.tm_min == lsystime1.wMinute);
+  ASSERT (lm.tm_sec == lsystime1.wSecond);
+
+  return test_exit_status;
+}
-- 
2.53.0

>From 8381dafd560d71bae4406f09d2590636da760a20 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Thu, 1 Oct 2026 15:35:28 +0200
Subject: [PATCH 4/4] mktime: Add tests.

* tests/test-mktime-w32.c: New file.
* modules/mktime-tests: New file.
---
 ChangeLog               |   6 ++
 modules/mktime-tests    |  14 ++++
 tests/test-mktime-w32.c | 143 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 163 insertions(+)
 create mode 100644 modules/mktime-tests
 create mode 100644 tests/test-mktime-w32.c

diff --git a/ChangeLog b/ChangeLog
index baa0953592..a16f35bcc3 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2026-10-01  Bruno Haible  <[email protected]>
+
+	mktime: Add tests.
+	* tests/test-mktime-w32.c: New file.
+	* modules/mktime-tests: New file.
+
 2026-10-01  Bruno Haible  <[email protected]>
 
 	time_r tests: Add more tests.
diff --git a/modules/mktime-tests b/modules/mktime-tests
new file mode 100644
index 0000000000..b5bfa14683
--- /dev/null
+++ b/modules/mktime-tests
@@ -0,0 +1,14 @@
+Files:
+tests/test-mktime-w32.c
+tests/macros.h
+
+Depends-on:
+test-xfail
+
+configure.ac:
+
+Makefile.am:
+if OS_IS_NATIVE_WINDOWS
+TESTS += test-mktime-w32
+check_PROGRAMS += test-mktime-w32
+endif
diff --git a/tests/test-mktime-w32.c b/tests/test-mktime-w32.c
new file mode 100644
index 0000000000..8ff66c57aa
--- /dev/null
+++ b/tests/test-mktime-w32.c
@@ -0,0 +1,143 @@
+/* Test mktime() on native Windows.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <[email protected]>, 2026.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include <time.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define WIN32_LEAN_AND_MEAN  /* avoid including junk */
+#include <windows.h>
+
+#include "macros.h"
+
+int
+main (void)
+{
+  /* Check that when the TZ environment variable is set to a value understood
+     by Cygwin but not by native Windows, the mktime function is still
+     consistent with the Windows API functions that uses the time zone settings
+     from the System Settings / Control Panel.  */
+
+  /* Set the TZ environment variable.
+     Documentation:
+     <https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setenvironmentvariable>
+     <https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/putenv-wputenv>  */
+  if (!SetEnvironmentVariable ("TZ", "Europe/Berlin"))
+    return 2;
+  if (_putenv ("TZ=Europe/Berlin"))
+    return 2;
+
+  /* Pick a specific time point, not too far away in the past.  */
+  struct tm lm;
+  /* 2026-09-12 08:00:00 local time */
+  lm.tm_year = 2026 - 1900;
+  lm.tm_mon = 9 - 1;
+  lm.tm_mday = 12;
+  lm.tm_hour = 8;
+  lm.tm_min = 0;
+  lm.tm_sec = 0;
+
+  /* Convert it to UTC through the Windows API functions.  */
+  SYSTEMTIME lsystime;
+  lsystime.wYear = lm.tm_year + 1900;
+  lsystime.wMonth = lm.tm_mon + 1;
+  lsystime.wDay = lm.tm_mday;
+  lsystime.wHour = lm.tm_hour;
+  lsystime.wMinute = lm.tm_min;
+  lsystime.wSecond = lm.tm_sec;
+  lsystime.wMilliseconds = 0;
+
+  /* Convert like GNU clisp does.  */
+  time_t utctime1;
+  {
+    FILETIME lfiletime;
+    FILETIME filetime;
+    /* Documentation:
+       <https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-systemtimetofiletime>  */
+    if (!SystemTimeToFileTime (&lsystime, &lfiletime))
+      return 3;
+    /* Documentation:
+       <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-localfiletimetofiletime>  */
+    if (!LocalFileTimeToFileTime (&lfiletime, &filetime))
+      return 4;
+    long long hundrednanos_since_1601_01_01 =
+      ((unsigned long long) filetime.dwHighDateTime << 32)
+      | (unsigned long long) filetime.dwLowDateTime;
+    utctime1 = (hundrednanos_since_1601_01_01 / 10000000LL) - 134774LL * 24LL * 3600LL;
+  }
+  printf ("Windows API 1: %lld\n", (long long) utctime1);
+
+  /* Convert like
+     <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-filetimetolocalfiletime>
+     suggests.  */
+  time_t utctime2;
+  {
+    SYSTEMTIME systime;
+    FILETIME filetime;
+    /* Documentation:
+       <https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-tzspecificlocaltimetosystemtime>  */
+    if (!TzSpecificLocalTimeToSystemTime (NULL, &lsystime, &systime))
+      return 5;
+    /* Documentation:
+       <https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-systemtimetofiletime>  */
+    if (!SystemTimeToFileTime (&systime, &filetime))
+      return 6;
+    long long hundrednanos_since_1601_01_01 =
+      ((unsigned long long) filetime.dwHighDateTime << 32)
+      | (unsigned long long) filetime.dwLowDateTime;
+    utctime2 = (hundrednanos_since_1601_01_01 / 10000000LL) - 134774LL * 24LL * 3600LL;
+  }
+  printf ("Windows API 2: %lld\n", (long long) utctime2);
+
+  /* Both methods should be equivalent.  */
+  if (utctime1 != utctime2)
+    return 7;
+
+  /* Now see whether mktime (a.k.a. timelocal) is consistent with that.  */
+  /* We need to invoke mktime up to twice, because the initial guess of tm_isdst
+     may be incorrect.
+     Documentation:
+     <https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/mktime-mktime32-mktime64>  */
+  time_t utctime;
+  {
+    struct tm tm1 = lm;
+    tm1.tm_isdst = 0; /* initial guess */
+    utctime = mktime (&tm1);
+    if (utctime == (time_t) -1)
+      return 8;
+    if (tm1.tm_isdst != 0)
+      {
+        struct tm tm2 = lm;
+        tm2.tm_isdst = tm1.tm_isdst;
+        utctime = mktime (&tm2);
+        if (utctime == (time_t) -1)
+          return 9;
+      }
+  }
+  printf ("mktime:        %lld\n", (long long) utctime);
+  /* Just for comparison, print the interpretation as UTC time.  */
+  printf ("timegm:        %lld\n", (long long) (497000 * 3600));
+  fflush (stdout);
+  ASSERT (utctime == utctime1);
+
+  return test_exit_status;
+}
-- 
2.53.0

Reply via email to