usleep tests: Avoid failure due to known Cygwin 3.5.3 bug.
[gnulib.git] / tests / test-parse-datetime.c
blob330d5ea574e27c7c4e8e2ba2991e81c20c02b3e3
1 /* Test of parse_datetime() function.
2 Copyright (C) 2008-2024 Free Software Foundation, Inc.
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3, or (at your option)
7 any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, see <https://www.gnu.org/licenses/>. */
17 /* Written by Simon Josefsson <simon@josefsson.org>, 2008. */
19 #include <config.h>
21 #include "parse-datetime.h"
23 #include <errno.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
29 #include "macros.h"
31 #ifdef DEBUG
32 #define LOG(str, now, res) \
33 printf ("string '%s' diff %d %d\n", \
34 str, res.tv_sec - now.tv_sec, res.tv_nsec - now.tv_nsec);
35 #else
36 #define LOG(str, now, res) (void) 0
37 #endif
39 static const char *const day_table[] =
41 "SUNDAY",
42 "MONDAY",
43 "TUESDAY",
44 "WEDNESDAY",
45 "THURSDAY",
46 "FRIDAY",
47 "SATURDAY",
48 NULL
52 #if ! HAVE_TM_GMTOFF
53 /* Shift A right by B bits portably, by dividing A by 2**B and
54 truncating towards minus infinity. A and B should be free of side
55 effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
56 INT_BITS is the number of useful bits in an int. GNU code can
57 assume that INT_BITS is at least 32.
59 ISO C99 says that A >> B is implementation-defined if A < 0. Some
60 implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
61 right in the usual way when A < 0, so SHR falls back on division if
62 ordinary A >> B doesn't seem to be the usual signed shift. */
63 #define SHR(a, b) \
64 (-1 >> 1 == -1 \
65 ? (a) >> (b) \
66 : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0))
68 #define TM_YEAR_BASE 1900
70 /* Yield the difference between *A and *B,
71 measured in seconds, ignoring leap seconds.
72 The body of this function is taken directly from the GNU C Library;
73 see src/strftime.c. */
74 static long int
75 tm_diff (struct tm const *a, struct tm const *b)
77 /* Compute intervening leap days correctly even if year is negative.
78 Take care to avoid int overflow in leap day calculations. */
79 int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3);
80 int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3);
81 int a100 = a4 / 25 - (a4 % 25 < 0);
82 int b100 = b4 / 25 - (b4 % 25 < 0);
83 int a400 = SHR (a100, 2);
84 int b400 = SHR (b100, 2);
85 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
86 long int ayear = a->tm_year;
87 long int years = ayear - b->tm_year;
88 long int days = (365 * years + intervening_leap_days
89 + (a->tm_yday - b->tm_yday));
90 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
91 + (a->tm_min - b->tm_min))
92 + (a->tm_sec - b->tm_sec));
94 #endif /* ! HAVE_TM_GMTOFF */
96 static long
97 gmt_offset (time_t s)
99 long gmtoff;
101 #if !HAVE_TM_GMTOFF
102 struct tm tm_local = *localtime (&s);
103 struct tm tm_gmt = *gmtime (&s);
105 gmtoff = tm_diff (&tm_local, &tm_gmt);
106 #else
107 gmtoff = localtime (&s)->tm_gmtoff;
108 #endif
110 return gmtoff;
113 /* Define SOME_TIMEPOINT to some tv_sec value that is supported by the
114 platform's localtime() function and that is on the same weekday as
115 the Unix epoch. */
116 #if defined _WIN32 && !defined __CYGWIN__
117 /* On native Windows, localtime() fails for all time_t values < 0. */
118 # define SOME_TIMEPOINT (700 * 86400)
119 #else
120 /* The Unix epoch. */
121 # define SOME_TIMEPOINT 0
122 #endif
125 main (_GL_UNUSED int argc, char **argv)
127 struct timespec result;
128 struct timespec result2;
129 struct timespec expected;
130 struct timespec now;
131 const char *p;
132 int i;
133 long gmtoff;
134 time_t ref_time = 1304250918;
136 /* Set the time zone to US Eastern time with the 2012 rules. This
137 should disable any leap second support. Otherwise, there will be
138 a problem with glibc on sites that default to leap seconds; see
139 <https://bugs.gnu.org/12206>. */
140 ASSERT (setenv ("TZ", "EST5EDT,M3.2.0,M11.1.0", 1) == 0);
141 tzset ();
143 gmtoff = gmt_offset (ref_time);
146 /* ISO 8601 extended date and time of day representation,
147 'T' separator, local time zone */
148 p = "2011-05-01T11:55:18";
149 expected.tv_sec = ref_time - gmtoff;
150 expected.tv_nsec = 0;
151 ASSERT (parse_datetime (&result, p, 0));
152 LOG (p, expected, result);
153 ASSERT (expected.tv_sec == result.tv_sec
154 && expected.tv_nsec == result.tv_nsec);
156 /* ISO 8601 extended date and time of day representation,
157 ' ' separator, local time zone */
158 p = "2011-05-01 11:55:18";
159 expected.tv_sec = ref_time - gmtoff;
160 expected.tv_nsec = 0;
161 ASSERT (parse_datetime (&result, p, 0));
162 LOG (p, expected, result);
163 ASSERT (expected.tv_sec == result.tv_sec
164 && expected.tv_nsec == result.tv_nsec);
166 /* ISO 8601 extended date and time of day representation,
167 ' ' separator, 'J' (local) time zone */
168 p = "2011-05-01 11:55:18J";
169 expected.tv_sec = ref_time - gmtoff;
170 expected.tv_nsec = 0;
171 ASSERT (parse_datetime (&result, p, 0));
172 LOG (p, expected, result);
173 ASSERT (expected.tv_sec == result.tv_sec
174 && expected.tv_nsec == result.tv_nsec);
177 /* ISO 8601, extended date and time of day representation,
178 'T' separator, UTC */
179 p = "2011-05-01T11:55:18Z";
180 expected.tv_sec = ref_time;
181 expected.tv_nsec = 0;
182 ASSERT (parse_datetime (&result, p, 0));
183 LOG (p, expected, result);
184 ASSERT (expected.tv_sec == result.tv_sec
185 && expected.tv_nsec == result.tv_nsec);
187 /* ISO 8601, extended date and time of day representation,
188 ' ' separator, UTC */
189 p = "2011-05-01 11:55:18Z";
190 expected.tv_sec = ref_time;
191 expected.tv_nsec = 0;
192 ASSERT (parse_datetime (&result, p, 0));
193 LOG (p, expected, result);
194 ASSERT (expected.tv_sec == result.tv_sec
195 && expected.tv_nsec == result.tv_nsec);
198 /* ISO 8601 extended date and time of day representation,
199 'T' separator, w/UTC offset */
200 p = "2011-05-01T11:55:18-07:00";
201 expected.tv_sec = 1304276118;
202 expected.tv_nsec = 0;
203 ASSERT (parse_datetime (&result, p, 0));
204 LOG (p, expected, result);
205 ASSERT (expected.tv_sec == result.tv_sec
206 && expected.tv_nsec == result.tv_nsec);
208 /* ISO 8601 extended date and time of day representation,
209 ' ' separator, w/UTC offset */
210 p = "2011-05-01 11:55:18-07:00";
211 expected.tv_sec = 1304276118;
212 expected.tv_nsec = 0;
213 ASSERT (parse_datetime (&result, p, 0));
214 LOG (p, expected, result);
215 ASSERT (expected.tv_sec == result.tv_sec
216 && expected.tv_nsec == result.tv_nsec);
219 /* ISO 8601 extended date and time of day representation,
220 'T' separator, w/hour only UTC offset */
221 p = "2011-05-01T11:55:18-07";
222 expected.tv_sec = 1304276118;
223 expected.tv_nsec = 0;
224 ASSERT (parse_datetime (&result, p, 0));
225 LOG (p, expected, result);
226 ASSERT (expected.tv_sec == result.tv_sec
227 && expected.tv_nsec == result.tv_nsec);
229 /* ISO 8601 extended date and time of day representation,
230 ' ' separator, w/hour only UTC offset */
231 p = "2011-05-01 11:55:18-07";
232 expected.tv_sec = 1304276118;
233 expected.tv_nsec = 0;
234 ASSERT (parse_datetime (&result, p, 0));
235 LOG (p, expected, result);
236 ASSERT (expected.tv_sec == result.tv_sec
237 && expected.tv_nsec == result.tv_nsec);
240 now.tv_sec = SOME_TIMEPOINT + 4711;
241 now.tv_nsec = 1267;
242 p = "now";
243 ASSERT (parse_datetime (&result, p, &now));
244 LOG (p, now, result);
245 ASSERT (now.tv_sec == result.tv_sec && now.tv_nsec == result.tv_nsec);
247 now.tv_sec = SOME_TIMEPOINT + 4711;
248 now.tv_nsec = 1267;
249 p = "tomorrow";
250 ASSERT (parse_datetime (&result, p, &now));
251 LOG (p, now, result);
252 ASSERT (now.tv_sec + 24 * 60 * 60 == result.tv_sec
253 && now.tv_nsec == result.tv_nsec);
255 now.tv_sec = SOME_TIMEPOINT + 4711;
256 now.tv_nsec = 1267;
257 p = "yesterday";
258 ASSERT (parse_datetime (&result, p, &now));
259 LOG (p, now, result);
260 ASSERT (now.tv_sec - 24 * 60 * 60 == result.tv_sec
261 && now.tv_nsec == result.tv_nsec);
263 now.tv_sec = SOME_TIMEPOINT + 4711;
264 now.tv_nsec = 1267;
265 p = "4 hours";
266 ASSERT (parse_datetime (&result, p, &now));
267 LOG (p, now, result);
268 ASSERT (now.tv_sec + 4 * 60 * 60 == result.tv_sec
269 && now.tv_nsec == result.tv_nsec);
271 /* test if timezone is not being ignored for day offset */
272 now.tv_sec = SOME_TIMEPOINT + 4711;
273 now.tv_nsec = 1267;
274 p = "UTC+400 +24 hours";
275 ASSERT (parse_datetime (&result, p, &now));
276 LOG (p, now, result);
277 p = "UTC+400 +1 day";
278 ASSERT (parse_datetime (&result2, p, &now));
279 LOG (p, now, result2);
280 ASSERT (result.tv_sec == result2.tv_sec
281 && result.tv_nsec == result2.tv_nsec);
283 /* test if several time zones formats are handled same way */
284 now.tv_sec = SOME_TIMEPOINT + 4711;
285 now.tv_nsec = 1267;
286 p = "UTC+14:00";
287 ASSERT (parse_datetime (&result, p, &now));
288 LOG (p, now, result);
289 p = "UTC+14";
290 ASSERT (parse_datetime (&result2, p, &now));
291 LOG (p, now, result2);
292 ASSERT (result.tv_sec == result2.tv_sec
293 && result.tv_nsec == result2.tv_nsec);
294 p = "UTC+1400";
295 ASSERT (parse_datetime (&result2, p, &now));
296 LOG (p, now, result2);
297 ASSERT (result.tv_sec == result2.tv_sec
298 && result.tv_nsec == result2.tv_nsec);
300 now.tv_sec = SOME_TIMEPOINT + 4711;
301 now.tv_nsec = 1267;
302 p = "UTC-14:00";
303 ASSERT (parse_datetime (&result, p, &now));
304 LOG (p, now, result);
305 p = "UTC-14";
306 ASSERT (parse_datetime (&result2, p, &now));
307 LOG (p, now, result2);
308 ASSERT (result.tv_sec == result2.tv_sec
309 && result.tv_nsec == result2.tv_nsec);
310 p = "UTC-1400";
311 ASSERT (parse_datetime (&result2, p, &now));
312 LOG (p, now, result2);
313 ASSERT (result.tv_sec == result2.tv_sec
314 && result.tv_nsec == result2.tv_nsec);
316 now.tv_sec = SOME_TIMEPOINT + 4711;
317 now.tv_nsec = 1267;
318 p = "UTC+0:15";
319 ASSERT (parse_datetime (&result, p, &now));
320 LOG (p, now, result);
321 p = "UTC+0015";
322 ASSERT (parse_datetime (&result2, p, &now));
323 LOG (p, now, result2);
324 ASSERT (result.tv_sec == result2.tv_sec
325 && result.tv_nsec == result2.tv_nsec);
327 now.tv_sec = SOME_TIMEPOINT + 4711;
328 now.tv_nsec = 1267;
329 p = "UTC-1:30";
330 ASSERT (parse_datetime (&result, p, &now));
331 LOG (p, now, result);
332 p = "UTC-130";
333 ASSERT (parse_datetime (&result2, p, &now));
334 LOG (p, now, result2);
335 ASSERT (result.tv_sec == result2.tv_sec
336 && result.tv_nsec == result2.tv_nsec);
339 /* TZ out of range should cause parse_datetime failure */
340 now.tv_sec = SOME_TIMEPOINT + 4711;
341 now.tv_nsec = 1267;
342 p = "UTC+25:00";
343 ASSERT (!parse_datetime (&result, p, &now));
345 /* Check for several invalid countable dayshifts */
346 now.tv_sec = SOME_TIMEPOINT + 4711;
347 now.tv_nsec = 1267;
348 p = "UTC+4:00 +40 yesterday";
349 ASSERT (!parse_datetime (&result, p, &now));
350 p = "UTC+4:00 next yesterday";
351 ASSERT (!parse_datetime (&result, p, &now));
352 p = "UTC+4:00 tomorrow ago";
353 ASSERT (!parse_datetime (&result, p, &now));
354 p = "UTC+4:00 tomorrow hence";
355 ASSERT (!parse_datetime (&result, p, &now));
356 p = "UTC+4:00 40 now ago";
357 ASSERT (!parse_datetime (&result, p, &now));
358 p = "UTC+4:00 last tomorrow";
359 ASSERT (!parse_datetime (&result, p, &now));
360 p = "UTC+4:00 -4 today";
361 ASSERT (!parse_datetime (&result, p, &now));
363 /* And check correct usage of dayshifts */
364 now.tv_sec = SOME_TIMEPOINT + 4711;
365 now.tv_nsec = 1267;
366 p = "UTC+400 tomorrow";
367 ASSERT (parse_datetime (&result, p, &now));
368 LOG (p, now, result);
369 p = "UTC+400 +1 day";
370 ASSERT (parse_datetime (&result2, p, &now));
371 LOG (p, now, result2);
372 ASSERT (result.tv_sec == result2.tv_sec
373 && result.tv_nsec == result2.tv_nsec);
374 p = "UTC+400 1 day hence";
375 ASSERT (parse_datetime (&result2, p, &now));
376 LOG (p, now, result2);
377 ASSERT (result.tv_sec == result2.tv_sec
378 && result.tv_nsec == result2.tv_nsec);
379 now.tv_sec = SOME_TIMEPOINT + 4711;
380 now.tv_nsec = 1267;
381 p = "UTC+400 yesterday";
382 ASSERT (parse_datetime (&result, p, &now));
383 LOG (p, now, result);
384 p = "UTC+400 1 day ago";
385 ASSERT (parse_datetime (&result2, p, &now));
386 LOG (p, now, result2);
387 ASSERT (result.tv_sec == result2.tv_sec
388 && result.tv_nsec == result2.tv_nsec);
389 now.tv_sec = SOME_TIMEPOINT + 4711;
390 now.tv_nsec = 1267;
391 p = "UTC+400 now";
392 ASSERT (parse_datetime (&result, p, &now));
393 LOG (p, now, result);
394 p = "UTC+400 +0 minutes"; /* silly, but simple "UTC+400" is different*/
395 ASSERT (parse_datetime (&result2, p, &now));
396 LOG (p, now, result2);
397 ASSERT (result.tv_sec == result2.tv_sec
398 && result.tv_nsec == result2.tv_nsec);
400 /* If this platform has TZDB, check for GNU Bug#48085. */
401 #if defined _WIN32 && !defined __CYGWIN__
402 ASSERT (setenv ("TZ", "US Eastern Standard Time", 1) == 0);
403 #else
404 ASSERT (setenv ("TZ", "America/Indiana/Indianapolis", 1) == 0);
405 #endif
406 tzset ();
407 now.tv_sec = 1619641490;
408 now.tv_nsec = 0;
409 struct tm *tm = localtime (&now.tv_sec);
410 if (tm && tm->tm_year == 2021 - 1900 && tm->tm_mon == 4 - 1
411 && tm->tm_mday == 28 && tm->tm_hour == 16 && tm->tm_min == 24
412 && 0 < tm->tm_isdst)
414 int has_leap_seconds = tm->tm_sec != now.tv_sec % 60;
415 p = "now - 35 years";
416 ASSERT (parse_datetime (&result, p, &now));
417 LOG (p, now, result);
418 ASSERT (result.tv_sec
419 == 515107490 - 60 * 60 + (has_leap_seconds ? 13 : 0));
422 /* Check that some "next Monday", "last Wednesday", etc. are correct. */
423 ASSERT (setenv ("TZ", "UTC0", 1) == 0);
424 tzset ();
425 for (i = 0; day_table[i]; i++)
427 unsigned int thur2 = SOME_TIMEPOINT + 7 * 24 * 3600; /* 2nd thursday */
428 char tmp[32];
429 sprintf (tmp, "NEXT %s", day_table[i]);
430 now.tv_sec = thur2 + 4711;
431 now.tv_nsec = 1267;
432 ASSERT (parse_datetime (&result, tmp, &now));
433 LOG (tmp, now, result);
434 ASSERT (result.tv_nsec == 0);
435 ASSERT (result.tv_sec == thur2 + (i == 4 ? 7 : (i + 3) % 7) * 24 * 3600);
437 sprintf (tmp, "LAST %s", day_table[i]);
438 now.tv_sec = thur2 + 4711;
439 now.tv_nsec = 1267;
440 ASSERT (parse_datetime (&result, tmp, &now));
441 LOG (tmp, now, result);
442 ASSERT (result.tv_nsec == 0);
443 ASSERT (result.tv_sec == thur2 + ((i + 3) % 7 - 7) * 24 * 3600);
446 /* On native Windows, localtime() fails for all time_t values < 0. */
447 #if !(defined _WIN32 && !defined __CYGWIN__)
448 p = "1970-12-31T23:59:59+00:00 - 1 year"; /* Bug#50115 */
449 now.tv_sec = -1;
450 now.tv_nsec = 0;
451 ASSERT (parse_datetime (&result, p, &now));
452 LOG (p, now, result);
453 ASSERT (result.tv_sec == now.tv_sec
454 && result.tv_nsec == now.tv_nsec);
456 p = "THURSDAY UTC+00"; /* The epoch was on Thursday. */
457 now.tv_sec = 0;
458 now.tv_nsec = 0;
459 ASSERT (parse_datetime (&result, p, &now));
460 LOG (p, now, result);
461 ASSERT (result.tv_sec == now.tv_sec
462 && result.tv_nsec == now.tv_nsec);
464 p = "FRIDAY UTC+00";
465 now.tv_sec = 0;
466 now.tv_nsec = 0;
467 ASSERT (parse_datetime (&result, p, &now));
468 LOG (p, now, result);
469 ASSERT (result.tv_sec == 24 * 3600
470 && result.tv_nsec == now.tv_nsec);
471 #endif
473 /* Exercise a sign-extension bug. Before July 2012, an input
474 starting with a high-bit-set byte would be treated like "0". */
475 ASSERT ( ! parse_datetime (&result, "\xb0", &now));
477 /* Exercise TZ="" parsing code. */
478 /* These two would infloop or segfault before Feb 2014. */
479 ASSERT ( ! parse_datetime (&result, "TZ=\"\"\"", &now));
480 ASSERT ( ! parse_datetime (&result, "TZ=\"\" \"", &now));
481 /* Exercise invalid patterns. */
482 ASSERT ( ! parse_datetime (&result, "TZ=\"", &now));
483 ASSERT ( ! parse_datetime (&result, "TZ=\"\\\"", &now));
484 ASSERT ( ! parse_datetime (&result, "TZ=\"\\n", &now));
485 ASSERT ( ! parse_datetime (&result, "TZ=\"\\n\"", &now));
486 /* Exercise valid patterns. */
487 ASSERT ( parse_datetime (&result, "TZ=\"\"", &now));
488 ASSERT ( parse_datetime (&result, "TZ=\"\" ", &now));
489 ASSERT ( parse_datetime (&result, " TZ=\"\"", &now));
490 /* Exercise patterns which may be valid or invalid, depending on the
491 platform. */
492 #if !defined __NetBSD__
493 ASSERT ( parse_datetime (&result, "TZ=\"\\\\\"", &now));
494 ASSERT ( parse_datetime (&result, "TZ=\"\\\"\"", &now));
495 #endif
497 /* Outlandishly-long time zone abbreviations should not cause problems. */
499 static char const bufprefix[] = "TZ=\"";
500 long int tzname_max = -1;
501 errno = 0;
502 #ifdef _SC_TZNAME_MAX
503 tzname_max = sysconf (_SC_TZNAME_MAX);
504 #endif
505 enum { tzname_alloc = 2000 };
506 if (tzname_max < 0)
507 tzname_max = errno ? 6 : tzname_alloc;
508 int tzname_len = tzname_alloc < tzname_max ? tzname_alloc : tzname_max;
509 static char const bufsuffix[] = "0\" 1970-01-01 01:02:03.123456789";
510 enum { bufsize = sizeof bufprefix - 1 + tzname_alloc + sizeof bufsuffix };
511 char buf[bufsize];
512 memcpy (buf, bufprefix, sizeof bufprefix - 1);
513 memset (buf + sizeof bufprefix - 1, 'X', tzname_len);
514 strcpy (buf + sizeof bufprefix - 1 + tzname_len, bufsuffix);
515 ASSERT (parse_datetime (&result, buf, &now));
516 LOG (buf, now, result);
517 ASSERT (result.tv_sec == 1 * 60 * 60 + 2 * 60 + 3
518 && result.tv_nsec == 123456789);
521 return test_exit_status;