Return to parse-duration.c CVS log | Up to [ELWIX - Embedded LightWeight unIX -] / embedaddon / ntp / sntp / libopts |
1.1 ! misho 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 */