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