1 /* Test of parse_datetime() function.
2 Copyright (C) 2008-2017 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 <http://www.gnu.org/licenses/>. */
17 /* Written by Simon Josefsson <simon@josefsson.org>, 2008. */
21 #include "parse-datetime.h"
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);
34 #define LOG(str, now, res) (void) 0
37 static const char *const day_table
[] =
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. */
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. */
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 */
100 struct tm tm_local
= *localtime (&s
);
101 struct tm tm_gmt
= *gmtime (&s
);
103 gmtoff
= tm_diff (&tm_local
, &tm_gmt
);
105 gmtoff
= localtime (&s
)->tm_gmtoff
;
112 main (int argc _GL_UNUSED
, char **argv
)
114 struct timespec result
;
115 struct timespec result2
;
116 struct timespec expected
;
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 <http://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
);
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
);
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
);
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
);
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 */
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 */
263 ASSERT (parse_datetime (&result
, p
, &now
));
264 LOG (p
, now
, result
);
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
);
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
);
279 ASSERT (parse_datetime (&result
, p
, &now
));
280 LOG (p
, now
, result
);
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
);
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
);
295 ASSERT (parse_datetime (&result
, p
, &now
));
296 LOG (p
, now
, result
);
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
);
306 ASSERT (parse_datetime (&result
, p
, &now
));
307 LOG (p
, now
, result
);
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 */
319 ASSERT (!parse_datetime (&result
, p
, &now
));
321 /* Check for several invalid countable dayshifts */
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 */
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
);
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
);
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 */
382 sprintf (tmp
, "NEXT %s", day_table
[i
]);
383 now
.tv_sec
= thur2
+ 4711;
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;
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. */
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
);
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
};
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);