On Mon, 17 Nov 2025, Pali Rohár wrote:

On Monday 17 November 2025 16:18:14 Martin Storsjö wrote:
On Sat, 25 Oct 2025, Pali Rohár wrote:

---
mingw-w64-crt/testcases/t_time.c | 96 ++++++++++++++++++++++++++++++--
1 file changed, 92 insertions(+), 4 deletions(-)

diff --git a/mingw-w64-crt/testcases/t_time.c b/mingw-w64-crt/testcases/t_time.c
index 50b07d0083a7..75a15d816d31 100644
--- a/mingw-w64-crt/testcases/t_time.c
+++ b/mingw-w64-crt/testcases/t_time.c
@@ -10,14 +10,17 @@ int __cdecl ftime64(struct __timeb64 *tb64);

int main()
{
-  time_t t;
-  __time32_t t32;
-  __time64_t t64;
+  time_t t, t_;
+  __time32_t t32, t32_;
+  __time64_t t64, t64_;
  struct timeb tb;
  struct _timeb tb_;
  struct __timeb32 tb32;
  struct __timeb64 tb64;
  struct tm *htm;
+  struct tm tm1;
+  struct tm tm2;
+  struct tm tm3;
  int ret1, ret2, ret3;
  const char *str;
  const wchar_t *wstr;
@@ -95,7 +98,49 @@ int main()
  assert (htm->tm_yday == 74);
  assert (htm->tm_isdst == 0);

-  /* ctime returns time string in local timezone, so set local timezone to UTC 
to have test timezone independent */
+  time_t times[] = {
+    1700000000 /* Tue Nov 14 22:13:20 UTC 2023 */,
+    1600000000 /* Sun Sep 13 12:26:40 UTC 2020 */,
+  };
+  for (size_t i = 0; i < sizeof(times)/sizeof(*times); i++) {
+    t = times[i];
+    htm = localtime (&t);
+    tm1 = *htm;
+    printf ("localtime(%lld):    sec=%d min=%d hour=%d mday=%d mon=%d year=%d 
wday=%d yday=%d isdst=%d\n",
+        (long long)t, htm->tm_sec, htm->tm_min, htm->tm_hour, htm->tm_mday, htm->tm_mon, 
htm->tm_year, htm->tm_wday, htm->tm_yday, htm->tm_isdst);
+    t_ = mktime (htm);
+    printf ("mktime(): %lld      sec=%d min=%d hour=%d mday=%d mon=%d year=%d 
wday=%d yday=%d isdst=%d\n",
+        (long long)t_, htm->tm_sec, htm->tm_min, htm->tm_hour, htm->tm_mday, htm->tm_mon, 
htm->tm_year, htm->tm_wday, htm->tm_yday, htm->tm_isdst);
+    assert (t_ == t);
+    assert (memcmp (htm, &tm1, sizeof(tm1)) == 0);
+
+    t32 = t;
+    htm = _localtime32 (&t32);
+    tm2 = *htm;
+    printf ("_localtime32(%d): sec=%d min=%d hour=%d mday=%d mon=%d year=%d wday=%d 
yday=%d isdst=%d\n",
+        t32, htm->tm_sec, htm->tm_min, htm->tm_hour, htm->tm_mday, htm->tm_mon, 
htm->tm_year, htm->tm_wday, htm->tm_yday, htm->tm_isdst);
+    t32_ = _mktime32 (htm);
+    printf ("_mktime32(): %d   sec=%d min=%d hour=%d mday=%d mon=%d year=%d wday=%d 
yday=%d isdst=%d\n",
+        t32_, htm->tm_sec, htm->tm_min, htm->tm_hour, htm->tm_mday, htm->tm_mon, 
htm->tm_year, htm->tm_wday, htm->tm_yday, htm->tm_isdst);
+    assert (t32_ == t32);
+    assert (memcmp (htm, &tm2, sizeof(tm2)) == 0);
+
+    t64 = t;
+    htm = _localtime64 (&t64);
+    tm3 = *htm;
+    printf ("_localtime64(%lld): sec=%d min=%d hour=%d mday=%d mon=%d year=%d 
wday=%d yday=%d isdst=%d\n",
+        t64, htm->tm_sec, htm->tm_min, htm->tm_hour, htm->tm_mday, htm->tm_mon, 
htm->tm_year, htm->tm_wday, htm->tm_yday, htm->tm_isdst);
+    t64_ = _mktime64 (htm);
+    printf ("_mktime64(): %lld   sec=%d min=%d hour=%d mday=%d mon=%d year=%d 
wday=%d yday=%d isdst=%d\n",
+        t64_, htm->tm_sec, htm->tm_min, htm->tm_hour, htm->tm_mday, htm->tm_mon, 
htm->tm_year, htm->tm_wday, htm->tm_yday, htm->tm_isdst);
+    assert (t64_ == t64);
+    assert (memcmp (htm, &tm3, sizeof(tm3)) == 0);
+
+    assert (memcmp (&tm1, &tm2, sizeof(tm1)) == 0);
+    assert (memcmp (&tm2, &tm3, sizeof(tm2)) == 0);
+  }
+
+  /* ctime, localtime and mktime returns time string in local timezone, so set 
local timezone to UTC to have test timezone independent */
  putenv ("TZ=UTC");

In addition to setting TZ=UTC here, is it possible to set some other
well-known timezone that would be available everywhere, which we could use
for some testcases for cases closer to the DST switchover time? We could do
that for both mktime and localtime IMO.

I could try to add, but I'm not sure if CRT libraries would be able to
process DST support for non-system timezone. Because Windows system and
WinAPI has just one fixed timezone, which provides bunch of timezone and
DST related functions. And CRT library allows to define any timeshift,
so then there can be a mix of WinAPI timezone, CRT timezone, WinAPI DST
and CRT DST... That is why I wrote test below the quoted part in UTC as
it does not have any DST and no offset.

It does support setting a different timezone, but it seems a bit limited in exactly which aspects you can pass. It doesn't seem like I can set a custom timezone and get proper DST switchover time for other time zones than USA time. But that's enough for a testcase that does trigger the cases we're interested in here.

See my test code here:

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

void print(struct tm *tm) {
printf("date %d-%d-%d %d:%d:%d isdst %d", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_isdst);
#ifdef __GLIBC__
    printf(" gmtoff %ld", tm->tm_gmtoff);
#endif
    printf("\n");
}

void print_localtime(time_t t) {
    printf("time: %d\n", (int)t);
#ifdef _WIN32
    __time64_t t64 = t;
    struct tm *tm = _localtime64(&t64);
#else
    struct tm *tm = localtime(&t);
#endif
    print(tm);
}

int main(int argc, char **argv) {
    putenv("TZ=PST8PDT");
    tzset();
printf("timezone %ld daylight %d tzname %s %s\n", timezone, daylight, tzname[0], tzname[1]);

    struct tm input;
    input.tm_year = 2025 - 1900;
    input.tm_mon = 3 - 1;
    input.tm_mday = 9;
    input.tm_hour = 1;
    input.tm_min = 30;
    input.tm_sec = 0;
    input.tm_isdst = 0;
    print_localtime(mktime(&input));
    input.tm_hour = 3;
    input.tm_isdst = 1;
    print_localtime(mktime(&input));

    input.tm_mon = 11 - 1;
    input.tm_mday = 2;
    input.tm_hour = 1;
    input.tm_isdst = 1;
    print_localtime(mktime(&input));
    input.tm_isdst = 0;
    print_localtime(mktime(&input));

    return 0;
}

With this, I generate the time_t for the USA Pacific DST switch times this year, 30 minutes before and after the switch, and convert that back to a local time value.

I'm pretty sure this routine should work portably (the Wine source for parsing the TZ variable also handles it in the same way), and this string, TZ=PST8PDT is mentioned in the MS documentation as well.

So it would be good to include some case based on these time_t values in our testing. Specifically this testcase does showcase the missing "t32 += local_timezone;" and shows that it does fix the issue.

// Martin

_______________________________________________
Mingw-w64-public mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/mingw-w64-public

Reply via email to