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)
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. */
21 #include "parse-datetime.h"
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);
36 #define LOG(str, now, res) (void) 0
39 static const char *const day_table
[] =
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. */
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. */
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 */
102 struct tm tm_local
= *localtime (&s
);
103 struct tm tm_gmt
= *gmtime (&s
);
105 gmtoff
= tm_diff (&tm_local
, &tm_gmt
);
107 gmtoff
= localtime (&s
)->tm_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
116 #if defined _WIN32 && !defined __CYGWIN__
117 /* On native Windows, localtime() fails for all time_t values < 0. */
118 # define SOME_TIMEPOINT (700 * 86400)
120 /* The Unix epoch. */
121 # define SOME_TIMEPOINT 0
125 main (_GL_UNUSED
int argc
, char **argv
)
127 struct timespec result
;
128 struct timespec result2
;
129 struct timespec expected
;
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);
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;
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;
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;
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;
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;
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;
287 ASSERT (parse_datetime (&result
, p
, &now
));
288 LOG (p
, now
, result
);
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
);
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;
303 ASSERT (parse_datetime (&result
, p
, &now
));
304 LOG (p
, now
, result
);
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
);
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;
319 ASSERT (parse_datetime (&result
, p
, &now
));
320 LOG (p
, now
, result
);
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;
330 ASSERT (parse_datetime (&result
, p
, &now
));
331 LOG (p
, now
, result
);
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;
343 ASSERT (!parse_datetime (&result
, p
, &now
));
345 /* Check for several invalid countable dayshifts */
346 now
.tv_sec
= SOME_TIMEPOINT
+ 4711;
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;
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;
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;
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);
404 ASSERT (setenv ("TZ", "America/Indiana/Indianapolis", 1) == 0);
407 now
.tv_sec
= 1619641490;
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
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);
425 for (i
= 0; day_table
[i
]; i
++)
427 unsigned int thur2
= SOME_TIMEPOINT
+ 7 * 24 * 3600; /* 2nd thursday */
429 sprintf (tmp
, "NEXT %s", day_table
[i
]);
430 now
.tv_sec
= thur2
+ 4711;
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;
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 */
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. */
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
);
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
);
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
492 #if !defined __NetBSD__
493 ASSERT ( parse_datetime (&result
, "TZ=\"\\\\\"", &now
));
494 ASSERT ( parse_datetime (&result
, "TZ=\"\\\"\"", &now
));
497 /* Outlandishly-long time zone abbreviations should not cause problems. */
499 static char const bufprefix
[] = "TZ=\"";
500 long int tzname_max
= -1;
502 #ifdef _SC_TZNAME_MAX
503 tzname_max
= sysconf (_SC_TZNAME_MAX
);
505 enum { tzname_alloc
= 2000 };
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
};
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
;