summaryrefslogtreecommitdiffstats
path: root/src/lib/iso8601-date.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/iso8601-date.c')
-rw-r--r--src/lib/iso8601-date.c309
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,
+ &timestamp, 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(&timestamp);
+ timezone_offset = utc_offset(tm, timestamp);
+
+ return iso8601_date_create_tm(tm, timezone_offset);
+}