#include <config.h>
#include "parse-duration.h"
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "intprops.h"
#ifndef NUL
#define NUL '\0'
#endif
#define cch_t char const
typedef enum {
NOTHING_IS_DONE,
YEAR_IS_DONE,
MONTH_IS_DONE,
WEEK_IS_DONE,
DAY_IS_DONE,
HOUR_IS_DONE,
MINUTE_IS_DONE,
SECOND_IS_DONE
} whats_done_t;
#define SEC_PER_MIN 60
#define SEC_PER_HR (SEC_PER_MIN * 60)
#define SEC_PER_DAY (SEC_PER_HR * 24)
#define SEC_PER_WEEK (SEC_PER_DAY * 7)
#define SEC_PER_MONTH (SEC_PER_DAY * 30)
#define SEC_PER_YEAR (SEC_PER_DAY * 365)
#undef MAX_DURATION
#define MAX_DURATION TYPE_MAXIMUM(time_t)
static unsigned long
str_const_to_ul (cch_t * str, cch_t ** ppz, int base)
{
char * pz;
int rv = strtoul (str, &pz, base);
*ppz = pz;
return rv;
}
static long
str_const_to_l (cch_t * str, cch_t ** ppz, int base)
{
char * pz;
int rv = strtol (str, &pz, base);
*ppz = pz;
return rv;
}
static time_t
scale_n_add (time_t base, time_t val, int scale)
{
if (base == BAD_TIME)
{
if (errno == 0)
errno = EINVAL;
return BAD_TIME;
}
if (val > MAX_DURATION / scale)
{
errno = ERANGE;
return BAD_TIME;
}
val *= scale;
if (base > MAX_DURATION - val)
{
errno = ERANGE;
return BAD_TIME;
}
return base + val;
}
static time_t
parse_hr_min_sec (time_t start, cch_t * pz)
{
int lpct = 0;
errno = 0;
while ((*pz == ':') && (lpct++ <= 1))
{
unsigned long v = str_const_to_ul (pz+1, &pz, 10);
if (errno != 0)
return BAD_TIME;
start = scale_n_add (v, start, 60);
if (errno != 0)
return BAD_TIME;
}
while (isspace ((unsigned char)*pz))
pz++;
if (*pz != NUL)
{
errno = EINVAL;
return BAD_TIME;
}
return start;
}
static time_t
parse_scaled_value (time_t base, cch_t ** ppz, cch_t * endp, int scale)
{
cch_t * pz = *ppz;
time_t val;
if (base == BAD_TIME)
return base;
errno = 0;
val = str_const_to_ul (pz, &pz, 10);
if (errno != 0)
return BAD_TIME;
while (isspace ((unsigned char)*pz))
pz++;
if (pz != endp)
{
errno = EINVAL;
return BAD_TIME;
}
*ppz = pz;
return scale_n_add (base, val, scale);
}
static time_t
parse_year_month_day (cch_t * pz, cch_t * ps)
{
time_t res = 0;
res = parse_scaled_value (0, &pz, ps, SEC_PER_YEAR);
pz++;
ps = strchr (pz, '-');
if (ps == NULL)
{
errno = EINVAL;
return BAD_TIME;
}
res = parse_scaled_value (res, &pz, ps, SEC_PER_MONTH);
pz++;
ps = pz + strlen (pz);
return parse_scaled_value (res, &pz, ps, SEC_PER_DAY);
}
static time_t
parse_yearmonthday (cch_t * in_pz)
{
time_t res = 0;
char buf[8];
cch_t * pz;
if (strlen (in_pz) != 8)
{
errno = EINVAL;
return BAD_TIME;
}
memcpy (buf, in_pz, 4);
buf[4] = NUL;
pz = buf;
res = parse_scaled_value (0, &pz, buf + 4, SEC_PER_YEAR);
memcpy (buf, in_pz + 4, 2);
buf[2] = NUL;
pz = buf;
res = parse_scaled_value (res, &pz, buf + 2, SEC_PER_MONTH);
memcpy (buf, in_pz + 6, 2);
buf[2] = NUL;
pz = buf;
return parse_scaled_value (res, &pz, buf + 2, SEC_PER_DAY);
}
static time_t
parse_YMWD (cch_t * pz)
{
time_t res = 0;
cch_t * ps = strchr (pz, 'Y');
if (ps != NULL)
{
res = parse_scaled_value (0, &pz, ps, SEC_PER_YEAR);
pz++;
}
ps = strchr (pz, 'M');
if (ps != NULL)
{
res = parse_scaled_value (res, &pz, ps, SEC_PER_MONTH);
pz++;
}
ps = strchr (pz, 'W');
if (ps != NULL)
{
res = parse_scaled_value (res, &pz, ps, SEC_PER_WEEK);
pz++;
}
ps = strchr (pz, 'D');
if (ps != NULL)
{
res = parse_scaled_value (res, &pz, ps, SEC_PER_DAY);
pz++;
}
while (isspace ((unsigned char)*pz))
pz++;
if (*pz != NUL)
{
errno = EINVAL;
return BAD_TIME;
}
return res;
}
static time_t
parse_hour_minute_second (cch_t * pz, cch_t * ps)
{
time_t res = 0;
res = parse_scaled_value (0, &pz, ps, SEC_PER_HR);
pz++;
ps = strchr (pz, ':');
if (ps == NULL)
{
errno = EINVAL;
return BAD_TIME;
}
res = parse_scaled_value (res, &pz, ps, SEC_PER_MIN);
pz++;
ps = pz + strlen (pz);
return parse_scaled_value (res, &pz, ps, 1);
}
static time_t
parse_hourminutesecond (cch_t * in_pz)
{
time_t res = 0;
char buf[4];
cch_t * pz;
if (strlen (in_pz) != 6)
{
errno = EINVAL;
return BAD_TIME;
}
memcpy (buf, in_pz, 2);
buf[2] = NUL;
pz = buf;
res = parse_scaled_value (0, &pz, buf + 2, SEC_PER_HR);
memcpy (buf, in_pz + 2, 2);
buf[2] = NUL;
pz = buf;
res = parse_scaled_value (res, &pz, buf + 2, SEC_PER_MIN);
memcpy (buf, in_pz + 4, 2);
buf[2] = NUL;
pz = buf;
return parse_scaled_value (res, &pz, buf + 2, 1);
}
static time_t
parse_HMS (cch_t * pz)
{
time_t res = 0;
cch_t * ps = strchr (pz, 'H');
if (ps != NULL)
{
res = parse_scaled_value (0, &pz, ps, SEC_PER_HR);
pz++;
}
ps = strchr (pz, 'M');
if (ps != NULL)
{
res = parse_scaled_value (res, &pz, ps, SEC_PER_MIN);
pz++;
}
ps = strchr (pz, 'S');
if (ps != NULL)
{
res = parse_scaled_value (res, &pz, ps, 1);
pz++;
}
while (isspace ((unsigned char)*pz))
pz++;
if (*pz != NUL)
{
errno = EINVAL;
return BAD_TIME;
}
return res;
}
static time_t
parse_time (cch_t * pz)
{
cch_t * ps;
time_t res = 0;
ps = strchr (pz, ':');
if (ps != NULL)
{
res = parse_hour_minute_second (pz, ps);
}
else if (ps = strpbrk (pz, "HMS"),
ps == NULL)
{
res = parse_hourminutesecond (pz);
}
else
res = parse_HMS (pz);
return res;
}
static char *
trim (char * pz)
{
while (isspace ((unsigned char)*pz))
pz++;
{
char * pe = pz + strlen (pz);
while ((pe > pz) && isspace ((unsigned char)pe[-1]))
pe--;
*pe = NUL;
}
return pz;
}
static time_t
parse_period (cch_t * in_pz)
{
char * pT;
char * ps;
char * pz = strdup (in_pz);
void * fptr = pz;
time_t res = 0;
if (pz == NULL)
{
errno = ENOMEM;
return BAD_TIME;
}
pT = strchr (pz, 'T');
if (pT != NULL)
{
*(pT++) = NUL;
pz = trim (pz);
pT = trim (pT);
}
ps = strchr (pz, '-');
if (ps != NULL)
{
res = parse_year_month_day (pz, ps);
}
else if (ps = strpbrk (pz, "YMWD"),
ps == NULL)
{
res = parse_yearmonthday (pz);
}
else
res = parse_YMWD (pz);
if ((errno == 0) && (pT != NULL))
{
time_t val = parse_time (pT);
res = scale_n_add (res, val, 1);
}
free (fptr);
return res;
}
static time_t
parse_non_iso8601 (cch_t * pz)
{
whats_done_t whatd_we_do = NOTHING_IS_DONE;
time_t res = 0;
do {
time_t val;
errno = 0;
val = str_const_to_l (pz, &pz, 10);
if (errno != 0)
goto bad_time;
if (*pz == ':')
{
if (whatd_we_do >= MINUTE_IS_DONE)
break;
val = parse_hr_min_sec (val, pz);
if ((whatd_we_do == HOUR_IS_DONE) && (val >= SEC_PER_HR))
break;
return scale_n_add (res, val, 1);
}
{
unsigned int mult;
while (isspace ((unsigned char)*pz))
pz++;
switch (*pz)
{
default: goto bad_time;
case NUL:
return scale_n_add (res, val, 1);
case 'y': case 'Y':
if (whatd_we_do >= YEAR_IS_DONE)
goto bad_time;
mult = SEC_PER_YEAR;
whatd_we_do = YEAR_IS_DONE;
break;
case 'M':
if (whatd_we_do >= MONTH_IS_DONE)
goto bad_time;
mult = SEC_PER_MONTH;
whatd_we_do = MONTH_IS_DONE;
break;
case 'W':
if (whatd_we_do >= WEEK_IS_DONE)
goto bad_time;
mult = SEC_PER_WEEK;
whatd_we_do = WEEK_IS_DONE;
break;
case 'd': case 'D':
if (whatd_we_do >= DAY_IS_DONE)
goto bad_time;
mult = SEC_PER_DAY;
whatd_we_do = DAY_IS_DONE;
break;
case 'h':
if (whatd_we_do >= HOUR_IS_DONE)
goto bad_time;
mult = SEC_PER_HR;
whatd_we_do = HOUR_IS_DONE;
break;
case 'm':
if (whatd_we_do >= MINUTE_IS_DONE)
goto bad_time;
mult = SEC_PER_MIN;
whatd_we_do = MINUTE_IS_DONE;
break;
case 's':
mult = 1;
whatd_we_do = SECOND_IS_DONE;
break;
}
res = scale_n_add (res, val, mult);
pz++;
while (isspace ((unsigned char)*pz))
pz++;
if (*pz == NUL)
return res;
if (! isdigit ((unsigned char)*pz))
break;
}
} while (whatd_we_do < SECOND_IS_DONE);
bad_time:
errno = EINVAL;
return BAD_TIME;
}
time_t
parse_duration (char const * pz)
{
while (isspace ((unsigned char)*pz))
pz++;
switch (*pz)
{
case 'P':
return parse_period (pz + 1);
case 'T':
return parse_time (pz + 1);
default:
if (isdigit ((unsigned char)*pz))
return parse_non_iso8601 (pz);
errno = EINVAL;
return BAD_TIME;
}
}