add missing include to parse-duration.c
[gnulib.git] / lib / parse-duration.c
1 /* Parse a time duration and return a seconds count
2    Copyright (C) 2008 Free Software Foundation, Inc.
3    Written by Bruce Korb <bkorb@gnu.org>, 2008.
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
17
18 #include <config.h>
19
20 #include <ctype.h>
21 #include <errno.h>
22 #include <limits.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include "xalloc.h"
27
28 #include "parse-duration.h"
29
30 #ifndef _
31 #define _(_s)  _s
32 #endif
33
34 #ifndef NUL
35 #define NUL '\0'
36 #endif
37
38 #define cch_t char const
39
40 typedef enum {
41   NOTHING_IS_DONE,
42   YEAR_IS_DONE,
43   MONTH_IS_DONE,
44   WEEK_IS_DONE,
45   DAY_IS_DONE,
46   HOUR_IS_DONE,
47   MINUTE_IS_DONE,
48   SECOND_IS_DONE
49 } whats_done_t;
50
51 #define SEC_PER_MIN     60
52 #define SEC_PER_HR      (SEC_PER_MIN * 60)
53 #define SEC_PER_DAY     (SEC_PER_HR  * 24)
54 #define SEC_PER_WEEK    (SEC_PER_DAY * 7)
55 #define SEC_PER_MONTH   (SEC_PER_DAY * 30)
56 #define SEC_PER_YEAR    (SEC_PER_DAY * 365)
57
58 #define TIME_MAX        0x7FFFFFFF
59
60 static unsigned long inline
61 str_const_to_ul (cch_t * str, cch_t ** ppz, int base)
62 {
63   return strtoul (str, (char **)ppz, base);
64 }
65
66 static long inline
67 str_const_to_l (cch_t * str, cch_t ** ppz, int base)
68 {
69   return strtol (str, (char **)ppz, base);
70 }
71
72 static time_t inline
73 scale_n_add (time_t base, time_t val, int scale)
74 {
75   if (base == BAD_TIME)
76     {
77       if (errno == 0)
78         errno = EINVAL;
79       return BAD_TIME;
80     }
81
82   if (val > TIME_MAX / scale)
83     {
84       errno = ERANGE;
85       return BAD_TIME;
86     }
87
88   val *= scale;
89   if (base > TIME_MAX - val)
90     {
91       errno = ERANGE;
92       return BAD_TIME;
93     }
94
95   return base + val;
96 }
97
98 static time_t
99 parse_hr_min_sec (time_t start, cch_t * pz)
100 {
101   int lpct = 0;
102
103   errno = 0;
104
105   /* For as long as our scanner pointer points to a colon *AND*
106      we've not looped before, then keep looping.  (two iterations max) */
107   while ((*pz == ':') && (lpct++ <= 1))
108     {
109       unsigned long v = str_const_to_ul (pz+1, &pz, 10);
110
111       if (errno != 0)
112         return BAD_TIME;
113
114       start = scale_n_add (v, start, 60);
115
116       if (errno != 0)
117         return BAD_TIME;
118     }
119
120   /* allow for trailing spaces */
121   while (isspace ((unsigned char)*pz))   pz++;
122   if (*pz != NUL)
123     {
124       errno = EINVAL;
125       return BAD_TIME;
126     }
127
128   return start;
129 }
130
131 static time_t
132 parse_scaled_value (time_t base, cch_t ** ppz, cch_t * endp, int scale)
133 {
134   cch_t * pz = *ppz;
135   time_t val;
136
137   if (base == BAD_TIME)
138     return base;
139
140   errno = 0;
141   val = str_const_to_ul (pz, &pz, 10);
142   if (errno != 0)
143     return BAD_TIME;
144   while (isspace ((unsigned char)*pz))   pz++;
145   if (pz != endp)
146     {
147       errno = EINVAL;
148       return BAD_TIME;
149     }
150
151   *ppz =  pz;
152   return scale_n_add (base, val, scale);
153 }
154
155 static time_t
156 parse_year_month_day (cch_t * pz, cch_t * ps)
157 {
158   time_t res = 0;
159
160   res = parse_scaled_value (0, &pz, ps, SEC_PER_YEAR);
161
162   ps = strchr (++pz, '-');
163   if (ps == NULL)
164     {
165       errno = EINVAL;
166       return BAD_TIME;
167     }
168   res = parse_scaled_value (res, &pz, ps, SEC_PER_MONTH);
169
170   pz++;
171   ps = pz + strlen (pz);
172   return parse_scaled_value (res, &pz, ps, SEC_PER_DAY);
173 }
174
175 static time_t
176 parse_yearmonthday (cch_t * in_pz)
177 {
178   time_t res = 0;
179   char   buf[8];
180   cch_t * pz;
181
182   if (strlen (in_pz) != 8)
183     {
184       errno = EINVAL;
185       return BAD_TIME;
186     }
187
188   memcpy (buf, in_pz, 4);
189   buf[4] = NUL;
190   pz = buf;
191   res = parse_scaled_value (0, &pz, buf + 4, SEC_PER_YEAR);
192
193   memcpy (buf, in_pz + 4, 2);
194   buf[2] = NUL;
195   pz =   buf;
196   res = parse_scaled_value (res, &pz, buf + 2, SEC_PER_MONTH);
197
198   memcpy (buf, in_pz + 6, 2);
199   buf[2] = NUL;
200   pz =   buf;
201   return parse_scaled_value (res, &pz, buf + 2, SEC_PER_DAY);
202 }
203
204 static time_t
205 parse_YMWD (cch_t * pz)
206 {
207   time_t res = 0;
208   cch_t * ps = strchr (pz, 'Y');
209   if (ps != NULL)
210     {
211       res = parse_scaled_value (0, &pz, ps, SEC_PER_YEAR);
212       pz++;
213     }
214
215   ps = strchr (pz, 'M');
216   if (ps != NULL)
217     {
218       res = parse_scaled_value (res, &pz, ps, SEC_PER_MONTH);
219       pz++;
220     }
221
222   ps = strchr (pz, 'W');
223   if (ps != NULL)
224     {
225       res = parse_scaled_value (res, &pz, ps, SEC_PER_WEEK);
226       pz++;
227     }
228
229   ps = strchr (pz, 'D');
230   if (ps != NULL)
231     {
232       res = parse_scaled_value (res, &pz, ps, SEC_PER_DAY);
233       pz++;
234     }
235
236   while (isspace ((unsigned char)*pz))   pz++;
237   if (*pz != NUL)
238     {
239       errno = EINVAL;
240       return BAD_TIME;
241     }
242
243   return res;
244 }
245
246 static time_t
247 parse_hour_minute_second (cch_t * pz, cch_t * ps)
248 {
249   time_t res = 0;
250
251   res = parse_scaled_value (0, &pz, ps, SEC_PER_HR);
252
253   ps = strchr (++pz, ':');
254   if (ps == NULL)
255     {
256       errno = EINVAL;
257       return BAD_TIME;
258     }
259
260   res = parse_scaled_value (res, &pz, ps, SEC_PER_MIN);
261
262   pz++;
263   ps = pz + strlen (pz);
264   return parse_scaled_value (res, &pz, ps, 1);
265 }
266
267 static time_t
268 parse_hourminutesecond (cch_t * in_pz)
269 {
270   time_t res = 0;
271   char   buf[4];
272   cch_t * pz;
273
274   if (strlen (in_pz) != 6)
275     {
276       errno = EINVAL;
277       return BAD_TIME;
278     }
279
280   memcpy (buf, in_pz, 2);
281   buf[2] = NUL;
282   pz = buf;
283   res = parse_scaled_value (0, &pz, buf + 2, SEC_PER_HR);
284
285   memcpy (buf, in_pz + 2, 2);
286   buf[2] = NUL;
287   pz =   buf;
288   res = parse_scaled_value (res, &pz, buf + 2, SEC_PER_MIN);
289
290   memcpy (buf, in_pz + 4, 2);
291   buf[2] = NUL;
292   pz =   buf;
293   return parse_scaled_value (res, &pz, buf + 2, 1);
294 }
295
296 static time_t
297 parse_HMS (cch_t * pz)
298 {
299   time_t res = 0;
300   cch_t * ps = strchr (pz, 'H');
301   if (ps != NULL)
302     {
303       res = parse_scaled_value (0, &pz, ps, SEC_PER_HR);
304       pz++;
305     }
306
307   ps = strchr (pz, 'M');
308   if (ps != NULL)
309     {
310       res = parse_scaled_value (res, &pz, ps, SEC_PER_MIN);
311       pz++;
312     }
313
314   ps = strchr (pz, 'S');
315   if (ps != NULL)
316     {
317       res = parse_scaled_value (res, &pz, ps, 1);
318       pz++;
319     }
320
321   while (isspace ((unsigned char)*pz))   pz++;
322   if (*pz != NUL)
323     {
324       errno = EINVAL;
325       return BAD_TIME;
326     }
327
328   return res;
329 }
330
331 static time_t
332 parse_time (cch_t * pz)
333 {
334   cch_t * ps;
335   time_t  res = 0;
336
337   /*
338    *  Scan for a hyphen
339    */
340   ps = strchr (pz, ':');
341   if (ps != NULL)
342     {
343       res = parse_hour_minute_second (pz, ps);
344     }
345
346   /*
347    *  Try for a 'H', 'M' or 'S' suffix
348    */
349   else if (ps = strpbrk (pz, "HMS"),
350            ps == NULL)
351     {
352       /* Its a YYYYMMDD format: */
353       res = parse_hourminutesecond (pz);
354     }
355
356   else
357     res = parse_HMS (pz);
358
359   return res;
360 }
361
362 static char *
363 trim(char * pz)
364 {
365   /* trim leading white space */
366   while (isspace ((unsigned char)*pz))  pz++;
367
368   /* trim trailing white space */
369   {
370     char * pe = pz + strlen (pz);
371     while ((pe > pz) && isspace ((unsigned char)pe[-1])) pe--;
372     *pe = NUL;
373   }
374
375   return pz;
376 }
377
378 /*
379  *  Parse the year/months/days of a time period
380  */
381 static time_t
382 parse_period (cch_t * in_pz)
383 {
384   char * pz   = xstrdup (in_pz);
385   char * pT   = strchr (pz, 'T');
386   char * ps;
387   void * fptr = pz;
388   time_t res  = 0;
389
390   if (pT != NUL)
391     {
392       *(pT++) = NUL;
393       pz = trim (pz);
394       pT = trim (pT);
395     }
396
397   /*
398    *  Scan for a hyphen
399    */
400   ps = strchr (pz, '-');
401   if (ps != NULL)
402     {
403       res = parse_year_month_day (pz, ps);
404     }
405
406   /*
407    *  Try for a 'Y', 'M' or 'D' suffix
408    */
409   else if (ps = strpbrk (pz, "YMWD"),
410            ps == NULL)
411     {
412       /* Its a YYYYMMDD format: */
413       res = parse_yearmonthday (pz);
414     }
415
416   else
417     res = parse_YMWD (pz);
418
419   if ((errno == 0) && (pT != NULL))
420     {
421       time_t val = parse_time (pT);
422       res = scale_n_add (res, val, 1);
423     }
424
425   free (fptr);
426   return res;
427 }
428
429 static time_t
430 parse_non_iso8601(cch_t * pz)
431 {
432   whats_done_t whatd_we_do = NOTHING_IS_DONE;
433
434   time_t res = 0;
435
436   do  {
437     time_t val;
438
439     errno = 0;
440     val = str_const_to_l (pz, &pz, 10);
441     if (errno != 0)
442       goto bad_time;
443
444     /*  IF we find a colon, then we're going to have a seconds value.
445         We will not loop here any more.  We cannot already have parsed
446         a minute value and if we've parsed an hour value, then the result
447         value has to be less than an hour. */
448     if (*pz == ':')
449       {
450         if (whatd_we_do >= MINUTE_IS_DONE)
451           break;
452
453         val = parse_hr_min_sec (val, pz);
454
455         if ((whatd_we_do == HOUR_IS_DONE) && (val >= SEC_PER_HR))
456           break;
457
458         return scale_n_add (res, val, 1);
459       }
460
461     {
462       unsigned int mult;
463
464       /*  Skip over white space following the number we just parsed. */
465       while (isspace ((unsigned char)*pz))   pz++;
466
467       switch (*pz)
468         {
469         default:  goto bad_time;
470         case NUL:
471           return scale_n_add (res, val, 1);
472
473         case 'y': case 'Y':
474           if (whatd_we_do >= YEAR_IS_DONE)
475             goto bad_time;
476           mult = SEC_PER_YEAR;
477           whatd_we_do = YEAR_IS_DONE;
478           break;
479
480         case 'M':
481           if (whatd_we_do >= MONTH_IS_DONE)
482             goto bad_time;
483           mult = SEC_PER_MONTH;
484           whatd_we_do = MONTH_IS_DONE;
485           break;
486
487         case 'W':
488           if (whatd_we_do >= WEEK_IS_DONE)
489             goto bad_time;
490           mult = SEC_PER_WEEK;
491           whatd_we_do = WEEK_IS_DONE;
492           break;
493
494         case 'd': case 'D':
495           if (whatd_we_do >= DAY_IS_DONE)
496             goto bad_time;
497           mult = SEC_PER_DAY;
498           whatd_we_do = DAY_IS_DONE;
499           break;
500
501         case 'h':
502           if (whatd_we_do >= HOUR_IS_DONE)
503             goto bad_time;
504           mult = SEC_PER_HR;
505           whatd_we_do = HOUR_IS_DONE;
506           break;
507
508         case 'm':
509           if (whatd_we_do >= MINUTE_IS_DONE)
510             goto bad_time;
511           mult = SEC_PER_MIN;
512           whatd_we_do = MINUTE_IS_DONE;
513           break;
514
515         case 's':
516           mult = 1;
517           whatd_we_do = SECOND_IS_DONE;
518           break;
519         }
520
521       res = scale_n_add (res, val, mult);
522
523       while (isspace ((unsigned char)*++pz))   ;
524       if (*pz == NUL)
525         return res;
526
527       if (! isdigit ((unsigned char)*pz))
528         break;
529     }
530
531   } while (whatd_we_do < SECOND_IS_DONE);
532
533  bad_time:
534   errno = EINVAL;
535   return BAD_TIME;
536 }
537
538 time_t
539 parse_duration (char const * pz)
540 {
541   time_t res = 0;
542
543   while (isspace ((unsigned char)*pz)) pz++;
544
545   do {
546     if (*pz == 'P')
547       {
548         res = parse_period (pz + 1);
549         if ((errno != 0) || (res == BAD_TIME))
550           break;
551         return res;
552       }
553
554     if (*pz == 'T')
555       {
556         res = parse_time (pz + 1);
557         if ((errno != 0) || (res == BAD_TIME))
558           break;
559         return res;
560       }
561
562     if (! isdigit ((unsigned char)*pz))
563       break;
564
565     res = parse_non_iso8601 (pz);
566     if ((errno == 0) && (res != BAD_TIME))
567       return res;
568
569   } while (0);
570
571   fprintf (stderr, _("Invalid time duration:  %s\n"), pz);
572   if (errno == 0)
573     errno = EINVAL;
574   return BAD_TIME;
575 }
576
577 /*
578  * Local Variables:
579  * mode: C
580  * c-file-style: "gnu"
581  * indent-tabs-mode: nil
582  * End:
583  * end of parse-duration.c */