File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / ntp / sntp / libopts / parse-duration.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Tue May 29 12:08:38 2012 UTC (12 years, 2 months ago) by misho
Branches: ntp, MAIN
CVS tags: v4_2_6p5p0, v4_2_6p5, HEAD
ntp 4.2.6p5

    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>