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 */