parse-duration.c revision 285612
140939Sdes/* Parse a time duration and return a seconds count 2310060Sdes Copyright (C) 2008-2015 Free Software Foundation, Inc. 3253680Sdes Written by Bruce Korb <bkorb@gnu.org>, 2008. 440939Sdes 540939Sdes This program is free software: you can redistribute it and/or modify 640939Sdes it under the terms of the GNU Lesser General Public License as published by 740939Sdes the Free Software Foundation; either version 2.1 of the License, or 840939Sdes (at your option) any later version. 940939Sdes 1040939Sdes This program is distributed in the hope that it will be useful, 1140939Sdes but WITHOUT ANY WARRANTY; without even the implied warranty of 1240939Sdes MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1340939Sdes GNU Lesser General Public License for more details. 1440939Sdes 1540939Sdes You should have received a copy of the GNU Lesser General Public License 1640939Sdes along with this program. If not, see <http://www.gnu.org/licenses/>. */ 1740939Sdes 1840939Sdes#include <config.h> 1940939Sdes 2040939Sdes/* Specification. */ 2140939Sdes#include "parse-duration.h" 2240939Sdes 2340939Sdes#include <ctype.h> 2440939Sdes#include <errno.h> 2540939Sdes#include <limits.h> 2640939Sdes#include <stdio.h> 2740939Sdes#include <stdlib.h> 2840939Sdes#include <string.h> 2940939Sdes 3084203Sdillon#include "intprops.h" 3184203Sdillon 3284203Sdillon#ifndef NUL 3341862Sdes#define NUL '\0' 3440939Sdes#endif 3555557Sdes 3662981Sdes#define cch_t char const 37174752Sdes 3840939Sdestypedef enum { 3940939Sdes NOTHING_IS_DONE, 40174752Sdes YEAR_IS_DONE, 4140939Sdes MONTH_IS_DONE, 42210568Sdes WEEK_IS_DONE, 4340939Sdes DAY_IS_DONE, 44262560Sdes HOUR_IS_DONE, 45109695Sdes MINUTE_IS_DONE, 4660924Sdes SECOND_IS_DONE 4741862Sdes} whats_done_t; 4841862Sdes 4940939Sdes#define SEC_PER_MIN 60 5040939Sdes#define SEC_PER_HR (SEC_PER_MIN * 60) 5140939Sdes#define SEC_PER_DAY (SEC_PER_HR * 24) 52253680Sdes#define SEC_PER_WEEK (SEC_PER_DAY * 7) 53253680Sdes#define SEC_PER_MONTH (SEC_PER_DAY * 30) 54253680Sdes#define SEC_PER_YEAR (SEC_PER_DAY * 365) 55253680Sdes 5640939Sdes#undef MAX_DURATION 5740939Sdes#define MAX_DURATION TYPE_MAXIMUM(time_t) 5840939Sdes 5940975Sdes/* Wrapper around strtoul that does not require a cast. */ 6040939Sdesstatic unsigned long 6140939Sdesstr_const_to_ul (cch_t * str, cch_t ** ppz, int base) 6240939Sdes{ 6340939Sdes return strtoul (str, (char **)ppz, base); 6440939Sdes} 65174588Sdes 66121423Sume/* Wrapper around strtol that does not require a cast. */ 6790267Sdesstatic long 68121423Sumestr_const_to_l (cch_t * str, cch_t ** ppz, int base) 6990267Sdes{ 7090267Sdes return strtol (str, (char **)ppz, base); 7190267Sdes} 7290267Sdes 7340939Sdes/* Returns BASE + VAL * SCALE, interpreting BASE = BAD_TIME 7440939Sdes with errno set as an error situation, and returning BAD_TIME 7562981Sdes with errno set in an error situation. */ 7675891Sarchiestatic time_t 7740939Sdesscale_n_add (time_t base, time_t val, int scale) 7862981Sdes{ 7940939Sdes if (base == BAD_TIME) 8040939Sdes { 8140939Sdes if (errno == 0) 8240939Sdes errno = EINVAL; 8340939Sdes return BAD_TIME; 8460924Sdes } 85174588Sdes 8640939Sdes if (val > MAX_DURATION / scale) 8790267Sdes { 8890267Sdes errno = ERANGE; 8990267Sdes return BAD_TIME; 9040939Sdes } 9140939Sdes 9240939Sdes val *= scale; 9340939Sdes if (base > MAX_DURATION - val) 9440939Sdes { 9540939Sdes errno = ERANGE; 96174588Sdes return BAD_TIME; 9740939Sdes } 98174588Sdes 9990267Sdes return base + val; 10090267Sdes} 10140939Sdes 10240939Sdes/* After a number HH has been parsed, parse subsequent :MM or :MM:SS. */ 10340939Sdesstatic time_t 10440939Sdesparse_hr_min_sec (time_t start, cch_t * pz) 10540939Sdes{ 10640939Sdes int lpct = 0; 107174588Sdes 10840939Sdes errno = 0; 10990267Sdes 11090267Sdes /* For as long as our scanner pointer points to a colon *AND* 11190267Sdes we've not looped before, then keep looping. (two iterations max) */ 11290267Sdes while ((*pz == ':') && (lpct++ <= 1)) 11390267Sdes { 11490267Sdes unsigned long v = str_const_to_ul (pz+1, &pz, 10); 11590267Sdes 11690267Sdes if (errno != 0) 11790267Sdes return BAD_TIME; 11890267Sdes 11990267Sdes start = scale_n_add (v, start, 60); 12090267Sdes 12190267Sdes if (errno != 0) 12290267Sdes return BAD_TIME; 12390267Sdes } 12490267Sdes 12590267Sdes /* allow for trailing spaces */ 12690267Sdes while (isspace ((unsigned char)*pz)) 12790267Sdes pz++; 12890267Sdes if (*pz != NUL) 12990267Sdes { 13090267Sdes errno = EINVAL; 13190267Sdes return BAD_TIME; 13290267Sdes } 13390267Sdes 13490267Sdes return start; 13590267Sdes} 13690267Sdes 13790267Sdes/* Parses a value and returns BASE + value * SCALE, interpreting 13890267Sdes BASE = BAD_TIME with errno set as an error situation, and returning 13990267Sdes BAD_TIME with errno set in an error situation. */ 14090267Sdesstatic time_t 14190267Sdesparse_scaled_value (time_t base, cch_t ** ppz, cch_t * endp, int scale) 14290267Sdes{ 14390267Sdes cch_t * pz = *ppz; 14490267Sdes time_t val; 14590267Sdes 14690267Sdes if (base == BAD_TIME) 14790267Sdes return base; 14890267Sdes 14990267Sdes errno = 0; 15090267Sdes val = str_const_to_ul (pz, &pz, 10); 15190267Sdes if (errno != 0) 15290267Sdes return BAD_TIME; 15390267Sdes while (isspace ((unsigned char)*pz)) 15490267Sdes pz++; 15590267Sdes if (pz != endp) 156315904Sdes { 15790267Sdes errno = EINVAL; 15890267Sdes return BAD_TIME; 15990267Sdes } 16040939Sdes 16140939Sdes *ppz = pz; 16240939Sdes return scale_n_add (base, val, scale); 16341862Sdes} 16441862Sdes 16541862Sdes/* Parses the syntax YEAR-MONTH-DAY. 16660924Sdes PS points into the string, after "YEAR", before "-MONTH-DAY". */ 167174588Sdesstatic time_t 16841862Sdesparse_year_month_day (cch_t * pz, cch_t * ps) 16990267Sdes{ 17090267Sdes time_t res = 0; 17190267Sdes 17290267Sdes res = parse_scaled_value (0, &pz, ps, SEC_PER_YEAR); 17390267Sdes 17490267Sdes pz++; /* over the first '-' */ 17541862Sdes ps = strchr (pz, '-'); 17641862Sdes if (ps == NULL) 17741862Sdes { 17840939Sdes errno = EINVAL; 17940939Sdes return BAD_TIME; 18040939Sdes } 18168551Sdes res = parse_scaled_value (res, &pz, ps, SEC_PER_MONTH); 18268551Sdes 18368551Sdes pz++; /* over the second '-' */ 184174588Sdes ps = pz + strlen (pz); 18568551Sdes return parse_scaled_value (res, &pz, ps, SEC_PER_DAY); 18690267Sdes} 18768551Sdes 18890267Sdes/* Parses the syntax YYYYMMDD. */ 18990267Sdesstatic time_t 19090267Sdesparse_yearmonthday (cch_t * in_pz) 19190267Sdes{ 19290267Sdes time_t res = 0; 19390267Sdes char buf[8]; 19490267Sdes cch_t * pz; 19568551Sdes 19668551Sdes if (strlen (in_pz) != 8) 19768551Sdes { 19868551Sdes errno = EINVAL; 19968551Sdes return BAD_TIME; 20068551Sdes } 201174588Sdes 20268551Sdes memcpy (buf, in_pz, 4); 20390267Sdes buf[4] = NUL; 20490267Sdes pz = buf; 20590267Sdes res = parse_scaled_value (0, &pz, buf + 4, SEC_PER_YEAR); 20690267Sdes 20790267Sdes memcpy (buf, in_pz + 4, 2); 20868551Sdes buf[2] = NUL; 20968551Sdes pz = buf; 21098117Sdes res = parse_scaled_value (res, &pz, buf + 2, SEC_PER_MONTH); 21168551Sdes 21297866Sdes memcpy (buf, in_pz + 6, 2); 21397866Sdes buf[2] = NUL; 21497866Sdes pz = buf; 215174588Sdes return parse_scaled_value (res, &pz, buf + 2, SEC_PER_DAY); 21697866Sdes} 21797866Sdes 218236193Sjilles/* Parses the syntax yy Y mm M ww W dd D. */ 21997866Sdesstatic time_t 22097866Sdesparse_YMWD (cch_t * pz) 221109967Sdes{ 22297866Sdes time_t res = 0; 223221830Sdes cch_t * ps = strchr (pz, 'Y'); 224236193Sjilles if (ps != NULL) 22597866Sdes { 22698117Sdes res = parse_scaled_value (0, &pz, ps, SEC_PER_YEAR); 22797866Sdes pz++; 22897866Sdes } 22997866Sdes 23097866Sdes ps = strchr (pz, 'M'); 23197866Sdes if (ps != NULL) 23298117Sdes { 23398117Sdes res = parse_scaled_value (res, &pz, ps, SEC_PER_MONTH); 23498117Sdes pz++; 235174588Sdes } 23698117Sdes 23798117Sdes ps = strchr (pz, 'W'); 23898117Sdes if (ps != NULL) 23998117Sdes { 24098117Sdes res = parse_scaled_value (res, &pz, ps, SEC_PER_WEEK); 24198117Sdes pz++; 24298117Sdes } 24398117Sdes 244310060Sdes ps = strchr (pz, 'D'); 245310060Sdes if (ps != NULL) 246310060Sdes { 247310060Sdes res = parse_scaled_value (res, &pz, ps, SEC_PER_DAY); 248310060Sdes pz++; 249310060Sdes } 250310060Sdes 251315904Sdes while (isspace ((unsigned char)*pz)) 252315904Sdes pz++; 253310060Sdes if (*pz != NUL) 254310060Sdes { 255315904Sdes errno = EINVAL; 256315904Sdes return BAD_TIME; 257315904Sdes } 258315904Sdes 259315904Sdes return res; 260315904Sdes} 261315904Sdes 262315904Sdes/* Parses the syntax HH:MM:SS. 263315904Sdes PS points into the string, after "HH", before ":MM:SS". */ 264315904Sdesstatic time_t 265315904Sdesparse_hour_minute_second (cch_t * pz, cch_t * ps) 266315904Sdes{ 267315904Sdes time_t res = 0; 268315904Sdes 269315904Sdes res = parse_scaled_value (0, &pz, ps, SEC_PER_HR); 270315904Sdes 271310060Sdes pz++; 272315904Sdes ps = strchr (pz, ':'); 273310060Sdes if (ps == NULL) 274315904Sdes { 275310060Sdes errno = EINVAL; 276310060Sdes return BAD_TIME; 277315904Sdes } 278310060Sdes 279310060Sdes res = parse_scaled_value (res, &pz, ps, SEC_PER_MIN); 280315904Sdes 281315904Sdes pz++; 282315904Sdes ps = pz + strlen (pz); 283315904Sdes return parse_scaled_value (res, &pz, ps, 1); 284315904Sdes} 285315904Sdes 286310060Sdes/* Parses the syntax HHMMSS. */ 287310060Sdesstatic time_t 288315904Sdesparse_hourminutesecond (cch_t * in_pz) 289310060Sdes{ 290315904Sdes time_t res = 0; 291315904Sdes char buf[4]; 292310060Sdes cch_t * pz; 293315904Sdes 294323660Smarius if (strlen (in_pz) != 6) 295310060Sdes { 296310060Sdes errno = EINVAL; 297310060Sdes return BAD_TIME; 298310060Sdes } 299310060Sdes 300310060Sdes memcpy (buf, in_pz, 2); 301310060Sdes buf[2] = NUL; 302310060Sdes pz = buf; 303310060Sdes res = parse_scaled_value (0, &pz, buf + 2, SEC_PER_HR); 304310060Sdes 305310060Sdes memcpy (buf, in_pz + 2, 2); 306310060Sdes buf[2] = NUL; 307310060Sdes pz = buf; 308310060Sdes res = parse_scaled_value (res, &pz, buf + 2, SEC_PER_MIN); 309315904Sdes 310315904Sdes memcpy (buf, in_pz + 4, 2); 311315904Sdes buf[2] = NUL; 312310060Sdes pz = buf; 313310060Sdes return parse_scaled_value (res, &pz, buf + 2, 1); 314310060Sdes} 315310060Sdes 316310060Sdes/* Parses the syntax hh H mm M ss S. */ 317111816Sdesstatic time_t 318111816Sdesparse_HMS (cch_t * pz) 319111816Sdes{ 320174588Sdes time_t res = 0; 321111816Sdes cch_t * ps = strchr (pz, 'H'); 322310060Sdes if (ps != NULL) 323111816Sdes { 324111816Sdes res = parse_scaled_value (0, &pz, ps, SEC_PER_HR); 325310060Sdes pz++; 326111816Sdes } 327310060Sdes 328310060Sdes ps = strchr (pz, 'M'); 329310060Sdes if (ps != NULL) 330310060Sdes { 331310060Sdes res = parse_scaled_value (res, &pz, ps, SEC_PER_MIN); 332310060Sdes pz++; 333310060Sdes } 334111816Sdes 335111816Sdes ps = strchr (pz, 'S'); 336111816Sdes if (ps != NULL) 337111816Sdes { 33840939Sdes res = parse_scaled_value (res, &pz, ps, 1); 33940939Sdes pz++; 34097856Sdes } 341174588Sdes 34240939Sdes while (isspace ((unsigned char)*pz)) 343310060Sdes pz++; 344111816Sdes if (*pz != NUL) 345310060Sdes { 346310060Sdes errno = EINVAL; 34740939Sdes return BAD_TIME; 34890267Sdes } 34941862Sdes 350310060Sdes return res; 35190267Sdes} 352310060Sdes 353310060Sdes/* Parses a time (hours, minutes, seconds) specification in either syntax. */ 354310060Sdesstatic time_t 35540939Sdesparse_time (cch_t * pz) 356310060Sdes{ 357310060Sdes cch_t * ps; 358310060Sdes time_t res = 0; 359310060Sdes 360310060Sdes /* 361310060Sdes * Scan for a hyphen 362310060Sdes */ 36390267Sdes ps = strchr (pz, ':'); 36490267Sdes if (ps != NULL) 365310060Sdes { 366310060Sdes res = parse_hour_minute_second (pz, ps); 367310060Sdes } 368310060Sdes 369310060Sdes /* 370310060Sdes * Try for a 'H', 'M' or 'S' suffix 371310060Sdes */ 372310060Sdes else if (ps = strpbrk (pz, "HMS"), 373310060Sdes ps == NULL) 374310060Sdes { 375310060Sdes /* Its a YYYYMMDD format: */ 376111816Sdes res = parse_hourminutesecond (pz); 377310060Sdes } 378310060Sdes 379310060Sdes else 380310060Sdes res = parse_HMS (pz); 381310060Sdes 382310060Sdes return res; 383310060Sdes} 38490267Sdes 385310060Sdes/* Returns a substring of the given string, with spaces at the beginning and at 38690267Sdes the end destructively removed, per SNOBOL. */ 387310060Sdesstatic char * 38890267Sdestrim (char * pz) 389310060Sdes{ 390310060Sdes /* trim leading white space */ 391315904Sdes while (isspace ((unsigned char)*pz)) 392310060Sdes pz++; 39390267Sdes 39440939Sdes /* trim trailing white space */ 395310060Sdes { 396310060Sdes char * pe = pz + strlen (pz); 397310060Sdes while ((pe > pz) && isspace ((unsigned char)pe[-1])) 398310060Sdes pe--; 399310060Sdes *pe = NUL; 400310060Sdes } 401310060Sdes 402310060Sdes return pz; 403310060Sdes} 404310060Sdes 405310060Sdes/* 406310060Sdes * Parse the year/months/days of a time period 40797856Sdes */ 408310060Sdesstatic time_t 409310060Sdesparse_period (cch_t * in_pz) 410310060Sdes{ 411310060Sdes char * pT; 412310060Sdes char * ps; 41340939Sdes char * pz = strdup (in_pz); 41441989Sdes void * fptr = pz; 415253680Sdes time_t res = 0; 416253680Sdes 417253680Sdes if (pz == NULL) 418253680Sdes { 419253680Sdes errno = ENOMEM; 420253680Sdes return BAD_TIME; 421253680Sdes } 422253680Sdes 423253680Sdes pT = strchr (pz, 'T'); 424253680Sdes if (pT != NULL) 425253680Sdes { 426253680Sdes *(pT++) = NUL; 427253680Sdes pz = trim (pz); 42841989Sdes pT = trim (pT); 42955557Sdes } 430253680Sdes 431253680Sdes /* 432253680Sdes * Scan for a hyphen 433253680Sdes */ 434253680Sdes ps = strchr (pz, '-'); 435253680Sdes if (ps != NULL) 436253680Sdes { 437253680Sdes res = parse_year_month_day (pz, ps); 438253680Sdes } 439253680Sdes 440253680Sdes /* 441253680Sdes * Try for a 'Y', 'M' or 'D' suffix 442253680Sdes */ 443253680Sdes else if (ps = strpbrk (pz, "YMWD"), 444253680Sdes ps == NULL) 445253680Sdes { 446253680Sdes /* Its a YYYYMMDD format: */ 447253680Sdes res = parse_yearmonthday (pz); 448253680Sdes } 449253680Sdes 450253680Sdes else 451253680Sdes res = parse_YMWD (pz); 452253680Sdes 453253680Sdes if ((errno == 0) && (pT != NULL)) 454253680Sdes { 455253680Sdes time_t val = parse_time (pT); 456253680Sdes res = scale_n_add (res, val, 1); 457253680Sdes } 458253680Sdes 459253680Sdes free (fptr); 460253680Sdes return res; 461253680Sdes} 462253680Sdes 463253680Sdesstatic time_t 464253680Sdesparse_non_iso8601 (cch_t * pz) 465253680Sdes{ 466253680Sdes whats_done_t whatd_we_do = NOTHING_IS_DONE; 467253680Sdes 468253680Sdes time_t res = 0; 469253680Sdes 470253680Sdes do { 471253680Sdes time_t val; 472253680Sdes 473253680Sdes errno = 0; 474253680Sdes val = str_const_to_l (pz, &pz, 10); 475253680Sdes if (errno != 0) 476253680Sdes goto bad_time; 477253680Sdes 478253680Sdes /* IF we find a colon, then we're going to have a seconds value. 479253680Sdes We will not loop here any more. We cannot already have parsed 480253680Sdes a minute value and if we've parsed an hour value, then the result 481253680Sdes value has to be less than an hour. */ 482253680Sdes if (*pz == ':') 483253680Sdes { 484253680Sdes if (whatd_we_do >= MINUTE_IS_DONE) 485253680Sdes break; 486253680Sdes 487253680Sdes val = parse_hr_min_sec (val, pz); 488253680Sdes 489253680Sdes if ((whatd_we_do == HOUR_IS_DONE) && (val >= SEC_PER_HR)) 490253680Sdes break; 491253680Sdes 492253680Sdes return scale_n_add (res, val, 1); 493253680Sdes } 494253680Sdes 495253680Sdes { 496253680Sdes unsigned int mult; 497253680Sdes 498253680Sdes /* Skip over white space following the number we just parsed. */ 499253680Sdes while (isspace ((unsigned char)*pz)) 500253680Sdes pz++; 501253680Sdes 502253680Sdes switch (*pz) 503253680Sdes { 504253680Sdes default: goto bad_time; 505253680Sdes case NUL: 506253680Sdes return scale_n_add (res, val, 1); 507253680Sdes 508253680Sdes case 'y': case 'Y': 509253680Sdes if (whatd_we_do >= YEAR_IS_DONE) 510253680Sdes goto bad_time; 511253680Sdes mult = SEC_PER_YEAR; 512253680Sdes whatd_we_do = YEAR_IS_DONE; 513253680Sdes break; 514253680Sdes 515253680Sdes case 'M': 516253680Sdes if (whatd_we_do >= MONTH_IS_DONE) 517253680Sdes goto bad_time; 518253680Sdes mult = SEC_PER_MONTH; 519253680Sdes whatd_we_do = MONTH_IS_DONE; 520253680Sdes break; 521253680Sdes 522253680Sdes case 'W': 523253680Sdes if (whatd_we_do >= WEEK_IS_DONE) 524253680Sdes goto bad_time; 525253680Sdes mult = SEC_PER_WEEK; 526253680Sdes whatd_we_do = WEEK_IS_DONE; 527253680Sdes break; 528253680Sdes 529253680Sdes case 'd': case 'D': 530253680Sdes if (whatd_we_do >= DAY_IS_DONE) 531253680Sdes goto bad_time; 532253680Sdes mult = SEC_PER_DAY; 533253680Sdes whatd_we_do = DAY_IS_DONE; 534253680Sdes break; 535253680Sdes 536253680Sdes case 'h': 537253680Sdes if (whatd_we_do >= HOUR_IS_DONE) 538253680Sdes goto bad_time; 539253680Sdes mult = SEC_PER_HR; 540253680Sdes whatd_we_do = HOUR_IS_DONE; 541253680Sdes break; 542253680Sdes 543253680Sdes case 'm': 544253680Sdes if (whatd_we_do >= MINUTE_IS_DONE) 545253680Sdes goto bad_time; 546253680Sdes mult = SEC_PER_MIN; 547253680Sdes whatd_we_do = MINUTE_IS_DONE; 548253680Sdes break; 549253680Sdes 550253680Sdes case 's': 551253680Sdes mult = 1; 552253680Sdes whatd_we_do = SECOND_IS_DONE; 553253680Sdes break; 554253680Sdes } 555253680Sdes 556253680Sdes res = scale_n_add (res, val, mult); 557253680Sdes 558253680Sdes pz++; 559253680Sdes while (isspace ((unsigned char)*pz)) 560253680Sdes pz++; 561253680Sdes if (*pz == NUL) 562253680Sdes return res; 563253680Sdes 564253680Sdes if (! isdigit ((unsigned char)*pz)) 565253680Sdes break; 566253680Sdes } 567253680Sdes 568253680Sdes } while (whatd_we_do < SECOND_IS_DONE); 569253680Sdes 570253680Sdes bad_time: 571253680Sdes errno = EINVAL; 572253680Sdes return BAD_TIME; 573253680Sdes} 574253680Sdes 575253680Sdestime_t 576253680Sdesparse_duration (char const * pz) 577253680Sdes{ 578253680Sdes while (isspace ((unsigned char)*pz)) 579253680Sdes pz++; 580253680Sdes 581253680Sdes switch (*pz) 582253680Sdes { 583253680Sdes case 'P': 584253680Sdes return parse_period (pz + 1); 585253680Sdes 586253680Sdes case 'T': 587294194Sdes return parse_time (pz + 1); 588294194Sdes 589253680Sdes default: 590253680Sdes if (isdigit ((unsigned char)*pz)) 591253680Sdes return parse_non_iso8601 (pz); 592253680Sdes 593253680Sdes errno = EINVAL; 594253680Sdes return BAD_TIME; 595253680Sdes } 596253680Sdes} 597253680Sdes 598253680Sdes/* 599253680Sdes * Local Variables: 600253680Sdes * mode: C 601253680Sdes * c-file-style: "gnu" 602253680Sdes * indent-tabs-mode: nil 603253680Sdes * End: 604253680Sdes * end of parse-duration.c */ 605253680Sdes