On Sat, 25 Oct 2025, Pali Rohár wrote:
Function _localtime64 is available since msvcr70.dll. For older msvcrt
versions provide mingw-w64 emulation via _gmtime64() function and
adjustment of _timezone variable (filled by _tzset() call). To check
whether daylight bias adjustment is required (via _dstbias variable), use
the _localtime32() function for the same day and month as passed timestamp
but with changed year to fit into the signed 32-bit timestamp. This expects
that the DST start and end days of the last addressable year matches also
for all other future years.
---
mingw-w64-crt/Makefile.am | 2 +
mingw-w64-crt/lib-common/msvcrt.def.in | 2 +-
mingw-w64-crt/misc/_localtime64.c | 87 ++++++++++++++++++++++++++
3 files changed, 90 insertions(+), 1 deletion(-)
create mode 100644 mingw-w64-crt/misc/_localtime64.c
diff --git a/mingw-w64-crt/Makefile.am b/mingw-w64-crt/Makefile.am
index 99cccab61770..92da88f12f74 100644
--- a/mingw-w64-crt/Makefile.am
+++ b/mingw-w64-crt/Makefile.am
@@ -581,6 +581,7 @@ src_msvcrt32=\
misc/_get_fmode.c \
misc/_gmtime64.c \
misc/_initterm_e.c \
+ misc/_localtime64.c \
misc/_mkgmtime32.c \
misc/_mkgmtime64.c \
misc/_set_doserrno.c \
@@ -892,6 +893,7 @@ src_pre_msvcr70=\
misc/_aligned_realloc.c \
misc/_ftime64.c \
misc/_gmtime64.c \
+ misc/_localtime64.c \
misc/_time64.c \
misc/strtoimax.c \
misc/strtoumax.c \
diff --git a/mingw-w64-crt/lib-common/msvcrt.def.in
b/mingw-w64-crt/lib-common/msvcrt.def.in
index a1adeee91ac0..d4f50f316b3b 100644
--- a/mingw-w64-crt/lib-common/msvcrt.def.in
+++ b/mingw-w64-crt/lib-common/msvcrt.def.in
@@ -1157,7 +1157,7 @@ F_NON_I386(_fstat64) ; i386 _fstat64 replaced by emu
F_NON_I386(_ftime64) ; i386 _ftime64 replaced by emu
_futime64
F_NON_I386(_gmtime64) ; i386 _gmtime64 replaced by emu
-_localtime64
+F_NON_I386(_localtime64) ; i386 _localtime64 replaced by emu
_mktime64
F_X86_ANY(_osplatform DATA)
F_NON_I386(_stat64) ; i386 _stat64 replaced by emu
diff --git a/mingw-w64-crt/misc/_localtime64.c
b/mingw-w64-crt/misc/_localtime64.c
new file mode 100644
index 000000000000..85792a7be958
--- /dev/null
+++ b/mingw-w64-crt/misc/_localtime64.c
@@ -0,0 +1,87 @@
+/**
+ * This file has no copyright assigned and is placed in the Public Domain.
+ * This file is part of the mingw-w64 runtime package.
+ * No warranty is given; refer to the file DISCLAIMER.PD within this package.
+ */
+
+#include <windows.h>
+#include <time.h>
+
+static struct tm *__cdecl emu__localtime64(const __time64_t *timeptr)
+{
+ struct tm *tmptr;
+ struct tm tm32;
+ int local_daylight;
+ long local_dstbias;
+ long local_timezone;
+ __time64_t t64;
+ __time32_t t32;
+
+ /* _tzset() initialize _daylight, _dstbias and _timezone variables,
+ * so it needs to be called before accessing those variables.
+ * If those variables are already initialized then _tzset() does
+ * not need to be called again. As _tzset() is an expensive call,
+ * guard repeated calls by static variable. As the _tzset() is a
+ * thread-safe call, the race condition is not a problem.
+ */
+ {
+ static volatile long tzset_called = 0;
+ if (!tzset_called) {
+ _tzset();
+ (void)InterlockedExchange(&tzset_called, 1);
+ }
+ }
+ local_daylight = *__daylight();
+ local_dstbias = *__dstbias();
+ local_timezone = *__timezone();
+
+ /* __localtime64() for the case when the timezone does not use DST */
+ t64 = *timeptr - local_timezone;
+ tmptr = _gmtime64(&t64);
+ if (!tmptr)
+ return NULL;
+
+ /* If the timezone use DST then it is needed to check if the DST is active
+ * for passed timestamp. To do that use the existing _localtime32()
function
+ * and its tm_isdst member of return value. As the _localtime32() function
+ * works only for time structure which can be represented by signed 32-bit
+ * time_t type, change year of the passed timestamp, so the timestamp can
+ * be represented by 32-bit type. This expects that the DST start and end
+ * days of the last addressable year matches also for all other future
years.
+ */
+ if (local_daylight) {
+ /* Prepare struct tm to be representable by 32-bit time_t value, just
by changing year */
+ tm32 = *tmptr;
+ if (tm32.tm_year > 2037-1900)
+ tm32.tm_year = 2037-1900;
+ else if (tm32.tm_year < 1971-1900)
+ tm32.tm_year = 1971-1900;
+
+ /* Use _localtime32()'s tm_isdst to determinate if the DST is active
for passed timestamp */
+ t32 = _mkgmtime32(&tm32);
+ if (t32 == -1)
+ return NULL;
+ tmptr = _localtime32(&t32);
+ if (!tmptr)
+ return NULL;
+
+ /* If the DST is active for passed timestamp then recalculate the
struct tm according to DST bias */
+ if (tmptr->tm_isdst) {
+ t64 -= local_dstbias;
+ tmptr = _gmtime64(&t64);
+ if (!tmptr)
+ return NULL;
+ tmptr->tm_isdst = 1;
+ } else {
+ tmptr = _gmtime64(&t64);
+ }
+ }
First off, I had my misgivings about using gmtime() for this - if we'd be
on a CRT with more fields in "struct tm", then we would need to fill in
the field tm_gmtoff from localtime() as well. But as I see that the MS
CRTs don't include such fields, this is probably mostly fine.
But there is one issue in the calculation of tm_isdst here.
You're doing "t64 = *timeptr - local_timezone" to fake the difference
between localtime and gmtime - while this makes the functions operate on a
different time_t value, offset by a few hours. If operating on a time
close to the DST switch time, then this can give the wrong results.
So to compensate for that, I think something like this is needed:
t32 = _mkgmtime32(&tm32);
if (t32 == -1)
return NULL;
+ t32 += local_timezone; /* Remove the fake timezone offset */
tmptr = _localtime32(&t32);
if (!tmptr)
return NULL;
Essentially, the _localtime32 call we use for determining whether we are
in DST or not, would need to operate on the original, non-offset time_t
value. (And for out of range values, the logic above for switching to a
different year, but for the same date/time.)
// Martin
_______________________________________________
Mingw-w64-public mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/mingw-w64-public