openat: Fix theoretically possible issue on GNU/Hurd.
[gnulib.git] / tests / test-parse-datetime.c
blob920c9ae8410d151fc0ef4e706f1bf64037c9da7d
1 /* Test of parse_datetime() function.
2 Copyright (C) 2008-2020 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 <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
27 #include "macros.h"
29 #ifdef DEBUG
30 #define LOG(str, now, res) \
31 printf ("string '%s' diff %d %d\n", \
32 str, res.tv_sec - now.tv_sec, res.tv_nsec - now.tv_nsec);
33 #else
34 #define LOG(str, now, res) (void) 0
35 #endif
37 static const char *const day_table[] =
39 "SUNDAY",
40 "MONDAY",
41 "TUESDAY",
42 "WEDNESDAY",
43 "THURSDAY",
44 "FRIDAY",
45 "SATURDAY",
46 NULL
50 #if ! HAVE_TM_GMTOFF
51 /* Shift A right by B bits portably, by dividing A by 2**B and
52 truncating towards minus infinity. A and B should be free of side
53 effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
54 INT_BITS is the number of useful bits in an int. GNU code can
55 assume that INT_BITS is at least 32.
57 ISO C99 says that A >> B is implementation-defined if A < 0. Some
58 implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
59 right in the usual way when A < 0, so SHR falls back on division if
60 ordinary A >> B doesn't seem to be the usual signed shift. */
61 #define SHR(a, b) \
62 (-1 >> 1 == -1 \
63 ? (a) >> (b) \
64 : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0))
66 #define TM_YEAR_BASE 1900
68 /* Yield the difference between *A and *B,
69 measured in seconds, ignoring leap seconds.
70 The body of this function is taken directly from the GNU C Library;
71 see src/strftime.c. */
72 static long int
73 tm_diff (struct tm const *a, struct tm const *b)
75 /* Compute intervening leap days correctly even if year is negative.
76 Take care to avoid int overflow in leap day calculations. */
77 int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3);
78 int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3);
79 int a100 = a4 / 25 - (a4 % 25 < 0);
80 int b100 = b4 / 25 - (b4 % 25 < 0);
81 int a400 = SHR (a100, 2);
82 int b400 = SHR (b100, 2);
83 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
84 long int ayear = a->tm_year;
85 long int years = ayear - b->tm_year;
86 long int days = (365 * years + intervening_leap_days
87 + (a->tm_yday - b->tm_yday));
88 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
89 + (a->tm_min - b->tm_min))
90 + (a->tm_sec - b->tm_sec));
92 #endif /* ! HAVE_TM_GMTOFF */
94 static long
95 gmt_offset (time_t s)
97 long gmtoff;
99 #if !HAVE_TM_GMTOFF
100 struct tm tm_local = *localtime (&s);
101 struct tm tm_gmt = *gmtime (&s);
103 gmtoff = tm_diff (&tm_local, &tm_gmt);
104 #else
105 gmtoff = localtime (&s)->tm_gmtoff;
106 #endif
108 return gmtoff;
112 main (int argc _GL_UNUSED, char **argv)
114 struct timespec result;
115 struct timespec result2;
116 struct timespec expected;
117 struct timespec now;
118 const char *p;
119 int i;
120 long gmtoff;
121 time_t ref_time = 1304250918;
123 /* Set the time zone to US Eastern time with the 2012 rules. This
124 should disable any leap second support. Otherwise, there will be
125 a problem with glibc on sites that default to leap seconds; see
126 <https://bugs.gnu.org/12206>. */
127 setenv ("TZ", "EST5EDT,M3.2.0,M11.1.0", 1);
129 gmtoff = gmt_offset (ref_time);
132 /* ISO 8601 extended date and time of day representation,
133 'T' separator, local time zone */
134 p = "2011-05-01T11:55:18";
135 expected.tv_sec = ref_time - gmtoff;
136 expected.tv_nsec = 0;
137 ASSERT (parse_datetime (&result, p, 0));
138 LOG (p, expected, result);
139 ASSERT (expected.tv_sec == result.tv_sec
140 && expected.tv_nsec == result.tv_nsec);
142 /* ISO 8601 extended date and time of day representation,
143 ' ' separator, local time zone */
144 p = "2011-05-01 11:55:18";
145 expected.tv_sec = ref_time - gmtoff;
146 expected.tv_nsec = 0;
147 ASSERT (parse_datetime (&result, p, 0));
148 LOG (p, expected, result);
149 ASSERT (expected.tv_sec == result.tv_sec
150 && expected.tv_nsec == result.tv_nsec);
153 /* ISO 8601, extended date and time of day representation,
154 'T' separator, UTC */
155 p = "2011-05-01T11:55:18Z";
156 expected.tv_sec = ref_time;
157 expected.tv_nsec = 0;
158 ASSERT (parse_datetime (&result, p, 0));
159 LOG (p, expected, result);
160 ASSERT (expected.tv_sec == result.tv_sec
161 && expected.tv_nsec == result.tv_nsec);
163 /* ISO 8601, extended date and time of day representation,
164 ' ' separator, UTC */
165 p = "2011-05-01 11:55:18Z";
166 expected.tv_sec = ref_time;
167 expected.tv_nsec = 0;
168 ASSERT (parse_datetime (&result, p, 0));
169 LOG (p, expected, result);
170 ASSERT (expected.tv_sec == result.tv_sec
171 && expected.tv_nsec == result.tv_nsec);
174 /* ISO 8601 extended date and time of day representation,
175 'T' separator, w/UTC offset */
176 p = "2011-05-01T11:55:18-07:00";
177 expected.tv_sec = 1304276118;
178 expected.tv_nsec = 0;
179 ASSERT (parse_datetime (&result, p, 0));
180 LOG (p, expected, result);
181 ASSERT (expected.tv_sec == result.tv_sec
182 && expected.tv_nsec == result.tv_nsec);
184 /* ISO 8601 extended date and time of day representation,
185 ' ' separator, w/UTC offset */
186 p = "2011-05-01 11:55:18-07:00";
187 expected.tv_sec = 1304276118;
188 expected.tv_nsec = 0;
189 ASSERT (parse_datetime (&result, p, 0));
190 LOG (p, expected, result);
191 ASSERT (expected.tv_sec == result.tv_sec
192 && expected.tv_nsec == result.tv_nsec);
195 /* ISO 8601 extended date and time of day representation,
196 'T' separator, w/hour only UTC offset */
197 p = "2011-05-01T11:55:18-07";
198 expected.tv_sec = 1304276118;
199 expected.tv_nsec = 0;
200 ASSERT (parse_datetime (&result, p, 0));
201 LOG (p, expected, result);
202 ASSERT (expected.tv_sec == result.tv_sec
203 && expected.tv_nsec == result.tv_nsec);
205 /* ISO 8601 extended date and time of day representation,
206 ' ' separator, w/hour only UTC offset */
207 p = "2011-05-01 11:55:18-07";
208 expected.tv_sec = 1304276118;
209 expected.tv_nsec = 0;
210 ASSERT (parse_datetime (&result, p, 0));
211 LOG (p, expected, result);
212 ASSERT (expected.tv_sec == result.tv_sec
213 && expected.tv_nsec == result.tv_nsec);
216 now.tv_sec = 4711;
217 now.tv_nsec = 1267;
218 p = "now";
219 ASSERT (parse_datetime (&result, p, &now));
220 LOG (p, now, result);
221 ASSERT (now.tv_sec == result.tv_sec && now.tv_nsec == result.tv_nsec);
223 now.tv_sec = 4711;
224 now.tv_nsec = 1267;
225 p = "tomorrow";
226 ASSERT (parse_datetime (&result, p, &now));
227 LOG (p, now, result);
228 ASSERT (now.tv_sec + 24 * 60 * 60 == result.tv_sec
229 && now.tv_nsec == result.tv_nsec);
231 now.tv_sec = 4711;
232 now.tv_nsec = 1267;
233 p = "yesterday";
234 ASSERT (parse_datetime (&result, p, &now));
235 LOG (p, now, result);
236 ASSERT (now.tv_sec - 24 * 60 * 60 == result.tv_sec
237 && now.tv_nsec == result.tv_nsec);
239 now.tv_sec = 4711;
240 now.tv_nsec = 1267;
241 p = "4 hours";
242 ASSERT (parse_datetime (&result, p, &now));
243 LOG (p, now, result);
244 ASSERT (now.tv_sec + 4 * 60 * 60 == result.tv_sec
245 && now.tv_nsec == result.tv_nsec);
247 /* test if timezone is not being ignored for day offset */
248 now.tv_sec = 4711;
249 now.tv_nsec = 1267;
250 p = "UTC+400 +24 hours";
251 ASSERT (parse_datetime (&result, p, &now));
252 LOG (p, now, result);
253 p = "UTC+400 +1 day";
254 ASSERT (parse_datetime (&result2, p, &now));
255 LOG (p, now, result2);
256 ASSERT (result.tv_sec == result2.tv_sec
257 && result.tv_nsec == result2.tv_nsec);
259 /* test if several time zones formats are handled same way */
260 now.tv_sec = 4711;
261 now.tv_nsec = 1267;
262 p = "UTC+14:00";
263 ASSERT (parse_datetime (&result, p, &now));
264 LOG (p, now, result);
265 p = "UTC+14";
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 p = "UTC+1400";
271 ASSERT (parse_datetime (&result2, p, &now));
272 LOG (p, now, result2);
273 ASSERT (result.tv_sec == result2.tv_sec
274 && result.tv_nsec == result2.tv_nsec);
276 now.tv_sec = 4711;
277 now.tv_nsec = 1267;
278 p = "UTC-14:00";
279 ASSERT (parse_datetime (&result, p, &now));
280 LOG (p, now, result);
281 p = "UTC-14";
282 ASSERT (parse_datetime (&result2, p, &now));
283 LOG (p, now, result2);
284 ASSERT (result.tv_sec == result2.tv_sec
285 && result.tv_nsec == result2.tv_nsec);
286 p = "UTC-1400";
287 ASSERT (parse_datetime (&result2, p, &now));
288 LOG (p, now, result2);
289 ASSERT (result.tv_sec == result2.tv_sec
290 && result.tv_nsec == result2.tv_nsec);
292 now.tv_sec = 4711;
293 now.tv_nsec = 1267;
294 p = "UTC+0:15";
295 ASSERT (parse_datetime (&result, p, &now));
296 LOG (p, now, result);
297 p = "UTC+0015";
298 ASSERT (parse_datetime (&result2, p, &now));
299 LOG (p, now, result2);
300 ASSERT (result.tv_sec == result2.tv_sec
301 && result.tv_nsec == result2.tv_nsec);
303 now.tv_sec = 4711;
304 now.tv_nsec = 1267;
305 p = "UTC-1:30";
306 ASSERT (parse_datetime (&result, p, &now));
307 LOG (p, now, result);
308 p = "UTC-130";
309 ASSERT (parse_datetime (&result2, p, &now));
310 LOG (p, now, result2);
311 ASSERT (result.tv_sec == result2.tv_sec
312 && result.tv_nsec == result2.tv_nsec);
315 /* TZ out of range should cause parse_datetime failure */
316 now.tv_sec = 4711;
317 now.tv_nsec = 1267;
318 p = "UTC+25:00";
319 ASSERT (!parse_datetime (&result, p, &now));
321 /* Check for several invalid countable dayshifts */
322 now.tv_sec = 4711;
323 now.tv_nsec = 1267;
324 p = "UTC+4:00 +40 yesterday";
325 ASSERT (!parse_datetime (&result, p, &now));
326 p = "UTC+4:00 next yesterday";
327 ASSERT (!parse_datetime (&result, p, &now));
328 p = "UTC+4:00 tomorrow ago";
329 ASSERT (!parse_datetime (&result, p, &now));
330 p = "UTC+4:00 tomorrow hence";
331 ASSERT (!parse_datetime (&result, p, &now));
332 p = "UTC+4:00 40 now ago";
333 ASSERT (!parse_datetime (&result, p, &now));
334 p = "UTC+4:00 last tomorrow";
335 ASSERT (!parse_datetime (&result, p, &now));
336 p = "UTC+4:00 -4 today";
337 ASSERT (!parse_datetime (&result, p, &now));
339 /* And check correct usage of dayshifts */
340 now.tv_sec = 4711;
341 now.tv_nsec = 1267;
342 p = "UTC+400 tomorrow";
343 ASSERT (parse_datetime (&result, p, &now));
344 LOG (p, now, result);
345 p = "UTC+400 +1 day";
346 ASSERT (parse_datetime (&result2, p, &now));
347 LOG (p, now, result2);
348 ASSERT (result.tv_sec == result2.tv_sec
349 && result.tv_nsec == result2.tv_nsec);
350 p = "UTC+400 1 day hence";
351 ASSERT (parse_datetime (&result2, p, &now));
352 LOG (p, now, result2);
353 ASSERT (result.tv_sec == result2.tv_sec
354 && result.tv_nsec == result2.tv_nsec);
355 now.tv_sec = 4711;
356 now.tv_nsec = 1267;
357 p = "UTC+400 yesterday";
358 ASSERT (parse_datetime (&result, p, &now));
359 LOG (p, now, result);
360 p = "UTC+400 1 day ago";
361 ASSERT (parse_datetime (&result2, p, &now));
362 LOG (p, now, result2);
363 ASSERT (result.tv_sec == result2.tv_sec
364 && result.tv_nsec == result2.tv_nsec);
365 now.tv_sec = 4711;
366 now.tv_nsec = 1267;
367 p = "UTC+400 now";
368 ASSERT (parse_datetime (&result, p, &now));
369 LOG (p, now, result);
370 p = "UTC+400 +0 minutes"; /* silly, but simple "UTC+400" is different*/
371 ASSERT (parse_datetime (&result2, p, &now));
372 LOG (p, now, result2);
373 ASSERT (result.tv_sec == result2.tv_sec
374 && result.tv_nsec == result2.tv_nsec);
376 /* Check that some "next Monday", "last Wednesday", etc. are correct. */
377 setenv ("TZ", "UTC0", 1);
378 for (i = 0; day_table[i]; i++)
380 unsigned int thur2 = 7 * 24 * 3600; /* 2nd thursday */
381 char tmp[32];
382 sprintf (tmp, "NEXT %s", day_table[i]);
383 now.tv_sec = thur2 + 4711;
384 now.tv_nsec = 1267;
385 ASSERT (parse_datetime (&result, tmp, &now));
386 LOG (tmp, now, result);
387 ASSERT (result.tv_nsec == 0);
388 ASSERT (result.tv_sec == thur2 + (i == 4 ? 7 : (i + 3) % 7) * 24 * 3600);
390 sprintf (tmp, "LAST %s", day_table[i]);
391 now.tv_sec = thur2 + 4711;
392 now.tv_nsec = 1267;
393 ASSERT (parse_datetime (&result, tmp, &now));
394 LOG (tmp, now, result);
395 ASSERT (result.tv_nsec == 0);
396 ASSERT (result.tv_sec == thur2 + ((i + 3) % 7 - 7) * 24 * 3600);
399 p = "THURSDAY UTC+00"; /* The epoch was on Thursday. */
400 now.tv_sec = 0;
401 now.tv_nsec = 0;
402 ASSERT (parse_datetime (&result, p, &now));
403 LOG (p, now, result);
404 ASSERT (result.tv_sec == now.tv_sec
405 && result.tv_nsec == now.tv_nsec);
407 p = "FRIDAY UTC+00";
408 now.tv_sec = 0;
409 now.tv_nsec = 0;
410 ASSERT (parse_datetime (&result, p, &now));
411 LOG (p, now, result);
412 ASSERT (result.tv_sec == 24 * 3600
413 && result.tv_nsec == now.tv_nsec);
415 /* Exercise a sign-extension bug. Before July 2012, an input
416 starting with a high-bit-set byte would be treated like "0". */
417 ASSERT ( ! parse_datetime (&result, "\xb0", &now));
419 /* Exercise TZ="" parsing code. */
420 /* These two would infloop or segfault before Feb 2014. */
421 ASSERT ( ! parse_datetime (&result, "TZ=\"\"\"", &now));
422 ASSERT ( ! parse_datetime (&result, "TZ=\"\" \"", &now));
423 /* Exercise invalid patterns. */
424 ASSERT ( ! parse_datetime (&result, "TZ=\"", &now));
425 ASSERT ( ! parse_datetime (&result, "TZ=\"\\\"", &now));
426 ASSERT ( ! parse_datetime (&result, "TZ=\"\\n", &now));
427 ASSERT ( ! parse_datetime (&result, "TZ=\"\\n\"", &now));
428 /* Exercise valid patterns. */
429 ASSERT ( parse_datetime (&result, "TZ=\"\"", &now));
430 ASSERT ( parse_datetime (&result, "TZ=\"\" ", &now));
431 ASSERT ( parse_datetime (&result, " TZ=\"\"", &now));
432 ASSERT ( parse_datetime (&result, "TZ=\"\\\\\"", &now));
433 ASSERT ( parse_datetime (&result, "TZ=\"\\\"\"", &now));
435 /* Outlandishly-long time zone abbreviations should not cause problems. */
437 static char const bufprefix[] = "TZ=\"";
438 enum { tzname_len = 2000 };
439 static char const bufsuffix[] = "0\" 1970-01-01 01:02:03.123456789";
440 enum { bufsize = sizeof bufprefix - 1 + tzname_len + sizeof bufsuffix };
441 char buf[bufsize];
442 memcpy (buf, bufprefix, sizeof bufprefix - 1);
443 memset (buf + sizeof bufprefix - 1, 'X', tzname_len);
444 strcpy (buf + bufsize - sizeof bufsuffix, bufsuffix);
445 ASSERT (parse_datetime (&result, buf, &now));
446 LOG (buf, now, result);
447 ASSERT (result.tv_sec == 1 * 60 * 60 + 2 * 60 + 3
448 && result.tv_nsec == 123456789);
451 return 0;