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