Patrick Welche <prlw1@cam.ac.uk>
[netbsd-mini2440.git] / external / bsd / ntp / dist / libopts / parse-duration.c
blobe1d0b7a1d4c1de6eec54e97a69b9e8dc48bfed40
1 /* $NetBSD$ */
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.
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.
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.
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/>. */
20 #include <config.h>
22 #include <ctype.h>
23 #include <errno.h>
24 #include <limits.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
29 #include "parse-duration.h"
31 #ifndef _
32 #define _(_s) _s
33 #endif
35 #ifndef NUL
36 #define NUL '\0'
37 #endif
39 #define cch_t char const
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;
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)
59 #define TIME_MAX 0x7FFFFFFF
61 static unsigned long inline
62 str_const_to_ul (cch_t * str, cch_t ** ppz, int base)
64 return strtoul (str, (char **)ppz, base);
67 static long inline
68 str_const_to_l (cch_t * str, cch_t ** ppz, int base)
70 return strtol (str, (char **)ppz, base);
73 static time_t inline
74 scale_n_add (time_t base, time_t val, int scale)
76 if (base == BAD_TIME)
78 if (errno == 0)
79 errno = EINVAL;
80 return BAD_TIME;
83 if (val > TIME_MAX / scale)
85 errno = ERANGE;
86 return BAD_TIME;
89 val *= scale;
90 if (base > TIME_MAX - val)
92 errno = ERANGE;
93 return BAD_TIME;
96 return base + val;
99 static time_t
100 parse_hr_min_sec (time_t start, cch_t * pz)
102 int lpct = 0;
104 errno = 0;
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))
110 unsigned long v = str_const_to_ul (pz+1, &pz, 10);
112 if (errno != 0)
113 return BAD_TIME;
115 start = scale_n_add (v, start, 60);
117 if (errno != 0)
118 return BAD_TIME;
121 /* allow for trailing spaces */
122 while (isspace ((unsigned char)*pz)) pz++;
123 if (*pz != NUL)
125 errno = EINVAL;
126 return BAD_TIME;
129 return start;
132 static time_t
133 parse_scaled_value (time_t base, cch_t ** ppz, cch_t * endp, int scale)
135 cch_t * pz = *ppz;
136 time_t val;
138 if (base == BAD_TIME)
139 return base;
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)
148 errno = EINVAL;
149 return BAD_TIME;
152 *ppz = pz;
153 return scale_n_add (base, val, scale);
156 static time_t
157 parse_year_month_day (cch_t * pz, cch_t * ps)
159 time_t res = 0;
161 res = parse_scaled_value (0, &pz, ps, SEC_PER_YEAR);
163 ps = strchr (++pz, '-');
164 if (ps == NULL)
166 errno = EINVAL;
167 return BAD_TIME;
169 res = parse_scaled_value (res, &pz, ps, SEC_PER_MONTH);
171 pz++;
172 ps = pz + strlen (pz);
173 return parse_scaled_value (res, &pz, ps, SEC_PER_DAY);
176 static time_t
177 parse_yearmonthday (cch_t * in_pz)
179 time_t res = 0;
180 char buf[8];
181 cch_t * pz;
183 if (strlen (in_pz) != 8)
185 errno = EINVAL;
186 return BAD_TIME;
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);
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);
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);
205 static time_t
206 parse_YMWD (cch_t * pz)
208 time_t res = 0;
209 cch_t * ps = strchr (pz, 'Y');
210 if (ps != NULL)
212 res = parse_scaled_value (0, &pz, ps, SEC_PER_YEAR);
213 pz++;
216 ps = strchr (pz, 'M');
217 if (ps != NULL)
219 res = parse_scaled_value (res, &pz, ps, SEC_PER_MONTH);
220 pz++;
223 ps = strchr (pz, 'W');
224 if (ps != NULL)
226 res = parse_scaled_value (res, &pz, ps, SEC_PER_WEEK);
227 pz++;
230 ps = strchr (pz, 'D');
231 if (ps != NULL)
233 res = parse_scaled_value (res, &pz, ps, SEC_PER_DAY);
234 pz++;
237 while (isspace ((unsigned char)*pz)) pz++;
238 if (*pz != NUL)
240 errno = EINVAL;
241 return BAD_TIME;
244 return res;
247 static time_t
248 parse_hour_minute_second (cch_t * pz, cch_t * ps)
250 time_t res = 0;
252 res = parse_scaled_value (0, &pz, ps, SEC_PER_HR);
254 ps = strchr (++pz, ':');
255 if (ps == NULL)
257 errno = EINVAL;
258 return BAD_TIME;
261 res = parse_scaled_value (res, &pz, ps, SEC_PER_MIN);
263 pz++;
264 ps = pz + strlen (pz);
265 return parse_scaled_value (res, &pz, ps, 1);
268 static time_t
269 parse_hourminutesecond (cch_t * in_pz)
271 time_t res = 0;
272 char buf[4];
273 cch_t * pz;
275 if (strlen (in_pz) != 6)
277 errno = EINVAL;
278 return BAD_TIME;
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);
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);
291 memcpy (buf, in_pz + 4, 2);
292 buf[2] = NUL;
293 pz = buf;
294 return parse_scaled_value (res, &pz, buf + 2, 1);
297 static time_t
298 parse_HMS (cch_t * pz)
300 time_t res = 0;
301 cch_t * ps = strchr (pz, 'H');
302 if (ps != NULL)
304 res = parse_scaled_value (0, &pz, ps, SEC_PER_HR);
305 pz++;
308 ps = strchr (pz, 'M');
309 if (ps != NULL)
311 res = parse_scaled_value (res, &pz, ps, SEC_PER_MIN);
312 pz++;
315 ps = strchr (pz, 'S');
316 if (ps != NULL)
318 res = parse_scaled_value (res, &pz, ps, 1);
319 pz++;
322 while (isspace ((unsigned char)*pz)) pz++;
323 if (*pz != NUL)
325 errno = EINVAL;
326 return BAD_TIME;
329 return res;
332 static time_t
333 parse_time (cch_t * pz)
335 cch_t * ps;
336 time_t res = 0;
339 * Scan for a hyphen
341 ps = strchr (pz, ':');
342 if (ps != NULL)
344 res = parse_hour_minute_second (pz, ps);
348 * Try for a 'H', 'M' or 'S' suffix
350 else if (ps = strpbrk (pz, "HMS"),
351 ps == NULL)
353 /* Its a YYYYMMDD format: */
354 res = parse_hourminutesecond (pz);
357 else
358 res = parse_HMS (pz);
360 return res;
363 static char *
364 trim(char * pz)
366 /* trim leading white space */
367 while (isspace ((unsigned char)*pz)) pz++;
369 /* trim trailing white space */
371 char * pe = pz + strlen (pz);
372 while ((pe > pz) && isspace ((unsigned char)pe[-1])) pe--;
373 *pe = NUL;
376 return pz;
380 * Parse the year/months/days of a time period
382 static time_t
383 parse_period (cch_t * in_pz)
385 char * pz = xstrdup (in_pz);
386 char * pT = strchr (pz, 'T');
387 char * ps;
388 void * fptr = pz;
389 time_t res = 0;
391 if (pT != NUL)
393 *(pT++) = NUL;
394 pz = trim (pz);
395 pT = trim (pT);
399 * Scan for a hyphen
401 ps = strchr (pz, '-');
402 if (ps != NULL)
404 res = parse_year_month_day (pz, ps);
408 * Try for a 'Y', 'M' or 'D' suffix
410 else if (ps = strpbrk (pz, "YMWD"),
411 ps == NULL)
413 /* Its a YYYYMMDD format: */
414 res = parse_yearmonthday (pz);
417 else
418 res = parse_YMWD (pz);
420 if ((errno == 0) && (pT != NULL))
422 time_t val = parse_time (pT);
423 res = scale_n_add (res, val, 1);
426 free (fptr);
427 return res;
430 static time_t
431 parse_non_iso8601(cch_t * pz)
433 whats_done_t whatd_we_do = NOTHING_IS_DONE;
435 time_t res = 0;
437 do {
438 time_t val;
440 errno = 0;
441 val = str_const_to_l (pz, &pz, 10);
442 if (errno != 0)
443 goto bad_time;
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 == ':')
451 if (whatd_we_do >= MINUTE_IS_DONE)
452 break;
454 val = parse_hr_min_sec (val, pz);
456 if ((whatd_we_do == HOUR_IS_DONE) && (val >= SEC_PER_HR))
457 break;
459 return scale_n_add (res, val, 1);
463 unsigned int mult;
465 /* Skip over white space following the number we just parsed. */
466 while (isspace ((unsigned char)*pz)) pz++;
468 switch (*pz)
470 default: goto bad_time;
471 case NUL:
472 return scale_n_add (res, val, 1);
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;
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;
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;
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;
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;
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;
516 case 's':
517 mult = 1;
518 whatd_we_do = SECOND_IS_DONE;
519 break;
522 res = scale_n_add (res, val, mult);
524 while (isspace ((unsigned char)*++pz)) ;
525 if (*pz == NUL)
526 return res;
528 if (! isdigit ((unsigned char)*pz))
529 break;
532 } while (whatd_we_do < SECOND_IS_DONE);
534 bad_time:
535 errno = EINVAL;
536 return BAD_TIME;
539 time_t
540 parse_duration (char const * pz)
542 time_t res = 0;
544 while (isspace ((unsigned char)*pz)) pz++;
546 do {
547 if (*pz == 'P')
549 res = parse_period (pz + 1);
550 if ((errno != 0) || (res == BAD_TIME))
551 break;
552 return res;
555 if (*pz == 'T')
557 res = parse_time (pz + 1);
558 if ((errno != 0) || (res == BAD_TIME))
559 break;
560 return res;
563 if (! isdigit ((unsigned char)*pz))
564 break;
566 res = parse_non_iso8601 (pz);
567 if ((errno == 0) && (res != BAD_TIME))
568 return res;
570 } while (0);
572 fprintf (stderr, _("Invalid time duration: %s\n"), pz);
573 if (errno == 0)
574 errno = EINVAL;
575 return BAD_TIME;
579 * Local Variables:
580 * mode: C
581 * c-file-style: "gnu"
582 * indent-tabs-mode: nil
583 * End:
584 * end of parse-duration.c */