diff options
Diffstat (limited to 'src/lib/iso8601-date.c')
-rw-r--r-- | src/lib/iso8601-date.c | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/src/lib/iso8601-date.c b/src/lib/iso8601-date.c new file mode 100644 index 0000000..a0c84ac --- /dev/null +++ b/src/lib/iso8601-date.c @@ -0,0 +1,309 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "utc-offset.h" +#include "utc-mktime.h" +#include "iso8601-date.h" + +#include <ctype.h> + +/* RFC3339/ISO8601 date-time syntax + + date-fullyear = 4DIGIT + date-month = 2DIGIT ; 01-12 + date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on + ; month/year + time-hour = 2DIGIT ; 00-23 + time-minute = 2DIGIT ; 00-59 + time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second + ; rules + time-secfrac = "." 1*DIGIT + time-numoffset = ("+" / "-") time-hour ":" time-minute + time-offset = "Z" / time-numoffset + + partial-time = time-hour ":" time-minute ":" time-second [time-secfrac] + full-date = date-fullyear "-" date-month "-" date-mday + full-time = partial-time time-offset + + date-time = full-date "T" full-time + */ + +struct iso8601_date_parser { + const unsigned char *cur, *end; + + struct tm tm; + int timezone_offset; +}; + +static inline int +iso8601_date_parse_number(struct iso8601_date_parser *parser, + int digits, int *number_r) +{ + int i; + + if (parser->cur >= parser->end || !i_isdigit(parser->cur[0])) + return 0; + + *number_r = parser->cur[0] - '0'; + parser->cur++; + + for (i=0; i < digits-1; i++) { + if (parser->cur >= parser->end || !i_isdigit(parser->cur[0])) + return -1; + *number_r = ((*number_r) * 10) + parser->cur[0] - '0'; + parser->cur++; + } + return 1; +} + +static int +iso8601_date_parse_secfrac(struct iso8601_date_parser *parser) +{ + /* time-secfrac = "." 1*DIGIT + + NOTE: Currently not applied anywhere, so fraction is just skipped. + */ + + /* "." */ + if (parser->cur >= parser->end || parser->cur[0] != '.') + return 0; + parser->cur++; + + /* 1DIGIT */ + if (parser->cur >= parser->end || !i_isdigit(parser->cur[0])) + return -1; + parser->cur++; + + /* *DIGIT */ + while (parser->cur < parser->end && i_isdigit(parser->cur[0])) + parser->cur++; + return 1; +} + +static int is08601_date_parse_time_offset(struct iso8601_date_parser *parser) +{ + int tz_sign = 1, tz_hour = 0, tz_min = 0; + + /* time-offset = "Z" / time-numoffset + time-numoffset = ("+" / "-") time-hour ":" time-minute + time-hour = 2DIGIT ; 00-23 + time-minute = 2DIGIT ; 00-59 + */ + + if (parser->cur >= parser->end) + return 0; + + /* time-offset = "Z" / time-numoffset */ + switch (parser->cur[0]) { + case '-': + tz_sign = -1; + /* fall through */ + case '+': + parser->cur++; + + /* time-hour = 2DIGIT */ + if (iso8601_date_parse_number(parser, 2, &tz_hour) <= 0) + return -1; + if (tz_hour > 23) + return -1; + + /* ":" */ + if (parser->cur >= parser->end || parser->cur[0] != ':') + return -1; + parser->cur++; + + /* time-minute = 2DIGIT */ + if (iso8601_date_parse_number(parser, 2, &tz_min) <= 0) + return -1; + if (tz_min > 59) + return -1; + break; + case 'Z': + case 'z': + parser->cur++; + break; + default: + return -1; + } + + parser->timezone_offset = tz_sign*(tz_hour*60 + tz_min); + return 1; +} + +static int is08601_date_parse_full_time(struct iso8601_date_parser *parser) +{ + /* full-time = partial-time time-offset + partial-time = time-hour ":" time-minute ":" time-second [time-secfrac] + time-hour = 2DIGIT ; 00-23 + time-minute = 2DIGIT ; 00-59 + time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second + ; rules + */ + + /* time-hour = 2DIGIT */ + if (iso8601_date_parse_number(parser, 2, &parser->tm.tm_hour) <= 0) + return -1; + + /* ":" */ + if (parser->cur >= parser->end || parser->cur[0] != ':') + return -1; + parser->cur++; + + /* time-minute = 2DIGIT */ + if (iso8601_date_parse_number(parser, 2, &parser->tm.tm_min) <= 0) + return -1; + + /* ":" */ + if (parser->cur >= parser->end || parser->cur[0] != ':') + return -1; + parser->cur++; + + /* time-second = 2DIGIT */ + if (iso8601_date_parse_number(parser, 2, &parser->tm.tm_sec) <= 0) + return -1; + + /* [time-secfrac] */ + if (iso8601_date_parse_secfrac(parser) < 0) + return -1; + + /* time-offset */ + if (is08601_date_parse_time_offset(parser) <= 0) + return -1; + return 1; +} + +static int is08601_date_parse_full_date(struct iso8601_date_parser *parser) +{ + /* full-date = date-fullyear "-" date-month "-" date-mday + date-fullyear = 4DIGIT + date-month = 2DIGIT ; 01-12 + date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on + ; month/year + */ + + /* date-fullyear = 4DIGIT */ + if (iso8601_date_parse_number(parser, 4, &parser->tm.tm_year) <= 0) + return -1; + if (parser->tm.tm_year < 1900) + return -1; + parser->tm.tm_year -= 1900; + + /* "-" */ + if (parser->cur >= parser->end || parser->cur[0] != '-') + return -1; + parser->cur++; + + /* date-month = 2DIGIT */ + if (iso8601_date_parse_number(parser, 2, &parser->tm.tm_mon) <= 0) + return -1; + parser->tm.tm_mon -= 1; + + /* "-" */ + if (parser->cur >= parser->end || parser->cur[0] != '-') + return -1; + parser->cur++; + + /* time-second = 2DIGIT */ + if (iso8601_date_parse_number(parser, 2, &parser->tm.tm_mday) <= 0) + return -1; + return 1; +} + +static int iso8601_date_parse_date_time(struct iso8601_date_parser *parser) +{ + /* date-time = full-date "T" full-time */ + + /* full-date */ + if (is08601_date_parse_full_date(parser) <= 0) + return -1; + + /* "T" */ + if (parser->cur >= parser->end || + (parser->cur[0] != 'T' && parser->cur[0] != 't')) + return -1; + parser->cur++; + + /* full-time */ + if (is08601_date_parse_full_time(parser) <= 0) + return -1; + + if (parser->cur != parser->end) + return -1; + return 1; +} + +static bool +iso8601_date_do_parse(const unsigned char *data, size_t size, struct tm *tm_r, + time_t *timestamp_r, int *timezone_offset_r) +{ + struct iso8601_date_parser parser; + time_t timestamp; + + i_zero(&parser); + parser.cur = data; + parser.end = data + size; + + if (iso8601_date_parse_date_time(&parser) <= 0) + return FALSE; + + parser.tm.tm_isdst = -1; + timestamp = utc_mktime(&parser.tm); + if (timestamp == (time_t)-1) + return FALSE; + + *timezone_offset_r = parser.timezone_offset; + *tm_r = parser.tm; + *timestamp_r = timestamp - parser.timezone_offset * 60; + return TRUE; +} + +bool iso8601_date_parse(const unsigned char *data, size_t size, + time_t *timestamp_r, int *timezone_offset_r) +{ + struct tm tm; + + return iso8601_date_do_parse(data, size, &tm, + timestamp_r, timezone_offset_r); +} + +bool iso8601_date_parse_tm(const unsigned char *data, size_t size, + struct tm *tm_r, int *timezone_offset_r) +{ + time_t timestamp; + + return iso8601_date_do_parse(data, size, tm_r, + ×tamp, timezone_offset_r); +} + +const char *iso8601_date_create_tm(struct tm *tm, int timezone_offset) +{ + const char *time_offset; + + if (timezone_offset == INT_MAX) + time_offset = "Z"; + else { + char sign = '+'; + if (timezone_offset < 0) { + timezone_offset = -timezone_offset; + sign = '-'; + } + time_offset = t_strdup_printf("%c%02d:%02d", sign, + timezone_offset / 60, + timezone_offset % 60); + } + + return t_strdup_printf("%04d-%02d-%02dT%02d:%02d:%02d%s", + tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec, time_offset); +} + +const char *iso8601_date_create(time_t timestamp) +{ + struct tm *tm; + int timezone_offset; + + tm = localtime(×tamp); + timezone_offset = utc_offset(tm, timestamp); + + return iso8601_date_create_tm(tm, timezone_offset); +} |