I wrote:
> For these modules, the next function to provide in an MT-safe way is
> localtime_r.
Our gmtime_r and localtime_r are MT-safe on native Windows. I ran the
test-gmtime_r-mt and test-localtime_r-mt tests for 2000 seconds each, and
they did not crash.
But the problem is that localtime() and localtime_r() on native Windows
produce nonsensical results:
- They pretend that in France, in 2007, DST began on 2007-03-11. When in
fact, it started on 2007-03-25.
- The hour is wrong.
Witness: The attached program loc.c.
> On native Windows, when the 'localtime_s' function [1][2]
> is not available, such as on the older Windows versions that Emacs cares
> about, the solution is to use GetTimeZoneInformation [3].
None of the GetTimeZoneInformation APIs from the Windows DLLs works either.
They pretend that in German and French time zones, DST starts on March 5,
in all years. Witness: The attached program tzi.c.
So, there is no way around implementing a correct localtime_r, based on
tzdata, in Gnulib.
It will be useful
- for localtime_r on native Windows,
- for nstrftime, c_nstrftime, parse-datetime, which all take a timezone_t
argument.
For reading tzdata: The first question is how to include tzdata in gnulib.
- AFAICS, the main data file (without comments) is tzdata.zi and is about
100 KB large. It can be upgraded simply by copying the newest tzdata.zi
from a newer tzdata distribution. Including such a file in gnulib would
be OK (re copyright, number of files, total size), right?
- Whereas including all files from /usr/share/zoneinfo is probably not
acceptable (> 1300 files, ca. 6 MB total size).
- Access pattern: In a running program, very few among the time zones will
be used. Therefore, caching in memory is essential.
Bruno
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
/* Some common time zone name. */
#if defined _WIN32 && !defined __CYGWIN__
/* Cf. <https://ss64.com/timezones.html> */
# define FRENCH_TZ "Romance Standard Time"
#else
/* Cf. <https://en.wikipedia.org/wiki/List_of_tz_database_time_zones> */
//# define FRENCH_TZ "Europe/Paris"
# define FRENCH_TZ "Europe/Berlin"
#endif
static void
show_localtime_result (time_t t)
{
struct tm *result = localtime (&t);
printf ("%ld -> %04d-%02d-%02d %02d:%02d:%02d DST=%d\n",
(long) t,
result->tm_year + 1900, result->tm_mon + 1, result->tm_mday,
result->tm_hour, result->tm_min, result->tm_sec,
result->tm_isdst);
}
int
main (int argc, char *argv[])
{
#if defined _WIN32 && !defined __CYGWIN__
_putenv ("TZ" "=" FRENCH_TZ);
#else
setenv ("TZ", FRENCH_TZ, 1);
#endif
show_localtime_result (1173578399); /* 2007-03-11 02:59:59 */
show_localtime_result (1173578401); /* 2007-03-11 03:00:01 */
show_localtime_result (1174784399); /* 2007-03-25 01:59:59 */
show_localtime_result (1174784401); /* 2007-03-25 03:00:01 */
return 0;
}
/*
glibc, Cygwin:
1173578399 -> 2007-03-11 02:59:59 DST=0
1173578401 -> 2007-03-11 03:00:01 DST=0
1174784399 -> 2007-03-25 01:59:59 DST=0
1174784401 -> 2007-03-25 03:00:01 DST=1
Native Windows:
1173578399 -> 2007-03-11 01:59:59 DST=0
1173578401 -> 2007-03-11 03:00:01 DST=1
1174784399 -> 2007-03-25 01:59:59 DST=1
1174784401 -> 2007-03-25 02:00:01 DST=1
*/
#include <stdio.h>
#include <windows.h>
#include <timezoneapi.h>
#include <wchar.h>
/* Cf. <https://ss64.com/timezones.html> */
# define FRENCH_TZ "Romance Standard Time"
int main ()
{
_putenv ("TZ=" FRENCH_TZ);
DWORD ret;
TIME_ZONE_INFORMATION info1;
ret = GetTimeZoneInformation (&info1);
printf ("GetTimeZoneInformation:\n"
"ret = %lu info1 =\n"
"standard: |%ls| bias=%ld date=%4d-%02d-%02d %02d:%02d:%02d.%03d\n"
"daylight: |%ls| bias=%ld date=%4d-%02d-%02d %02d:%02d:%02d.%03d\n",
ret,
info1.StandardName,
info1.StandardBias,
info1.StandardDate.wYear,
info1.StandardDate.wMonth,
info1.StandardDate.wDay,
info1.StandardDate.wHour,
info1.StandardDate.wMinute,
info1.StandardDate.wSecond,
info1.StandardDate.wMilliseconds,
info1.DaylightName,
info1.DaylightBias,
info1.DaylightDate.wYear,
info1.DaylightDate.wMonth,
info1.DaylightDate.wDay,
info1.DaylightDate.wHour,
info1.DaylightDate.wMinute,
info1.DaylightDate.wSecond,
info1.DaylightDate.wMilliseconds);
DYNAMIC_TIME_ZONE_INFORMATION info2;
ret = GetDynamicTimeZoneInformation (&info2);
printf ("GetDynamicTimeZoneInformation:\n"
"ret = %lu info2 = bias=%ld timezonekey=%ls dynamicdisabled=%d\n"
"standard: |%ls| bias=%ld date=%4d-%02d-%02d %02d:%02d:%02d.%03d\n"
"daylight: |%ls| bias=%ld date=%4d-%02d-%02d %02d:%02d:%02d.%03d\n",
ret,
info2.Bias,
info2.TimeZoneKeyName,
info2.DynamicDaylightTimeDisabled,
info2.StandardName,
info2.StandardBias,
info2.StandardDate.wYear,
info2.StandardDate.wMonth,
info2.StandardDate.wDay,
info2.StandardDate.wHour,
info2.StandardDate.wMinute,
info2.StandardDate.wSecond,
info2.StandardDate.wMilliseconds,
info2.DaylightName,
info2.DaylightBias,
info2.DaylightDate.wYear,
info2.DaylightDate.wMonth,
info2.DaylightDate.wDay,
info2.DaylightDate.wHour,
info2.DaylightDate.wMinute,
info2.DaylightDate.wSecond,
info2.DaylightDate.wMilliseconds);
DYNAMIC_TIME_ZONE_INFORMATION info3i;
TIME_ZONE_INFORMATION info3;
{
DWORD i;
for (i = 0; ; i++)
{
if (EnumDynamicTimeZoneInformation (i, &info3i) == ERROR_SUCCESS // Link error in mingw, OK in MSVC.
&& wcscmp (info3i.TimeZoneKeyName, L"Romance Standard Time") == 0)
break;
}
}
ret = GetTimeZoneInformationForYear (2007, &info3i, &info3);
printf ("GetTimeZoneInformationForYear(2007):\n"
"ret = %lu info3 =\n"
"standard: |%ls| bias=%ld date=%4d-%02d-%02d %02d:%02d:%02d.%03d\n"
"daylight: |%ls| bias=%ld date=%4d-%02d-%02d %02d:%02d:%02d.%03d\n",
ret,
info3.StandardName,
info3.StandardBias,
info3.StandardDate.wYear,
info3.StandardDate.wMonth,
info3.StandardDate.wDay,
info3.StandardDate.wHour,
info3.StandardDate.wMinute,
info3.StandardDate.wSecond,
info3.StandardDate.wMilliseconds,
info3.DaylightName,
info3.DaylightBias,
info3.DaylightDate.wYear,
info3.DaylightDate.wMonth,
info3.DaylightDate.wDay,
info3.DaylightDate.wHour,
info3.DaylightDate.wMinute,
info3.DaylightDate.wSecond,
info3.DaylightDate.wMilliseconds);
}
/* Compile:
$CC tzi.c -Wall -D_WIN32_WINNT=_WIN32_WINNT_WIN8 -ladvapi32
*/
/* Results:
GetTimeZoneInformation:
ret = 1 info1 =
standard: |W. Europe Standard Time| bias=0 date= 0-10-05 03:00:00.000
daylight: |W. Europe Daylight Time| bias=-60 date= 0-03-05 02:00:00.000
GetDynamicTimeZoneInformation:
ret = 1 info2 = bias=-60 timezonekey=W. Europe Standard Time dynamicdisabled=0
standard: |W. Europe Standard Time| bias=0 date= 0-10-05 03:00:00.000
daylight: |W. Europe Daylight Time| bias=-60 date= 0-03-05 02:00:00.000
GetTimeZoneInformationForYear(2007):
ret = 1 info3 =
standard: |Romance Standard Time| bias=0 date= 0-10-05 03:00:00.000
daylight: |Romance Daylight Time| bias=-60 date= 0-03-05 02:00:00.000
*/