root/maint/gnulib/lib/parse-duration.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. str_const_to_ul
  2. str_const_to_l
  3. scale_n_add
  4. parse_hr_min_sec
  5. parse_scaled_value
  6. parse_year_month_day
  7. parse_yearmonthday
  8. parse_YMWD
  9. parse_hour_minute_second
  10. parse_hourminutesecond
  11. parse_HMS
  12. parse_time
  13. trim
  14. parse_period
  15. parse_non_iso8601
  16. parse_duration

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

/* [previous][next][first][last][top][bottom][index][help] */