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