bug-gnulib
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

parse_time()


From: Bruce Korb
Subject: parse_time()
Date: Sat, 01 Nov 2008 16:37:00 -0700
User-agent: Thunderbird 2.0.0.12 (X11/20071114)

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 <address@hidden>, 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;
}

reply via email to

[Prev in Thread] Current Thread [Next in Thread]