Merge branch 'stable'
[gnulib.git] / tests / test-parse-datetime.c
1 /* Test of parse_datetime() function.
2    Copyright (C) 2008-2011 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, write to the Free Software Foundation,
16    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
17
18 /* Written by Simon Josefsson <simon@josefsson.org>, 2008.  */
19
20 #include <config.h>
21
22 #include "parse-datetime.h"
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 #include "progname.h"
29 #include "macros.h"
30
31 #ifdef DEBUG
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);
35 #else
36 #define LOG(str, now, res) (void) 0
37 #endif
38
39 static const char *const day_table[] =
40 {
41   "SUNDAY",
42   "MONDAY",
43   "TUESDAY",
44   "WEDNESDAY",
45   "THURSDAY",
46   "FRIDAY",
47   "SATURDAY",
48   NULL
49 };
50
51
52 #if ! HAVE_TM_GMTOFF
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.
58
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.  */
63 #define SHR(a, b)       \
64   (-1 >> 1 == -1        \
65    ? (a) >> (b)         \
66    : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0))
67
68 #define TM_YEAR_BASE 1900
69
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.  */
74 static long int
75 tm_diff (struct tm const *a, struct tm const *b)
76 {
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));
93 }
94 #endif /* ! HAVE_TM_GMTOFF */
95
96 static long
97 gmt_offset (time_t s)
98 {
99   long gmtoff;
100
101 #if !HAVE_TM_GMTOFF
102   struct tm tm_local = *localtime (&s);
103   struct tm tm_gmt   = *gmtime (&s);
104
105   gmtoff = tm_diff (&tm_local, &tm_gmt);
106 #else
107   gmtoff = localtime (&s)->tm_gmtoff;
108 #endif
109
110   return gmtoff;
111 }
112
113 int
114 main (int argc _GL_UNUSED, char **argv)
115 {
116   struct timespec result;
117   struct timespec result2;
118   struct timespec expected;
119   struct timespec now;
120   const char *p;
121   int i;
122   long gmtoff;
123   time_t ref_time = 1304250918;
124
125   set_program_name (argv[0]);
126
127   gmtoff = gmt_offset (ref_time);
128
129
130   /* ISO 8601 extended date and time of day representation,
131      'T' separator, local time zone */
132   p = "2011-05-01T11:55:18";
133   expected.tv_sec = ref_time - gmtoff;
134   expected.tv_nsec = 0;
135   ASSERT (parse_datetime (&result, p, 0));
136   LOG (p, expected, result);
137   ASSERT (expected.tv_sec == result.tv_sec
138           && expected.tv_nsec == result.tv_nsec);
139
140   /* ISO 8601 extended date and time of day representation,
141      ' ' separator, local time zone */
142   p = "2011-05-01 11:55:18";
143   expected.tv_sec = ref_time - gmtoff;
144   expected.tv_nsec = 0;
145   ASSERT (parse_datetime (&result, p, 0));
146   LOG (p, expected, result);
147   ASSERT (expected.tv_sec == result.tv_sec
148           && expected.tv_nsec == result.tv_nsec);
149
150
151   /* ISO 8601, extended date and time of day representation,
152      'T' separator, UTC */
153   p = "2011-05-01T11:55:18Z";
154   expected.tv_sec = ref_time;
155   expected.tv_nsec = 0;
156   ASSERT (parse_datetime (&result, p, 0));
157   LOG (p, expected, result);
158   ASSERT (expected.tv_sec == result.tv_sec
159           && expected.tv_nsec == result.tv_nsec);
160
161   /* ISO 8601, extended date and time of day representation,
162      ' ' separator, UTC */
163   p = "2011-05-01 11:55:18Z";
164   expected.tv_sec = ref_time;
165   expected.tv_nsec = 0;
166   ASSERT (parse_datetime (&result, p, 0));
167   LOG (p, expected, result);
168   ASSERT (expected.tv_sec == result.tv_sec
169           && expected.tv_nsec == result.tv_nsec);
170
171
172   /* ISO 8601 extended date and time of day representation,
173      'T' separator, w/UTC offset */
174   p = "2011-05-01T11:55:18-07:00";
175   expected.tv_sec = 1304276118;
176   expected.tv_nsec = 0;
177   ASSERT (parse_datetime (&result, p, 0));
178   LOG (p, expected, result);
179   ASSERT (expected.tv_sec == result.tv_sec
180           && expected.tv_nsec == result.tv_nsec);
181
182   /* ISO 8601 extended date and time of day representation,
183      ' ' separator, w/UTC offset */
184   p = "2011-05-01 11:55:18-07:00";
185   expected.tv_sec = 1304276118;
186   expected.tv_nsec = 0;
187   ASSERT (parse_datetime (&result, p, 0));
188   LOG (p, expected, result);
189   ASSERT (expected.tv_sec == result.tv_sec
190           && expected.tv_nsec == result.tv_nsec);
191
192
193   /* ISO 8601 extended date and time of day representation,
194      'T' separator, w/hour only UTC offset */
195   p = "2011-05-01T11:55:18-07";
196   expected.tv_sec = 1304276118;
197   expected.tv_nsec = 0;
198   ASSERT (parse_datetime (&result, p, 0));
199   LOG (p, expected, result);
200   ASSERT (expected.tv_sec == result.tv_sec
201           && expected.tv_nsec == result.tv_nsec);
202
203   /* ISO 8601 extended date and time of day representation,
204      ' ' separator, w/hour only UTC offset */
205   p = "2011-05-01 11:55:18-07";
206   expected.tv_sec = 1304276118;
207   expected.tv_nsec = 0;
208   ASSERT (parse_datetime (&result, p, 0));
209   LOG (p, expected, result);
210   ASSERT (expected.tv_sec == result.tv_sec
211           && expected.tv_nsec == result.tv_nsec);
212
213
214   now.tv_sec = 4711;
215   now.tv_nsec = 1267;
216   p = "now";
217   ASSERT (parse_datetime (&result, p, &now));
218   LOG (p, now, result);
219   ASSERT (now.tv_sec == result.tv_sec && now.tv_nsec == result.tv_nsec);
220
221   now.tv_sec = 4711;
222   now.tv_nsec = 1267;
223   p = "tomorrow";
224   ASSERT (parse_datetime (&result, p, &now));
225   LOG (p, now, result);
226   ASSERT (now.tv_sec + 24 * 60 * 60 == result.tv_sec
227           && now.tv_nsec == result.tv_nsec);
228
229   now.tv_sec = 4711;
230   now.tv_nsec = 1267;
231   p = "yesterday";
232   ASSERT (parse_datetime (&result, p, &now));
233   LOG (p, now, result);
234   ASSERT (now.tv_sec - 24 * 60 * 60 == result.tv_sec
235           && now.tv_nsec == result.tv_nsec);
236
237   now.tv_sec = 4711;
238   now.tv_nsec = 1267;
239   p = "4 hours";
240   ASSERT (parse_datetime (&result, p, &now));
241   LOG (p, now, result);
242   ASSERT (now.tv_sec + 4 * 60 * 60 == result.tv_sec
243           && now.tv_nsec == result.tv_nsec);
244
245   /* test if timezone is not being ignored for day offset */
246   now.tv_sec = 4711;
247   now.tv_nsec = 1267;
248   p = "UTC+400 +24 hours";
249   ASSERT (parse_datetime (&result, p, &now));
250   LOG (p, now, result);
251   p = "UTC+400 +1 day";
252   ASSERT (parse_datetime (&result2, p, &now));
253   LOG (p, now, result2);
254   ASSERT (result.tv_sec == result2.tv_sec
255           && result.tv_nsec == result2.tv_nsec);
256
257   /* test if several time zones formats are handled same way */
258   now.tv_sec = 4711;
259   now.tv_nsec = 1267;
260   p = "UTC+14:00";
261   ASSERT (parse_datetime (&result, p, &now));
262   LOG (p, now, result);
263   p = "UTC+14";
264   ASSERT (parse_datetime (&result2, p, &now));
265   LOG (p, now, result2);
266   ASSERT (result.tv_sec == result2.tv_sec
267           && result.tv_nsec == result2.tv_nsec);
268   p = "UTC+1400";
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
274   now.tv_sec = 4711;
275   now.tv_nsec = 1267;
276   p = "UTC-14:00";
277   ASSERT (parse_datetime (&result, p, &now));
278   LOG (p, now, result);
279   p = "UTC-14";
280   ASSERT (parse_datetime (&result2, p, &now));
281   LOG (p, now, result2);
282   ASSERT (result.tv_sec == result2.tv_sec
283           && result.tv_nsec == result2.tv_nsec);
284   p = "UTC-1400";
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
290   now.tv_sec = 4711;
291   now.tv_nsec = 1267;
292   p = "UTC+0:15";
293   ASSERT (parse_datetime (&result, p, &now));
294   LOG (p, now, result);
295   p = "UTC+0015";
296   ASSERT (parse_datetime (&result2, p, &now));
297   LOG (p, now, result2);
298   ASSERT (result.tv_sec == result2.tv_sec
299           && result.tv_nsec == result2.tv_nsec);
300
301   now.tv_sec = 4711;
302   now.tv_nsec = 1267;
303   p = "UTC-1:30";
304   ASSERT (parse_datetime (&result, p, &now));
305   LOG (p, now, result);
306   p = "UTC-130";
307   ASSERT (parse_datetime (&result2, p, &now));
308   LOG (p, now, result2);
309   ASSERT (result.tv_sec == result2.tv_sec
310           && result.tv_nsec == result2.tv_nsec);
311
312
313   /* TZ out of range should cause parse_datetime failure */
314   now.tv_sec = 4711;
315   now.tv_nsec = 1267;
316   p = "UTC+25:00";
317   ASSERT (!parse_datetime (&result, p, &now));
318
319         /* Check for several invalid countable dayshifts */
320   now.tv_sec = 4711;
321   now.tv_nsec = 1267;
322   p = "UTC+4:00 +40 yesterday";
323   ASSERT (!parse_datetime (&result, p, &now));
324   p = "UTC+4:00 next yesterday";
325   ASSERT (!parse_datetime (&result, p, &now));
326   p = "UTC+4:00 tomorrow ago";
327   ASSERT (!parse_datetime (&result, p, &now));
328   p = "UTC+4:00 tomorrow hence";
329   ASSERT (!parse_datetime (&result, p, &now));
330   p = "UTC+4:00 40 now ago";
331   ASSERT (!parse_datetime (&result, p, &now));
332   p = "UTC+4:00 last tomorrow";
333   ASSERT (!parse_datetime (&result, p, &now));
334   p = "UTC+4:00 -4 today";
335   ASSERT (!parse_datetime (&result, p, &now));
336
337   /* And check correct usage of dayshifts */
338   now.tv_sec = 4711;
339   now.tv_nsec = 1267;
340   p = "UTC+400 tomorrow";
341   ASSERT (parse_datetime (&result, p, &now));
342   LOG (p, now, result);
343   p = "UTC+400 +1 day";
344   ASSERT (parse_datetime (&result2, p, &now));
345   LOG (p, now, result2);
346   ASSERT (result.tv_sec == result2.tv_sec
347           && result.tv_nsec == result2.tv_nsec);
348   p = "UTC+400 1 day hence";
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   now.tv_sec = 4711;
354   now.tv_nsec = 1267;
355   p = "UTC+400 yesterday";
356   ASSERT (parse_datetime (&result, p, &now));
357   LOG (p, now, result);
358   p = "UTC+400 1 day ago";
359   ASSERT (parse_datetime (&result2, p, &now));
360   LOG (p, now, result2);
361   ASSERT (result.tv_sec == result2.tv_sec
362           && result.tv_nsec == result2.tv_nsec);
363   now.tv_sec = 4711;
364   now.tv_nsec = 1267;
365   p = "UTC+400 now";
366   ASSERT (parse_datetime (&result, p, &now));
367   LOG (p, now, result);
368   p = "UTC+400 +0 minutes"; /* silly, but simple "UTC+400" is different*/
369   ASSERT (parse_datetime (&result2, p, &now));
370   LOG (p, now, result2);
371   ASSERT (result.tv_sec == result2.tv_sec
372           && result.tv_nsec == result2.tv_nsec);
373
374   /* Check that some "next Monday", "last Wednesday", etc. are correct.  */
375   setenv ("TZ", "UTC0", 1);
376   for (i = 0; day_table[i]; i++)
377     {
378       unsigned int thur2 = 7 * 24 * 3600; /* 2nd thursday */
379       char tmp[32];
380       sprintf (tmp, "NEXT %s", day_table[i]);
381       now.tv_sec = thur2 + 4711;
382       now.tv_nsec = 1267;
383       ASSERT (parse_datetime (&result, tmp, &now));
384       LOG (tmp, now, result);
385       ASSERT (result.tv_nsec == 0);
386       ASSERT (result.tv_sec == thur2 + (i == 4 ? 7 : (i + 3) % 7) * 24 * 3600);
387
388       sprintf (tmp, "LAST %s", day_table[i]);
389       now.tv_sec = thur2 + 4711;
390       now.tv_nsec = 1267;
391       ASSERT (parse_datetime (&result, tmp, &now));
392       LOG (tmp, now, result);
393       ASSERT (result.tv_nsec == 0);
394       ASSERT (result.tv_sec == thur2 + ((i + 3) % 7 - 7) * 24 * 3600);
395     }
396
397   p = "THURSDAY UTC+00";  /* The epoch was on Thursday.  */
398   now.tv_sec = 0;
399   now.tv_nsec = 0;
400   ASSERT (parse_datetime (&result, p, &now));
401   LOG (p, now, result);
402   ASSERT (result.tv_sec == now.tv_sec
403           && result.tv_nsec == now.tv_nsec);
404
405   p = "FRIDAY UTC+00";
406   now.tv_sec = 0;
407   now.tv_nsec = 0;
408   ASSERT (parse_datetime (&result, p, &now));
409   LOG (p, now, result);
410   ASSERT (result.tv_sec == 24 * 3600
411           && result.tv_nsec == now.tv_nsec);
412
413   return 0;
414 }