parse-duration.c 12 KB

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