parse-duration.c 11 KB

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