1 /* Test of parse_datetime() function.
2 Copyright (C) 2008-2023 Free Software Foundation, Inc.
3
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.
8
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.
13
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/>. */
16
17 /* Written by Simon Josefsson <simon@josefsson.org>, 2008. */
18
19 #include <config.h>
20
21 #include "parse-datetime.h"
22
23 #include <errno.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28
29 #include "macros.h"
30
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
38
39 static const char *const day_table[] =
40 {
41 "SUNDAY",
42 "MONDAY",
43 "TUESDAY",
44 "WEDNESDAY",
45 "THURSDAY",
46 "FRIDAY",
47 "SATURDAY",
48 NULL
49 };
50
51
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.
58
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))
67
68 #define TM_YEAR_BASE 1900
69
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)
76 {
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));
93 }
94 #endif /* ! HAVE_TM_GMTOFF */
95
96 static long
97 gmt_offset (time_t s)
98 {
99 long gmtoff;
100
101 #if !HAVE_TM_GMTOFF
102 struct tm tm_local = *localtime (&s);
103 struct tm tm_gmt = *gmtime (&s);
104
105 gmtoff = tm_diff (&tm_local, &tm_gmt);
106 #else
107 gmtoff = localtime (&s)->tm_gmtoff;
108 #endif
109
110 return gmtoff;
111 }
112
113 int
114 main (_GL_UNUSED int argc, char **argv)
115 {
116 struct timespec result;
117 struct timespec result2;
118 struct timespec expected;
119 struct timespec now;
120 const char *p;
121 int i;
122 long gmtoff;
123 time_t ref_time = 1304250918;
124
125 /* Set the time zone to US Eastern time with the 2012 rules. This
126 should disable any leap second support. Otherwise, there will be
127 a problem with glibc on sites that default to leap seconds; see
128 <https://bugs.gnu.org/12206>. */
129 ASSERT (setenv ("TZ", "EST5EDT,M3.2.0,M11.1.0", 1) == 0);
130
131 gmtoff = gmt_offset (ref_time);
132
133
134 /* ISO 8601 extended date and time of day representation,
135 'T' separator, local time zone */
136 p = "2011-05-01T11:55:18";
137 expected.tv_sec = ref_time - gmtoff;
138 expected.tv_nsec = 0;
139 ASSERT (parse_datetime (&result, p, 0));
140 LOG (p, expected, result);
141 ASSERT (expected.tv_sec == result.tv_sec
142 && expected.tv_nsec == result.tv_nsec);
143
144 /* ISO 8601 extended date and time of day representation,
145 ' ' separator, local time zone */
146 p = "2011-05-01 11:55:18";
147 expected.tv_sec = ref_time - gmtoff;
148 expected.tv_nsec = 0;
149 ASSERT (parse_datetime (&result, p, 0));
150 LOG (p, expected, result);
151 ASSERT (expected.tv_sec == result.tv_sec
152 && expected.tv_nsec == result.tv_nsec);
153
154 /* ISO 8601 extended date and time of day representation,
155 ' ' separator, 'J' (local) time zone */
156 p = "2011-05-01 11:55:18J";
157 expected.tv_sec = ref_time - gmtoff;
158 expected.tv_nsec = 0;
159 ASSERT (parse_datetime (&result, p, 0));
160 LOG (p, expected, result);
161 ASSERT (expected.tv_sec == result.tv_sec
162 && expected.tv_nsec == result.tv_nsec);
163
164
165 /* ISO 8601, extended date and time of day representation,
166 'T' separator, UTC */
167 p = "2011-05-01T11:55:18Z";
168 expected.tv_sec = ref_time;
169 expected.tv_nsec = 0;
170 ASSERT (parse_datetime (&result, p, 0));
171 LOG (p, expected, result);
172 ASSERT (expected.tv_sec == result.tv_sec
173 && expected.tv_nsec == result.tv_nsec);
174
175 /* ISO 8601, extended date and time of day representation,
176 ' ' separator, UTC */
177 p = "2011-05-01 11:55:18Z";
178 expected.tv_sec = ref_time;
179 expected.tv_nsec = 0;
180 ASSERT (parse_datetime (&result, p, 0));
181 LOG (p, expected, result);
182 ASSERT (expected.tv_sec == result.tv_sec
183 && expected.tv_nsec == result.tv_nsec);
184
185
186 /* ISO 8601 extended date and time of day representation,
187 'T' separator, w/UTC offset */
188 p = "2011-05-01T11:55:18-07:00";
189 expected.tv_sec = 1304276118;
190 expected.tv_nsec = 0;
191 ASSERT (parse_datetime (&result, p, 0));
192 LOG (p, expected, result);
193 ASSERT (expected.tv_sec == result.tv_sec
194 && expected.tv_nsec == result.tv_nsec);
195
196 /* ISO 8601 extended date and time of day representation,
197 ' ' separator, w/UTC offset */
198 p = "2011-05-01 11:55:18-07:00";
199 expected.tv_sec = 1304276118;
200 expected.tv_nsec = 0;
201 ASSERT (parse_datetime (&result, p, 0));
202 LOG (p, expected, result);
203 ASSERT (expected.tv_sec == result.tv_sec
204 && expected.tv_nsec == result.tv_nsec);
205
206
207 /* ISO 8601 extended date and time of day representation,
208 'T' separator, w/hour only UTC offset */
209 p = "2011-05-01T11:55:18-07";
210 expected.tv_sec = 1304276118;
211 expected.tv_nsec = 0;
212 ASSERT (parse_datetime (&result, p, 0));
213 LOG (p, expected, result);
214 ASSERT (expected.tv_sec == result.tv_sec
215 && expected.tv_nsec == result.tv_nsec);
216
217 /* ISO 8601 extended date and time of day representation,
218 ' ' separator, w/hour only UTC offset */
219 p = "2011-05-01 11:55:18-07";
220 expected.tv_sec = 1304276118;
221 expected.tv_nsec = 0;
222 ASSERT (parse_datetime (&result, p, 0));
223 LOG (p, expected, result);
224 ASSERT (expected.tv_sec == result.tv_sec
225 && expected.tv_nsec == result.tv_nsec);
226
227
228 now.tv_sec = 4711;
229 now.tv_nsec = 1267;
230 p = "now";
231 ASSERT (parse_datetime (&result, p, &now));
232 LOG (p, now, result);
233 ASSERT (now.tv_sec == result.tv_sec && now.tv_nsec == result.tv_nsec);
234
235 now.tv_sec = 4711;
236 now.tv_nsec = 1267;
237 p = "tomorrow";
238 ASSERT (parse_datetime (&result, p, &now));
239 LOG (p, now, result);
240 ASSERT (now.tv_sec + 24 * 60 * 60 == result.tv_sec
241 && now.tv_nsec == result.tv_nsec);
242
243 now.tv_sec = 4711;
244 now.tv_nsec = 1267;
245 p = "yesterday";
246 ASSERT (parse_datetime (&result, p, &now));
247 LOG (p, now, result);
248 ASSERT (now.tv_sec - 24 * 60 * 60 == result.tv_sec
249 && now.tv_nsec == result.tv_nsec);
250
251 now.tv_sec = 4711;
252 now.tv_nsec = 1267;
253 p = "4 hours";
254 ASSERT (parse_datetime (&result, p, &now));
255 LOG (p, now, result);
256 ASSERT (now.tv_sec + 4 * 60 * 60 == result.tv_sec
257 && now.tv_nsec == result.tv_nsec);
258
259 /* test if timezone is not being ignored for day offset */
260 now.tv_sec = 4711;
261 now.tv_nsec = 1267;
262 p = "UTC+400 +24 hours";
263 ASSERT (parse_datetime (&result, p, &now));
264 LOG (p, now, result);
265 p = "UTC+400 +1 day";
266 ASSERT (parse_datetime (&result2, p, &now));
267 LOG (p, now, result2);
268 ASSERT (result.tv_sec == result2.tv_sec
269 && result.tv_nsec == result2.tv_nsec);
270
271 /* test if several time zones formats are handled same way */
272 now.tv_sec = 4711;
273 now.tv_nsec = 1267;
274 p = "UTC+14:00";
275 ASSERT (parse_datetime (&result, p, &now));
276 LOG (p, now, result);
277 p = "UTC+14";
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);
282 p = "UTC+1400";
283 ASSERT (parse_datetime (&result2, p, &now));
284 LOG (p, now, result2);
285 ASSERT (result.tv_sec == result2.tv_sec
286 && result.tv_nsec == result2.tv_nsec);
287
288 now.tv_sec = 4711;
289 now.tv_nsec = 1267;
290 p = "UTC-14:00";
291 ASSERT (parse_datetime (&result, p, &now));
292 LOG (p, now, result);
293 p = "UTC-14";
294 ASSERT (parse_datetime (&result2, p, &now));
295 LOG (p, now, result2);
296 ASSERT (result.tv_sec == result2.tv_sec
297 && result.tv_nsec == result2.tv_nsec);
298 p = "UTC-1400";
299 ASSERT (parse_datetime (&result2, p, &now));
300 LOG (p, now, result2);
301 ASSERT (result.tv_sec == result2.tv_sec
302 && result.tv_nsec == result2.tv_nsec);
303
304 now.tv_sec = 4711;
305 now.tv_nsec = 1267;
306 p = "UTC+0:15";
307 ASSERT (parse_datetime (&result, p, &now));
308 LOG (p, now, result);
309 p = "UTC+0015";
310 ASSERT (parse_datetime (&result2, p, &now));
311 LOG (p, now, result2);
312 ASSERT (result.tv_sec == result2.tv_sec
313 && result.tv_nsec == result2.tv_nsec);
314
315 now.tv_sec = 4711;
316 now.tv_nsec = 1267;
317 p = "UTC-1:30";
318 ASSERT (parse_datetime (&result, p, &now));
319 LOG (p, now, result);
320 p = "UTC-130";
321 ASSERT (parse_datetime (&result2, p, &now));
322 LOG (p, now, result2);
323 ASSERT (result.tv_sec == result2.tv_sec
324 && result.tv_nsec == result2.tv_nsec);
325
326
327 /* TZ out of range should cause parse_datetime failure */
328 now.tv_sec = 4711;
329 now.tv_nsec = 1267;
330 p = "UTC+25:00";
331 ASSERT (!parse_datetime (&result, p, &now));
332
333 /* Check for several invalid countable dayshifts */
334 now.tv_sec = 4711;
335 now.tv_nsec = 1267;
336 p = "UTC+4:00 +40 yesterday";
337 ASSERT (!parse_datetime (&result, p, &now));
338 p = "UTC+4:00 next yesterday";
339 ASSERT (!parse_datetime (&result, p, &now));
340 p = "UTC+4:00 tomorrow ago";
341 ASSERT (!parse_datetime (&result, p, &now));
342 p = "UTC+4:00 tomorrow hence";
343 ASSERT (!parse_datetime (&result, p, &now));
344 p = "UTC+4:00 40 now ago";
345 ASSERT (!parse_datetime (&result, p, &now));
346 p = "UTC+4:00 last tomorrow";
347 ASSERT (!parse_datetime (&result, p, &now));
348 p = "UTC+4:00 -4 today";
349 ASSERT (!parse_datetime (&result, p, &now));
350
351 /* And check correct usage of dayshifts */
352 now.tv_sec = 4711;
353 now.tv_nsec = 1267;
354 p = "UTC+400 tomorrow";
355 ASSERT (parse_datetime (&result, p, &now));
356 LOG (p, now, result);
357 p = "UTC+400 +1 day";
358 ASSERT (parse_datetime (&result2, p, &now));
359 LOG (p, now, result2);
360 ASSERT (result.tv_sec == result2.tv_sec
361 && result.tv_nsec == result2.tv_nsec);
362 p = "UTC+400 1 day hence";
363 ASSERT (parse_datetime (&result2, p, &now));
364 LOG (p, now, result2);
365 ASSERT (result.tv_sec == result2.tv_sec
366 && result.tv_nsec == result2.tv_nsec);
367 now.tv_sec = 4711;
368 now.tv_nsec = 1267;
369 p = "UTC+400 yesterday";
370 ASSERT (parse_datetime (&result, p, &now));
371 LOG (p, now, result);
372 p = "UTC+400 1 day ago";
373 ASSERT (parse_datetime (&result2, p, &now));
374 LOG (p, now, result2);
375 ASSERT (result.tv_sec == result2.tv_sec
376 && result.tv_nsec == result2.tv_nsec);
377 now.tv_sec = 4711;
378 now.tv_nsec = 1267;
379 p = "UTC+400 now";
380 ASSERT (parse_datetime (&result, p, &now));
381 LOG (p, now, result);
382 p = "UTC+400 +0 minutes"; /* silly, but simple "UTC+400" is different*/
383 ASSERT (parse_datetime (&result2, p, &now));
384 LOG (p, now, result2);
385 ASSERT (result.tv_sec == result2.tv_sec
386 && result.tv_nsec == result2.tv_nsec);
387
388 /* If this platform has TZDB, check for GNU Bug#48085. */
389 ASSERT (setenv ("TZ", "America/Indiana/Indianapolis", 1) == 0);
390 now.tv_sec = 1619641490;
391 now.tv_nsec = 0;
392 struct tm *tm = localtime (&now.tv_sec);
393 if (tm && tm->tm_year == 2021 - 1900 && tm->tm_mon == 4 - 1
394 && tm->tm_mday == 28 && tm->tm_hour == 16 && tm->tm_min == 24
395 && 0 < tm->tm_isdst)
396 {
397 int has_leap_seconds = tm->tm_sec != now.tv_sec % 60;
398 p = "now - 35 years";
399 ASSERT (parse_datetime (&result, p, &now));
400 LOG (p, now, result);
401 ASSERT (result.tv_sec
402 == 515107490 - 60 * 60 + (has_leap_seconds ? 13 : 0));
403 }
404
405 /* Check that some "next Monday", "last Wednesday", etc. are correct. */
406 ASSERT (setenv ("TZ", "UTC0", 1) == 0);
407 for (i = 0; day_table[i]; i++)
408 {
409 unsigned int thur2 = 7 * 24 * 3600; /* 2nd thursday */
410 char tmp[32];
411 sprintf (tmp, "NEXT %s", day_table[i]);
412 now.tv_sec = thur2 + 4711;
413 now.tv_nsec = 1267;
414 ASSERT (parse_datetime (&result, tmp, &now));
415 LOG (tmp, now, result);
416 ASSERT (result.tv_nsec == 0);
417 ASSERT (result.tv_sec == thur2 + (i == 4 ? 7 : (i + 3) % 7) * 24 * 3600);
418
419 sprintf (tmp, "LAST %s", day_table[i]);
420 now.tv_sec = thur2 + 4711;
421 now.tv_nsec = 1267;
422 ASSERT (parse_datetime (&result, tmp, &now));
423 LOG (tmp, now, result);
424 ASSERT (result.tv_nsec == 0);
425 ASSERT (result.tv_sec == thur2 + ((i + 3) % 7 - 7) * 24 * 3600);
426 }
427
428 p = "1970-12-31T23:59:59+00:00 - 1 year"; /* Bug#50115 */
429 now.tv_sec = -1;
430 now.tv_nsec = 0;
431 ASSERT (parse_datetime (&result, p, &now));
432 LOG (p, now, result);
433 ASSERT (result.tv_sec == now.tv_sec
434 && result.tv_nsec == now.tv_nsec);
435
436 p = "THURSDAY UTC+00"; /* The epoch was on Thursday. */
437 now.tv_sec = 0;
438 now.tv_nsec = 0;
439 ASSERT (parse_datetime (&result, p, &now));
440 LOG (p, now, result);
441 ASSERT (result.tv_sec == now.tv_sec
442 && result.tv_nsec == now.tv_nsec);
443
444 p = "FRIDAY UTC+00";
445 now.tv_sec = 0;
446 now.tv_nsec = 0;
447 ASSERT (parse_datetime (&result, p, &now));
448 LOG (p, now, result);
449 ASSERT (result.tv_sec == 24 * 3600
450 && result.tv_nsec == now.tv_nsec);
451
452 /* Exercise a sign-extension bug. Before July 2012, an input
453 starting with a high-bit-set byte would be treated like "0". */
454 ASSERT ( ! parse_datetime (&result, "\xb0", &now));
455
456 /* Exercise TZ="" parsing code. */
457 /* These two would infloop or segfault before Feb 2014. */
458 ASSERT ( ! parse_datetime (&result, "TZ=\"\"\"", &now));
459 ASSERT ( ! parse_datetime (&result, "TZ=\"\" \"", &now));
460 /* Exercise invalid patterns. */
461 ASSERT ( ! parse_datetime (&result, "TZ=\"", &now));
462 ASSERT ( ! parse_datetime (&result, "TZ=\"\\\"", &now));
463 ASSERT ( ! parse_datetime (&result, "TZ=\"\\n", &now));
464 ASSERT ( ! parse_datetime (&result, "TZ=\"\\n\"", &now));
465 /* Exercise valid patterns. */
466 ASSERT ( parse_datetime (&result, "TZ=\"\"", &now));
467 ASSERT ( parse_datetime (&result, "TZ=\"\" ", &now));
468 ASSERT ( parse_datetime (&result, " TZ=\"\"", &now));
469 /* Exercise patterns which may be valid or invalid, depending on the
470 platform. */
471 #if !defined __NetBSD__
472 ASSERT ( parse_datetime (&result, "TZ=\"\\\\\"", &now));
473 ASSERT ( parse_datetime (&result, "TZ=\"\\\"\"", &now));
474 #endif
475
476 /* Outlandishly-long time zone abbreviations should not cause problems. */
477 {
478 static char const bufprefix[] = "TZ=\"";
479 long int tzname_max = -1;
480 errno = 0;
481 #ifdef _SC_TZNAME_MAX
482 tzname_max = sysconf (_SC_TZNAME_MAX);
483 #endif
484 enum { tzname_alloc = 2000 };
485 if (tzname_max < 0)
486 tzname_max = errno ? 6 : tzname_alloc;
487 int tzname_len = tzname_alloc < tzname_max ? tzname_alloc : tzname_max;
488 static char const bufsuffix[] = "0\" 1970-01-01 01:02:03.123456789";
489 enum { bufsize = sizeof bufprefix - 1 + tzname_alloc + sizeof bufsuffix };
490 char buf[bufsize];
491 memcpy (buf, bufprefix, sizeof bufprefix - 1);
492 memset (buf + sizeof bufprefix - 1, 'X', tzname_len);
493 strcpy (buf + sizeof bufprefix - 1 + tzname_len, bufsuffix);
494 ASSERT (parse_datetime (&result, buf, &now));
495 LOG (buf, now, result);
496 ASSERT (result.tv_sec == 1 * 60 * 60 + 2 * 60 + 3
497 && result.tv_nsec == 123456789);
498 }
499
500 return 0;
501 }