On native Windows (mingw and MSVC), the test 'test-parse-datetime' fails. There are three problems that need to be worked around: - The test exercises time points around the Unix epoch. However, localtime() fails for negative arguments. - Native Windows does not support time zone names with slashes. See lib/tzset.c. - After setting the TZ environment variable, it is necessary to call tzset(), otherwise the previous GMT offset continue to be used.
This patch fixes it all. 2024-05-27 Bruno Haible <br...@clisp.org> parse-datetime tests: Avoid failure on native Windows. * tests/test-parse-datetime.c (SOME_TIMEPOINT): New macro. (main): Use it. On native Windows, use Windows time zone names. Invoke tzset() after setting TZ. * doc/posix-functions/gmtime.texi: Mention the native Windows problem. * doc/posix-functions/localtime.texi: Likewise. diff --git a/doc/posix-functions/gmtime.texi b/doc/posix-functions/gmtime.texi index 7590fe9108..f31eeaf09f 100644 --- a/doc/posix-functions/gmtime.texi +++ b/doc/posix-functions/gmtime.texi @@ -16,4 +16,8 @@ On some platforms, this function yields incorrect values for timestamps before the year 1: MacOS X 10.5, Solaris 11.3. +@item +On some platforms, this function returns NULL for arguments < -43200, that is, +for timestamps before 1969-12-31 12:00:00 UTC: +mingw, MSVC. @end itemize diff --git a/doc/posix-functions/localtime.texi b/doc/posix-functions/localtime.texi index 0cad93c1b2..7ce348e286 100644 --- a/doc/posix-functions/localtime.texi +++ b/doc/posix-functions/localtime.texi @@ -29,6 +29,10 @@ timestamps before the year 1: MacOS X 10.5, Solaris 11.3. @item +On some platforms, this function returns NULL for negative arguments, that is, +for timestamps before 1970-01-01 00:00:00 local time: +mingw, MSVC. +@item Native Windows platforms (mingw, MSVC) support only a subset of time zones supported by GNU or specified by POSIX@. @xref{tzset}. @end itemize diff --git a/tests/test-parse-datetime.c b/tests/test-parse-datetime.c index 3b35cb70dd..330d5ea574 100644 --- a/tests/test-parse-datetime.c +++ b/tests/test-parse-datetime.c @@ -110,6 +110,17 @@ gmt_offset (time_t s) return gmtoff; } +/* Define SOME_TIMEPOINT to some tv_sec value that is supported by the + platform's localtime() function and that is on the same weekday as + the Unix epoch. */ +#if defined _WIN32 && !defined __CYGWIN__ +/* On native Windows, localtime() fails for all time_t values < 0. */ +# define SOME_TIMEPOINT (700 * 86400) +#else +/* The Unix epoch. */ +# define SOME_TIMEPOINT 0 +#endif + int main (_GL_UNUSED int argc, char **argv) { @@ -127,6 +138,7 @@ main (_GL_UNUSED int argc, char **argv) a problem with glibc on sites that default to leap seconds; see <https://bugs.gnu.org/12206>. */ ASSERT (setenv ("TZ", "EST5EDT,M3.2.0,M11.1.0", 1) == 0); + tzset (); gmtoff = gmt_offset (ref_time); @@ -225,14 +237,14 @@ main (_GL_UNUSED int argc, char **argv) && expected.tv_nsec == result.tv_nsec); - now.tv_sec = 4711; + now.tv_sec = SOME_TIMEPOINT + 4711; now.tv_nsec = 1267; p = "now"; ASSERT (parse_datetime (&result, p, &now)); LOG (p, now, result); ASSERT (now.tv_sec == result.tv_sec && now.tv_nsec == result.tv_nsec); - now.tv_sec = 4711; + now.tv_sec = SOME_TIMEPOINT + 4711; now.tv_nsec = 1267; p = "tomorrow"; ASSERT (parse_datetime (&result, p, &now)); @@ -240,7 +252,7 @@ main (_GL_UNUSED int argc, char **argv) ASSERT (now.tv_sec + 24 * 60 * 60 == result.tv_sec && now.tv_nsec == result.tv_nsec); - now.tv_sec = 4711; + now.tv_sec = SOME_TIMEPOINT + 4711; now.tv_nsec = 1267; p = "yesterday"; ASSERT (parse_datetime (&result, p, &now)); @@ -248,7 +260,7 @@ main (_GL_UNUSED int argc, char **argv) ASSERT (now.tv_sec - 24 * 60 * 60 == result.tv_sec && now.tv_nsec == result.tv_nsec); - now.tv_sec = 4711; + now.tv_sec = SOME_TIMEPOINT + 4711; now.tv_nsec = 1267; p = "4 hours"; ASSERT (parse_datetime (&result, p, &now)); @@ -257,7 +269,7 @@ main (_GL_UNUSED int argc, char **argv) && now.tv_nsec == result.tv_nsec); /* test if timezone is not being ignored for day offset */ - now.tv_sec = 4711; + now.tv_sec = SOME_TIMEPOINT + 4711; now.tv_nsec = 1267; p = "UTC+400 +24 hours"; ASSERT (parse_datetime (&result, p, &now)); @@ -269,7 +281,7 @@ main (_GL_UNUSED int argc, char **argv) && result.tv_nsec == result2.tv_nsec); /* test if several time zones formats are handled same way */ - now.tv_sec = 4711; + now.tv_sec = SOME_TIMEPOINT + 4711; now.tv_nsec = 1267; p = "UTC+14:00"; ASSERT (parse_datetime (&result, p, &now)); @@ -285,7 +297,7 @@ main (_GL_UNUSED int argc, char **argv) ASSERT (result.tv_sec == result2.tv_sec && result.tv_nsec == result2.tv_nsec); - now.tv_sec = 4711; + now.tv_sec = SOME_TIMEPOINT + 4711; now.tv_nsec = 1267; p = "UTC-14:00"; ASSERT (parse_datetime (&result, p, &now)); @@ -301,7 +313,7 @@ main (_GL_UNUSED int argc, char **argv) ASSERT (result.tv_sec == result2.tv_sec && result.tv_nsec == result2.tv_nsec); - now.tv_sec = 4711; + now.tv_sec = SOME_TIMEPOINT + 4711; now.tv_nsec = 1267; p = "UTC+0:15"; ASSERT (parse_datetime (&result, p, &now)); @@ -312,7 +324,7 @@ main (_GL_UNUSED int argc, char **argv) ASSERT (result.tv_sec == result2.tv_sec && result.tv_nsec == result2.tv_nsec); - now.tv_sec = 4711; + now.tv_sec = SOME_TIMEPOINT + 4711; now.tv_nsec = 1267; p = "UTC-1:30"; ASSERT (parse_datetime (&result, p, &now)); @@ -325,13 +337,13 @@ main (_GL_UNUSED int argc, char **argv) /* TZ out of range should cause parse_datetime failure */ - now.tv_sec = 4711; + now.tv_sec = SOME_TIMEPOINT + 4711; now.tv_nsec = 1267; p = "UTC+25:00"; ASSERT (!parse_datetime (&result, p, &now)); - /* Check for several invalid countable dayshifts */ - now.tv_sec = 4711; + /* Check for several invalid countable dayshifts */ + now.tv_sec = SOME_TIMEPOINT + 4711; now.tv_nsec = 1267; p = "UTC+4:00 +40 yesterday"; ASSERT (!parse_datetime (&result, p, &now)); @@ -349,7 +361,7 @@ main (_GL_UNUSED int argc, char **argv) ASSERT (!parse_datetime (&result, p, &now)); /* And check correct usage of dayshifts */ - now.tv_sec = 4711; + now.tv_sec = SOME_TIMEPOINT + 4711; now.tv_nsec = 1267; p = "UTC+400 tomorrow"; ASSERT (parse_datetime (&result, p, &now)); @@ -364,7 +376,7 @@ main (_GL_UNUSED int argc, char **argv) LOG (p, now, result2); ASSERT (result.tv_sec == result2.tv_sec && result.tv_nsec == result2.tv_nsec); - now.tv_sec = 4711; + now.tv_sec = SOME_TIMEPOINT + 4711; now.tv_nsec = 1267; p = "UTC+400 yesterday"; ASSERT (parse_datetime (&result, p, &now)); @@ -374,7 +386,7 @@ main (_GL_UNUSED int argc, char **argv) LOG (p, now, result2); ASSERT (result.tv_sec == result2.tv_sec && result.tv_nsec == result2.tv_nsec); - now.tv_sec = 4711; + now.tv_sec = SOME_TIMEPOINT + 4711; now.tv_nsec = 1267; p = "UTC+400 now"; ASSERT (parse_datetime (&result, p, &now)); @@ -386,7 +398,12 @@ main (_GL_UNUSED int argc, char **argv) && result.tv_nsec == result2.tv_nsec); /* If this platform has TZDB, check for GNU Bug#48085. */ +#if defined _WIN32 && !defined __CYGWIN__ + ASSERT (setenv ("TZ", "US Eastern Standard Time", 1) == 0); +#else ASSERT (setenv ("TZ", "America/Indiana/Indianapolis", 1) == 0); +#endif + tzset (); now.tv_sec = 1619641490; now.tv_nsec = 0; struct tm *tm = localtime (&now.tv_sec); @@ -404,9 +421,10 @@ main (_GL_UNUSED int argc, char **argv) /* Check that some "next Monday", "last Wednesday", etc. are correct. */ ASSERT (setenv ("TZ", "UTC0", 1) == 0); + tzset (); for (i = 0; day_table[i]; i++) { - unsigned int thur2 = 7 * 24 * 3600; /* 2nd thursday */ + unsigned int thur2 = SOME_TIMEPOINT + 7 * 24 * 3600; /* 2nd thursday */ char tmp[32]; sprintf (tmp, "NEXT %s", day_table[i]); now.tv_sec = thur2 + 4711; @@ -425,6 +443,8 @@ main (_GL_UNUSED int argc, char **argv) ASSERT (result.tv_sec == thur2 + ((i + 3) % 7 - 7) * 24 * 3600); } +/* On native Windows, localtime() fails for all time_t values < 0. */ +#if !(defined _WIN32 && !defined __CYGWIN__) p = "1970-12-31T23:59:59+00:00 - 1 year"; /* Bug#50115 */ now.tv_sec = -1; now.tv_nsec = 0; @@ -448,6 +468,7 @@ main (_GL_UNUSED int argc, char **argv) LOG (p, now, result); ASSERT (result.tv_sec == 24 * 3600 && result.tv_nsec == now.tv_nsec); +#endif /* Exercise a sign-extension bug. Before July 2012, an input starting with a high-bit-set byte would be treated like "0". */