diff --git a/src/time/ISO8601.cxx b/src/time/ISO8601.cxx index f08938e4e..6671a8ca4 100644 --- a/src/time/ISO8601.cxx +++ b/src/time/ISO8601.cxx @@ -58,6 +58,56 @@ FormatISO8601(std::chrono::system_clock::time_point tp) return FormatISO8601(GmTime(tp)); } +static std::pair +ParseTimeZoneOffsetRaw(const char *&s) +{ + char *endptr; + unsigned long value = strtoul(s, &endptr, 10); + if (endptr == s + 4) { + s = endptr; + return std::make_pair(value / 100, value % 100); + } else if (endptr == s + 2) { + s = endptr; + + unsigned hours = value, minutes = 0; + if (*s == ':') { + ++s; + minutes = strtoul(s, &endptr, 10); + if (endptr != s + 2) + throw std::runtime_error("Failed to parse time zone offset"); + + s = endptr; + } + + return std::make_pair(hours, minutes); + } else + throw std::runtime_error("Failed to parse time zone offset"); +} + +static std::chrono::system_clock::duration +ParseTimeZoneOffset(const char *&s) +{ + assert(*s == '+' || *s == '-'); + + bool negative = *s == '-'; + ++s; + + auto raw = ParseTimeZoneOffsetRaw(s); + if (raw.first > 13) + throw std::runtime_error("Time offset hours out of range"); + + if (raw.second >= 60) + throw std::runtime_error("Time offset minutes out of range"); + + std::chrono::system_clock::duration d = std::chrono::hours(raw.first); + d += std::chrono::minutes(raw.second); + + if (negative) + d = -d; + + return d; +} + std::pair ParseISO8601(const char *s) @@ -93,8 +143,11 @@ ParseISO8601(const char *s) auto tp = TimeGm(tm); + /* time zone */ if (*s == 'Z') ++s; + else if (*s == '+' || *s == '-') + tp -= ParseTimeZoneOffset(s); if (*s != 0) throw std::runtime_error("Garbage at end of time stamp"); diff --git a/test/TestISO8601.cxx b/test/TestISO8601.cxx index 6fdc11dc8..e97798831 100644 --- a/test/TestISO8601.cxx +++ b/test/TestISO8601.cxx @@ -57,6 +57,12 @@ static constexpr struct { /* without time zone */ { "2019-02-04T16:46:41", 1549298801, std::chrono::seconds(1) }, + + /* with time zone */ + { "2019-02-04T16:46:41+02", 1549291601, std::chrono::seconds(1) }, + { "2019-02-04T16:46:41+0200", 1549291601, std::chrono::seconds(1) }, + { "2019-02-04T16:46:41+02:00", 1549291601, std::chrono::seconds(1) }, + { "2019-02-04T16:46:41-0200", 1549306001, std::chrono::seconds(1) }, }; TEST(ISO8601, Parse)