# HG changeset patch # User Bruce Korb # Date 1226923266 -3600 # Node ID b3d8e7fe1fe7a662231a54d22c09ae7560d04139 # Parent 84e8f689534c22312d23892a8224e9c2b30d666b New module 'parse-duration'. diff -r 84e8f689534c -r b3d8e7fe1fe7 ChangeLog --- a/ChangeLog Mon Nov 17 12:29:11 2008 +0100 +++ b/ChangeLog Mon Nov 17 13:01:06 2008 +0100 @@ -1,3 +1,10 @@ +2008-11-17 Bruce Korb + + New module 'parse-duration'. + * lib/parse-duration.h: New file. + * lib/parse-duration.c: New file. + * modules/parse-duration: New file. + 2008-11-17 Bruno Haible * tests/test-select-out.sh: Comment out the first pipe test. diff -r 84e8f689534c -r b3d8e7fe1fe7 lib/parse-duration.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/parse-duration.c Mon Nov 17 13:01:06 2008 +0100 @@ -0,0 +1,582 @@ +/* Parse a time duration and return a seconds count + Copyright (C) 2008 Free Software Foundation, Inc. + Written by Bruce Korb , 2008. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include + +#include +#include +#include +#include +#include +#include + +#include "parse-duration.h" + +#ifndef _ +#define _(_s) _s +#endif + +#ifndef NUL +#define NUL '\0' +#endif + +#define cch_t char const + +typedef enum { + NOTHING_IS_DONE, + YEAR_IS_DONE, + MONTH_IS_DONE, + WEEK_IS_DONE, + DAY_IS_DONE, + HOUR_IS_DONE, + MINUTE_IS_DONE, + SECOND_IS_DONE +} whats_done_t; + +#define SEC_PER_MIN 60 +#define SEC_PER_HR (SEC_PER_MIN * 60) +#define SEC_PER_DAY (SEC_PER_HR * 24) +#define SEC_PER_WEEK (SEC_PER_DAY * 7) +#define SEC_PER_MONTH (SEC_PER_DAY * 30) +#define SEC_PER_YEAR (SEC_PER_DAY * 365) + +#define TIME_MAX 0x7FFFFFFF + +static unsigned long inline +str_const_to_ul (cch_t * str, cch_t ** ppz, int base) +{ + return strtoul (str, (char **)ppz, base); +} + +static long inline +str_const_to_l (cch_t * str, cch_t ** ppz, int base) +{ + return strtol (str, (char **)ppz, base); +} + +static time_t inline +scale_n_add (time_t base, time_t val, int scale) +{ + if (base == BAD_TIME) + { + if (errno == 0) + errno = EINVAL; + return BAD_TIME; + } + + if (val > TIME_MAX / scale) + { + errno = ERANGE; + return BAD_TIME; + } + + val *= scale; + if (base > TIME_MAX - val) + { + errno = ERANGE; + return BAD_TIME; + } + + return base + val; +} + +static time_t +parse_hr_min_sec (time_t start, cch_t * pz) +{ + int lpct = 0; + + errno = 0; + + /* For as long as our scanner pointer points to a colon *AND* + we've not looped before, then keep looping. (two iterations max) */ + while ((*pz == ':') && (lpct++ <= 1)) + { + unsigned long v = str_const_to_ul (pz+1, &pz, 10); + + if (errno != 0) + return BAD_TIME; + + start = scale_n_add (v, start, 60); + + if (errno != 0) + return BAD_TIME; + } + + /* allow for trailing spaces */ + while (isspace ((unsigned char)*pz)) pz++; + if (*pz != NUL) + { + errno = EINVAL; + return BAD_TIME; + } + + return start; +} + +static time_t +parse_scaled_value (time_t base, cch_t ** ppz, cch_t * endp, int scale) +{ + cch_t * pz = *ppz; + time_t val; + + if (base == BAD_TIME) + return base; + + errno = 0; + val = str_const_to_ul (pz, &pz, 10); + if (errno != 0) + return BAD_TIME; + while (isspace ((unsigned char)*pz)) pz++; + if (pz != endp) + { + errno = EINVAL; + return BAD_TIME; + } + + *ppz = pz; + return scale_n_add (base, val, scale); +} + +static time_t +parse_year_month_day (cch_t * pz, cch_t * ps) +{ + time_t res = 0; + + res = parse_scaled_value (0, &pz, ps, SEC_PER_YEAR); + + ps = strchr (++pz, '-'); + if (ps == NULL) + { + errno = EINVAL; + return BAD_TIME; + } + res = parse_scaled_value (res, &pz, ps, SEC_PER_MONTH); + + pz++; + ps = pz + strlen (pz); + return parse_scaled_value (res, &pz, ps, SEC_PER_DAY); +} + +static time_t +parse_yearmonthday (cch_t * in_pz) +{ + time_t res = 0; + char buf[8]; + cch_t * pz; + + if (strlen (in_pz) != 8) + { + errno = EINVAL; + return BAD_TIME; + } + + memcpy (buf, in_pz, 4); + buf[4] = NUL; + pz = buf; + res = parse_scaled_value (0, &pz, buf + 4, SEC_PER_YEAR); + + memcpy (buf, in_pz + 4, 2); + buf[2] = NUL; + pz = buf; + res = parse_scaled_value (res, &pz, buf + 2, SEC_PER_MONTH); + + memcpy (buf, in_pz + 6, 2); + buf[2] = NUL; + pz = buf; + return parse_scaled_value (res, &pz, buf + 2, SEC_PER_DAY); +} + +static time_t +parse_YMWD (cch_t * pz) +{ + time_t res = 0; + cch_t * ps = strchr (pz, 'Y'); + if (ps != NULL) + { + res = parse_scaled_value (0, &pz, ps, SEC_PER_YEAR); + pz++; + } + + ps = strchr (pz, 'M'); + if (ps != NULL) + { + res = parse_scaled_value (res, &pz, ps, SEC_PER_MONTH); + pz++; + } + + ps = strchr (pz, 'W'); + if (ps != NULL) + { + res = parse_scaled_value (res, &pz, ps, SEC_PER_WEEK); + pz++; + } + + ps = strchr (pz, 'D'); + if (ps != NULL) + { + res = parse_scaled_value (res, &pz, ps, SEC_PER_DAY); + pz++; + } + + while (isspace ((unsigned char)*pz)) pz++; + if (*pz != NUL) + { + errno = EINVAL; + return BAD_TIME; + } + + return res; +} + +static time_t +parse_hour_minute_second (cch_t * pz, cch_t * ps) +{ + time_t res = 0; + + res = parse_scaled_value (0, &pz, ps, SEC_PER_HR); + + ps = strchr (++pz, ':'); + if (ps == NULL) + { + errno = EINVAL; + return BAD_TIME; + } + + res = parse_scaled_value (res, &pz, ps, SEC_PER_MIN); + + pz++; + ps = pz + strlen (pz); + return parse_scaled_value (res, &pz, ps, 1); +} + +static time_t +parse_hourminutesecond (cch_t * in_pz) +{ + time_t res = 0; + char buf[4]; + cch_t * pz; + + if (strlen (in_pz) != 6) + { + errno = EINVAL; + return BAD_TIME; + } + + memcpy (buf, in_pz, 2); + buf[2] = NUL; + pz = buf; + res = parse_scaled_value (0, &pz, buf + 2, SEC_PER_HR); + + memcpy (buf, in_pz + 2, 2); + buf[2] = NUL; + pz = buf; + res = parse_scaled_value (res, &pz, buf + 2, SEC_PER_MIN); + + memcpy (buf, in_pz + 4, 2); + buf[2] = NUL; + pz = buf; + return parse_scaled_value (res, &pz, buf + 2, 1); +} + +static time_t +parse_HMS (cch_t * pz) +{ + time_t res = 0; + cch_t * ps = strchr (pz, 'H'); + if (ps != NULL) + { + res = parse_scaled_value (0, &pz, ps, SEC_PER_HR); + pz++; + } + + ps = strchr (pz, 'M'); + if (ps != NULL) + { + res = parse_scaled_value (res, &pz, ps, SEC_PER_MIN); + pz++; + } + + ps = strchr (pz, 'S'); + if (ps != NULL) + { + res = parse_scaled_value (res, &pz, ps, 1); + pz++; + } + + while (isspace ((unsigned char)*pz)) pz++; + if (*pz != NUL) + { + errno = EINVAL; + return BAD_TIME; + } + + return res; +} + +static time_t +parse_time (cch_t * pz) +{ + cch_t * ps; + time_t res = 0; + + /* + * Scan for a hyphen + */ + ps = strchr (pz, ':'); + if (ps != NULL) + { + res = parse_hour_minute_second (pz, ps); + } + + /* + * Try for a 'H', 'M' or 'S' suffix + */ + else if (ps = strpbrk (pz, "HMS"), + ps == NULL) + { + /* Its a YYYYMMDD format: */ + res = parse_hourminutesecond (pz); + } + + else + res = parse_HMS (pz); + + return res; +} + +static char * +trim(char * pz) +{ + /* trim leading white space */ + while (isspace ((unsigned char)*pz)) pz++; + + /* trim trailing white space */ + { + char * pe = pz + strlen (pz); + while ((pe > pz) && isspace ((unsigned char)pe[-1])) pe--; + *pe = NUL; + } + + return pz; +} + +/* + * Parse the year/months/days of a time period + */ +static time_t +parse_period (cch_t * in_pz) +{ + char * pz = xstrdup (in_pz); + char * pT = strchr (pz, 'T'); + char * ps; + void * fptr = pz; + time_t res = 0; + + if (pT != NUL) + { + *(pT++) = NUL; + pz = trim (pz); + pT = trim (pT); + } + + /* + * Scan for a hyphen + */ + ps = strchr (pz, '-'); + if (ps != NULL) + { + res = parse_year_month_day (pz, ps); + } + + /* + * Try for a 'Y', 'M' or 'D' suffix + */ + else if (ps = strpbrk (pz, "YMWD"), + ps == NULL) + { + /* Its a YYYYMMDD format: */ + res = parse_yearmonthday (pz); + } + + else + res = parse_YMWD (pz); + + if ((errno == 0) && (pT != NULL)) + { + time_t val = parse_time (pT); + res = scale_n_add (res, val, 1); + } + + free (fptr); + return res; +} + +static time_t +parse_non_iso8601(cch_t * pz) +{ + whats_done_t whatd_we_do = NOTHING_IS_DONE; + + time_t res = 0; + + do { + time_t val; + + errno = 0; + val = str_const_to_l (pz, &pz, 10); + if (errno != 0) + goto bad_time; + + /* IF we find a colon, then we're going to have a seconds value. + We will not loop here any more. We cannot already have parsed + a minute value and if we've parsed an hour value, then the result + value has to be less than an hour. */ + if (*pz == ':') + { + if (whatd_we_do >= MINUTE_IS_DONE) + break; + + val = parse_hr_min_sec (val, pz); + + if ((whatd_we_do == HOUR_IS_DONE) && (val >= SEC_PER_HR)) + break; + + return scale_n_add (res, val, 1); + } + + { + unsigned int mult; + + /* Skip over white space following the number we just parsed. */ + while (isspace ((unsigned char)*pz)) pz++; + + switch (*pz) + { + default: goto bad_time; + case NUL: + return scale_n_add (res, val, 1); + + case 'y': case 'Y': + if (whatd_we_do >= YEAR_IS_DONE) + goto bad_time; + mult = SEC_PER_YEAR; + whatd_we_do = YEAR_IS_DONE; + break; + + case 'M': + if (whatd_we_do >= MONTH_IS_DONE) + goto bad_time; + mult = SEC_PER_MONTH; + whatd_we_do = MONTH_IS_DONE; + break; + + case 'W': + if (whatd_we_do >= WEEK_IS_DONE) + goto bad_time; + mult = SEC_PER_WEEK; + whatd_we_do = WEEK_IS_DONE; + break; + + case 'd': case 'D': + if (whatd_we_do >= DAY_IS_DONE) + goto bad_time; + mult = SEC_PER_DAY; + whatd_we_do = DAY_IS_DONE; + break; + + case 'h': + if (whatd_we_do >= HOUR_IS_DONE) + goto bad_time; + mult = SEC_PER_HR; + whatd_we_do = HOUR_IS_DONE; + break; + + case 'm': + if (whatd_we_do >= MINUTE_IS_DONE) + goto bad_time; + mult = SEC_PER_MIN; + whatd_we_do = MINUTE_IS_DONE; + break; + + case 's': + mult = 1; + whatd_we_do = SECOND_IS_DONE; + break; + } + + res = scale_n_add (res, val, mult); + + while (isspace ((unsigned char)*++pz)) ; + if (*pz == NUL) + return res; + + if (! isdigit ((unsigned char)*pz)) + break; + } + + } while (whatd_we_do < SECOND_IS_DONE); + + bad_time: + errno = EINVAL; + return BAD_TIME; +} + +time_t +parse_duration (char const * pz) +{ + time_t res = 0; + + while (isspace ((unsigned char)*pz)) pz++; + + do { + if (*pz == 'P') + { + res = parse_period (pz + 1); + if ((errno != 0) || (res == BAD_TIME)) + break; + return res; + } + + if (*pz == 'T') + { + res = parse_time (pz + 1); + if ((errno != 0) || (res == BAD_TIME)) + break; + return res; + } + + if (! isdigit ((unsigned char)*pz)) + break; + + res = parse_non_iso8601 (pz); + if ((errno == 0) && (res != BAD_TIME)) + return res; + + } while (0); + + fprintf (stderr, _("Invalid time duration: %s\n"), pz); + if (errno == 0) + errno = EINVAL; + return BAD_TIME; +} + +/* + * Local Variables: + * mode: C + * c-file-style: "gnu" + * indent-tabs-mode: nil + * End: + * end of parse-duration.c */ diff -r 84e8f689534c -r b3d8e7fe1fe7 lib/parse-duration.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/parse-duration.h Mon Nov 17 13:01:06 2008 +0100 @@ -0,0 +1,82 @@ +/* Parse a time duration and return a seconds count + Copyright (C) 2008 Free Software Foundation, Inc. + Written by Bruce Korb , 2008. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* + + Readers and users of this function are referred to the ISO-8601 + specification, with particular attention to "Durations". + + At the time of writing, this worked: + + http://en.wikipedia.org/wiki/ISO_8601#Durations + + The string must start with a 'P', 'T' or a digit. + + ==== if it is a digit + + the string may contain: NNN d NNN h NNN m NNN s + This represents NNN days, NNN hours, NNN minutes and NNN seconds. + The embeded white space is optional. + These terms must appear in this order. + The final "s" is optional. + All of the terms ("NNN" plus designator) are optional. + Minutes and seconds may optionally be represented as NNN:NNN. + Also, hours, minute and seconds may be represented as NNN:NNN:NNN. + There is no limitation on the value of any of the terms, except + that the final result must fit in a time_t value. + + ==== if it is a 'P' or 'T', please see ISO-8601 for a rigorous definition. + + The 'P' term may be followed by any of three formats: + yyyymmdd + yy-mm-dd + yy Y mm M ww W dd D + + or it may be empty and followed by a 'T'. The "yyyymmdd" must be eight + digits long. Note: months are always 30 days and years are always 365 + days long. 5 years is always 1825, not 1826 or 1827 depending on leap + year considerations. 3 months is always 90 days. There is no consideration + for how many days are in the current, next or previous months. + + For the final format: + * Embedded white space is allowed, but it is optional. + * All of the terms are optional. Any or all-but-one may be omitted. + * The meanings are yy years, mm months, ww weeks and dd days. + * The terms must appear in this order. + + ==== The 'T' term may be followed by any of these formats: + + hhmmss + hh:mm:ss + hh H mm M ss S + + For the final format: + * Embedded white space is allowed, but it is optional. + * All of the terms are optional. Any or all-but-one may be omitted. + * The terms must appear in this order. + + */ +#ifndef GNULIB_PARSE_DURATION_H +#define GNULIB_PARSE_DURATION_H + +#include + +#define BAD_TIME ((time_t)~0) + +extern time_t parse_duration(char const * in_pz); + +#endif /* GNULIB_PARSE_DURATION_H */ diff -r 84e8f689534c -r b3d8e7fe1fe7 modules/parse-duration --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/parse-duration Mon Nov 17 13:01:06 2008 +0100 @@ -0,0 +1,24 @@ +Description: +Parse a duration given as string. + +Files: +lib/parse-duration.h +lib/parse-duration.c + +Depends-on: + +configure.ac: +AC_REQUIRE([AC_C_INLINE]) + +Makefile.am: +lib_SOURCES += parse-duration.c + +Include: +"parse-duration.h" + +License: +GPL + +Maintainer: +Bruce Korb +