4c0370d29379cdb5e2da50b149633639a88ae486
[gnulib.git] / tests / test-parse-datetime.c
1 /* Test of parse_datetime() function.
2    Copyright (C) 2008-2012 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   gmtoff = gmt_offset (ref_time);
127
128
129   /* ISO 8601 extended date and time of day representation,
130      'T' separator, local time zone */
131   p = "2011-05-01T11:55:18";
132   expected.tv_sec = ref_time - gmtoff;
133   expected.tv_nsec = 0;
134   ASSERT (parse_datetime (&result, p, 0));
135   LOG (p, expected, result);
136   ASSERT (expected.tv_sec == result.tv_sec
137           && expected.tv_nsec == result.tv_nsec);
138
139   /* ISO 8601 extended date and time of day representation,
140      ' ' separator, local time zone */
141   p = "2011-05-01 11:55:18";
142   expected.tv_sec = ref_time - gmtoff;
143   expected.tv_nsec = 0;
144   ASSERT (parse_datetime (&result, p, 0));
145   LOG (p, expected, result);
146   ASSERT (expected.tv_sec == result.tv_sec
147           && expected.tv_nsec == result.tv_nsec);
148
149
150   /* ISO 8601, extended date and time of day representation,
151      'T' separator, UTC */
152   p = "2011-05-01T11:55:18Z";
153   expected.tv_sec = ref_time;
154   expected.tv_nsec = 0;
155   ASSERT (parse_datetime (&result, p, 0));
156   LOG (p, expected, result);
157   ASSERT (expected.tv_sec == result.tv_sec
158           && expected.tv_nsec == result.tv_nsec);
159
160   /* ISO 8601, extended date and time of day representation,
161      ' ' separator, UTC */
162   p = "2011-05-01 11:55:18Z";
163   expected.tv_sec = ref_time;
164   expected.tv_nsec = 0;
165   ASSERT (parse_datetime (&result, p, 0));
166   LOG (p, expected, result);
167   ASSERT (expected.tv_sec == result.tv_sec
168           && expected.tv_nsec == result.tv_nsec);
169
170
171   /* ISO 8601 extended date and time of day representation,
172      'T' separator, w/UTC offset */
173   p = "2011-05-01T11:55:18-07:00";
174   expected.tv_sec = 1304276118;
175   expected.tv_nsec = 0;
176   ASSERT (parse_datetime (&result, p, 0));
177   LOG (p, expected, result);
178   ASSERT (expected.tv_sec == result.tv_sec
179           && expected.tv_nsec == result.tv_nsec);
180
181   /* ISO 8601 extended date and time of day representation,
182      ' ' separator, w/UTC offset */
183   p = "2011-05-01 11:55:18-07:00";
184   expected.tv_sec = 1304276118;
185   expected.tv_nsec = 0;
186   ASSERT (parse_datetime (&result, p, 0));
187   LOG (p, expected, result);
188   ASSERT (expected.tv_sec == result.tv_sec
189           && expected.tv_nsec == result.tv_nsec);
190
191
192   /* ISO 8601 extended date and time of day representation,
193      'T' separator, w/hour only UTC offset */
194   p = "2011-05-01T11:55:18-07";
195   expected.tv_sec = 1304276118;
196   expected.tv_nsec = 0;
197   ASSERT (parse_datetime (&result, p, 0));
198   LOG (p, expected, result);
199   ASSERT (expected.tv_sec == result.tv_sec
200           && expected.tv_nsec == result.tv_nsec);
201
202   /* ISO 8601 extended date and time of day representation,
203      ' ' separator, w/hour only UTC offset */
204   p = "2011-05-01 11:55:18-07";
205   expected.tv_sec = 1304276118;
206   expected.tv_nsec = 0;
207   ASSERT (parse_datetime (&result, p, 0));
208   LOG (p, expected, result);
209   ASSERT (expected.tv_sec == result.tv_sec
210           && expected.tv_nsec == result.tv_nsec);
211
212
213   now.tv_sec = 4711;
214   now.tv_nsec = 1267;
215   p = "now";
216   ASSERT (parse_datetime (&result, p, &now));
217   LOG (p, now, result);
218   ASSERT (now.tv_sec == result.tv_sec && now.tv_nsec == result.tv_nsec);
219
220   now.tv_sec = 4711;
221   now.tv_nsec = 1267;
222   p = "tomorrow";
223   ASSERT (parse_datetime (&result, p, &now));
224   LOG (p, now, result);
225   ASSERT (now.tv_sec + 24 * 60 * 60 == result.tv_sec
226           && now.tv_nsec == result.tv_nsec);
227
228   now.tv_sec = 4711;
229   now.tv_nsec = 1267;
230   p = "yesterday";
231   ASSERT (parse_datetime (&result, p, &now));
232   LOG (p, now, result);
233   ASSERT (now.tv_sec - 24 * 60 * 60 == result.tv_sec
234           && now.tv_nsec == result.tv_nsec);
235
236   now.tv_sec = 4711;
237   now.tv_nsec = 1267;
238   p = "4 hours";
239   ASSERT (parse_datetime (&result, p, &now));
240   LOG (p, now, result);
241   ASSERT (now.tv_sec + 4 * 60 * 60 == result.tv_sec
242           && now.tv_nsec == result.tv_nsec);
243
244   /* test if timezone is not being ignored for day offset */
245   now.tv_sec = 4711;
246   now.tv_nsec = 1267;
247   p = "UTC+400 +24 hours";
248   ASSERT (parse_datetime (&result, p, &now));
249   LOG (p, now, result);
250   p = "UTC+400 +1 day";
251   ASSERT (parse_datetime (&result2, p, &now));
252   LOG (p, now, result2);
253   ASSERT (result.tv_sec == result2.tv_sec
254           && result.tv_nsec == result2.tv_nsec);
255
256   /* test if several time zones formats are handled same way */
257   now.tv_sec = 4711;
258   now.tv_nsec = 1267;
259   p = "UTC+14:00";
260   ASSERT (parse_datetime (&result, p, &now));
261   LOG (p, now, result);
262   p = "UTC+14";
263   ASSERT (parse_datetime (&result2, p, &now));
264   LOG (p, now, result2);
265   ASSERT (result.tv_sec == result2.tv_sec
266           && result.tv_nsec == result2.tv_nsec);
267   p = "UTC+1400";
268   ASSERT (parse_datetime (&result2, p, &now));
269   LOG (p, now, result2);
270   ASSERT (result.tv_sec == result2.tv_sec
271           && result.tv_nsec == result2.tv_nsec);
272
273   now.tv_sec = 4711;
274   now.tv_nsec = 1267;
275   p = "UTC-14:00";
276   ASSERT (parse_datetime (&result, p, &now));
277   LOG (p, now, result);
278   p = "UTC-14";
279   ASSERT (parse_datetime (&result2, p, &now));
280   LOG (p, now, result2);
281   ASSERT (result.tv_sec == result2.tv_sec
282           && result.tv_nsec == result2.tv_nsec);
283   p = "UTC-1400";
284   ASSERT (parse_datetime (&result2, p, &now));
285   LOG (p, now, result2);
286   ASSERT (result.tv_sec == result2.tv_sec
287           && result.tv_nsec == result2.tv_nsec);
288
289   now.tv_sec = 4711;
290   now.tv_nsec = 1267;
291   p = "UTC+0:15";
292   ASSERT (parse_datetime (&result, p, &now));
293   LOG (p, now, result);
294   p = "UTC+0015";
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);
299
300   now.tv_sec = 4711;
301   now.tv_nsec = 1267;
302   p = "UTC-1:30";
303   ASSERT (parse_datetime (&result, p, &now));
304   LOG (p, now, result);
305   p = "UTC-130";
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);
310
311
312   /* TZ out of range should cause parse_datetime failure */
313   now.tv_sec = 4711;
314   now.tv_nsec = 1267;
315   p = "UTC+25:00";
316   ASSERT (!parse_datetime (&result, p, &now));
317
318         /* Check for several invalid countable dayshifts */
319   now.tv_sec = 4711;
320   now.tv_nsec = 1267;
321   p = "UTC+4:00 +40 yesterday";
322   ASSERT (!parse_datetime (&result, p, &now));
323   p = "UTC+4:00 next yesterday";
324   ASSERT (!parse_datetime (&result, p, &now));
325   p = "UTC+4:00 tomorrow ago";
326   ASSERT (!parse_datetime (&result, p, &now));
327   p = "UTC+4:00 tomorrow hence";
328   ASSERT (!parse_datetime (&result, p, &now));
329   p = "UTC+4:00 40 now ago";
330   ASSERT (!parse_datetime (&result, p, &now));
331   p = "UTC+4:00 last tomorrow";
332   ASSERT (!parse_datetime (&result, p, &now));
333   p = "UTC+4:00 -4 today";
334   ASSERT (!parse_datetime (&result, p, &now));
335
336   /* And check correct usage of dayshifts */
337   now.tv_sec = 4711;
338   now.tv_nsec = 1267;
339   p = "UTC+400 tomorrow";
340   ASSERT (parse_datetime (&result, p, &now));
341   LOG (p, now, result);
342   p = "UTC+400 +1 day";
343   ASSERT (parse_datetime (&result2, p, &now));
344   LOG (p, now, result2);
345   ASSERT (result.tv_sec == result2.tv_sec
346           && result.tv_nsec == result2.tv_nsec);
347   p = "UTC+400 1 day hence";
348   ASSERT (parse_datetime (&result2, p, &now));
349   LOG (p, now, result2);
350   ASSERT (result.tv_sec == result2.tv_sec
351           && result.tv_nsec == result2.tv_nsec);
352   now.tv_sec = 4711;
353   now.tv_nsec = 1267;
354   p = "UTC+400 yesterday";
355   ASSERT (parse_datetime (&result, p, &now));
356   LOG (p, now, result);
357   p = "UTC+400 1 day ago";
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   now.tv_sec = 4711;
363   now.tv_nsec = 1267;
364   p = "UTC+400 now";
365   ASSERT (parse_datetime (&result, p, &now));
366   LOG (p, now, result);
367   p = "UTC+400 +0 minutes"; /* silly, but simple "UTC+400" is different*/
368   ASSERT (parse_datetime (&result2, p, &now));
369   LOG (p, now, result2);
370   ASSERT (result.tv_sec == result2.tv_sec
371           && result.tv_nsec == result2.tv_nsec);
372
373   /* Check that some "next Monday", "last Wednesday", etc. are correct.  */
374   setenv ("TZ", "UTC0", 1);
375   for (i = 0; day_table[i]; i++)
376     {
377       unsigned int thur2 = 7 * 24 * 3600; /* 2nd thursday */
378       char tmp[32];
379       sprintf (tmp, "NEXT %s", day_table[i]);
380       now.tv_sec = thur2 + 4711;
381       now.tv_nsec = 1267;
382       ASSERT (parse_datetime (&result, tmp, &now));
383       LOG (tmp, now, result);
384       ASSERT (result.tv_nsec == 0);
385       ASSERT (result.tv_sec == thur2 + (i == 4 ? 7 : (i + 3) % 7) * 24 * 3600);
386
387       sprintf (tmp, "LAST %s", day_table[i]);
388       now.tv_sec = thur2 + 4711;
389       now.tv_nsec = 1267;
390       ASSERT (parse_datetime (&result, tmp, &now));
391       LOG (tmp, now, result);
392       ASSERT (result.tv_nsec == 0);
393       ASSERT (result.tv_sec == thur2 + ((i + 3) % 7 - 7) * 24 * 3600);
394     }
395
396   p = "THURSDAY UTC+00";  /* The epoch was on Thursday.  */
397   now.tv_sec = 0;
398   now.tv_nsec = 0;
399   ASSERT (parse_datetime (&result, p, &now));
400   LOG (p, now, result);
401   ASSERT (result.tv_sec == now.tv_sec
402           && result.tv_nsec == now.tv_nsec);
403
404   p = "FRIDAY UTC+00";
405   now.tv_sec = 0;
406   now.tv_nsec = 0;
407   ASSERT (parse_datetime (&result, p, &now));
408   LOG (p, now, result);
409   ASSERT (result.tv_sec == 24 * 3600
410           && result.tv_nsec == now.tv_nsec);
411
412   return 0;
413 }