From: Alan T. DeKok Date: Wed, 21 Aug 2019 12:54:51 +0000 (-0400) Subject: add parser for ISO8601 date formats X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9fd673d138d43d294ead2896cb13a35afe779cab;p=thirdparty%2Ffreeradius-server.git add parser for ISO8601 date formats Which incidentally also gets the time zone conversions correct. The old parser just ignored the time zone. --- diff --git a/src/lib/util/misc.c b/src/lib/util/misc.c index e0c002046c4..870d55525ba 100644 --- a/src/lib/util/misc.c +++ b/src/lib/util/misc.c @@ -708,6 +708,31 @@ static char *mystrtok(char **ptr, char const *sep) return res; } +/* + * Helper function to get a 2-digit date. With a maximum value, + * and a terminating character. + */ +static int get_part(char **str, int *date, int max, char term) +{ + char *p = *str; + + if (!isdigit((int) *p) || !isdigit((int) p[1])) return -1; + *date = (p[0] - '0') * 10 + (p[1] - '0'); + if (*date > max) return -1; + + p += 2; + if (!term) { + *str = p; + return 0; + } + + if (*p != term) return -1; + p++; + + *str = p; + return 0; +} + /** Convert string in various formats to a time_t * * @param date_str input date string. @@ -740,6 +765,86 @@ int fr_time_from_str(fr_time_t *date, char const *date_str) memset(tm, 0, sizeof(*tm)); tm->tm_isdst = -1; /* don't know, and don't care about DST */ + /* + * Check for RFC 3339 dates. Note that we only support + * dates in a ~1000 year period. If the server is being + * used after 3000AD, someone can patch it then. + * + * %Y-%m-%dT%H:%M:%S + * [.%d] sub-seconds + * Z | (+/-)%H:%M time zone offset + * + */ + if ((t > 1900) && (t < 3000) && *tail == '-') { + unsigned long subseconds; + int tz, tz_hour, tz_min; + + p = tail + 1; + s_tm.tm_year = t - 1900; /* 'struct tm' starts years in 1900 */ + + if (get_part(&p, &s_tm.tm_mon, 13, '-') < 0) return -1; + if (s_tm.tm_mon == 0) return -1; + s_tm.tm_mon--; /* ISO is 1..12, where 'struct tm' is 0..11 */ + + if (get_part(&p, &s_tm.tm_mday, 31, 'T') < 0) return -1; + if (get_part(&p, &s_tm.tm_hour, 23, ':') < 0) return -1; + if (get_part(&p, &s_tm.tm_min, 59, ':') < 0) return -1; + if (get_part(&p, &s_tm.tm_sec, 60, '\0') < 0) return -1; /* leap seconds */ + + if (*p == '.') { + subseconds = strtoul(p, &tail, 10); + if (subseconds > NSEC) return -1; + p = tail; + } else { + subseconds = 0; + } + + /* + * Time zone is GMT. Leave well enough + * alone. + */ + if (*p == 'Z') { + if (p[1] != '\0') return -1; + tz = 0; + goto done; + } + + if ((*p != '+') && (*p != '-')) return -1; + tail = p; /* remember sign for later */ + p++; + + if (get_part(&p, &tz_hour, 23, ':') < 0) return -1; + if (get_part(&p, &tz_min, 59, '\0') < 0) return -1; + + if (*p != '\0') return -1; + + /* + * We set this, but the timegm() function ignores + * it. Note also that mktime() ignores it too, + * and treats the time zone as local. + * + * We can't store this value in s_tm.gtmoff, + * because the timegm() function helpfully zeros + * it out. + */ + tz = tz_hour * 3600 + tz_min; + if (*tail == '-') tz *= -1; + + done: + t = timegm(tm); + if (t == (time_t) -1) return -1; + + /* + * Add in the time zone offset, which the posix + * functions are too stupid to do. + */ + t += tz; + + *date = fr_time_from_timeval(&(struct timeval) { .tv_sec = t }); + *date += subseconds; + return 0; + } + strlcpy(buf, date_str, sizeof(buf)); p = buf; diff --git a/src/tests/unit/data_types.txt b/src/tests/unit/data_types.txt index 6b5ad108192..b113f1bafe8 100644 --- a/src/tests/unit/data_types.txt +++ b/src/tests/unit/data_types.txt @@ -73,3 +73,21 @@ data Sep 27 1972 00:00:00 GMT # 10,000 days date 864000000 data May 19 1997 00:00:00 GMT + +# +# Input can be whatever. But for the tests, output is GMT. +# +# Note that this test is wrong, because the HH:MM are the +# same, even though they are in different time zones. +# Our parse function ignores time zones, which is bad. +# +date Aug 21 2019 07:43:03 EDT +data Aug 21 2019 07:43:03 GMT + +# +# Can't yet test this as the parse / print / parse / cmp +# functions don't yet allow for choosing ISO8601 vs the +# traditional FreeRADIUS time. +# +#date 2019-08-21T07:40:31-04:00 +#data foo