Hi, I had need of a function to convert some variation of a time (duration) specification into a count of seconds. For your amusement and optional inclusion...(I *think* I've got papers on file for gnulib....)
It accepts several formats: [DD d] [HH h] [MM m] [SS s] [DD d] [[HH:]MM:]SS [DD d] [HH h] [MM:]SS though it is unhappy if it finds nothing. Cheers - Bruce
/* Parse a time duration and return a seconds count Copyright (C) 2008 Free Software Foundation, Inc. Written by Bruce Korb <[EMAIL PROTECTED]>, 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 <http://www.gnu.org/licenses/>. */ #include <config.h> #include <ctype.h> #include <errno.h> #include <limits.h> #include <time.h> #ifndef _ #define _(_s) _s #endif #ifndef NUL #define NUL '\0' #endif typedef enum { NOTHING_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 TIME_MAX 0x7FFFFFFF static time_t parse_hr_min_sec(time_t start, char * pz) { int lpct = 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++ == 0)) { if ( (start > TIME_MAX / 60) || ! isdigit((int)*pz)) { errno = EINVAL; return ~0; } start *= 60; errno = 0; { unsigned long v = strtoul(pz, &pz, 10); if (errno != 0) return ~0; if (start > TIME_MAX - v) { errno = EINVAL; return ~0; } start += v; } } /* allow for trailing spaces */ while (isspace(*pz)) pz++; if (*pz != NUL) { errno = EINVAL; return ~0; } return start; } time_t parse_time(char const * in_pz) { whats_done_t whatd_we_do = NOTHING_IS_DONE; char * pz; time_t val; time_t res = 0; while (isspace(*in_pz)) in_pz++; if (! isdigit((int)*in_pz)) goto bad_time; do { errno = 0; val = strtol(in_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 ((errno != 0) || (res > TIME_MAX - val)) break; if ((whatd_we_do == HOUR_IS_DONE) && (val >= SEC_PER_HR)) break; /* Check for overflow */ if (res > TIME_MAX - val) break; return res + val; } { unsigned int mult; while (isspace(*pz)) pz++; switch (*pz) { default: goto bad_time; case NUL: /* Check for overflow */ if (res > TIME_MAX - val) goto bad_time; return val + res; 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; } /* Check for overflow: value that overflows or an overflowing result when "val" gets added to it. */ if (val > TIME_MAX / mult) break; val *= mult; if (res > TIME_MAX - val) break; res += val; while (isspace(*pz)) pz++; if (*pz == NUL) return res; if (! isdigit(*pz)) break; } } while (whatd_we_do < SECOND_IS_DONE); bad_time: fprintf(stderr, _("Invalid time duration: %s\n"), in_pz); errno = EINVAL; return (time_t)~0; }