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