diff --git a/Foundation/src/DateTimeFormat.cpp b/Foundation/src/DateTimeFormat.cpp index b026f3352f..da25d6e8c8 100644 --- a/Foundation/src/DateTimeFormat.cpp +++ b/Foundation/src/DateTimeFormat.cpp @@ -38,7 +38,6 @@ const std::string DateTimeFormat::ISO8601_REGEX("([\\+-]?\\d{4}(?!\\d{2}\\b))" const std::string DateTimeFormat::RFC822_FORMAT("%w, %e %b %y %H:%M:%S %Z"); -// TODO: Is this regex correct? RFC 822 spec does not mention timezone codes, just GMT. const std::string DateTimeFormat::RFC822_REGEX("(((Mon)|(Tue)|(Wed)|(Thu)|(Fri)|(Sat)|(Sun)), *)?" "\\d\\d? +" "((Jan)|(Feb)|(Mar)|(Apr)|(May)|(Jun)|(Jul)|(Aug)|(Sep)|(Oct)|(Nov)|(Dec)) +" @@ -75,6 +74,9 @@ const std::string DateTimeFormat::RFC1036_REGEX( "(" TIMEZONES_REGEX_PART "|)?+" "(([+\\-]?\\d\\d\\d\\d)?|" TIMEZONES_REGEX_PART "|\\w)"); +// It would perhaps be useful to add RFC 2822 (successor of 822) +// https://www.rfc-editor.org/rfc/rfc2822#section-3.3 + const std::string DateTimeFormat::ASCTIME_FORMAT("%w %b %f %H:%M:%S %Y"); const std::string DateTimeFormat::ASCTIME_REGEX("((Mon)|(Tue)|(Wed)|(Thu)|(Fri)|(Sat)|(Sun)) +" "((Jan)|(Feb)|(Mar)|(Apr)|(May)|(Jun)|(Jul)|(Aug)|(Sep)|(Oct)|(Nov)|(Dec)) +" diff --git a/Foundation/src/DateTimeParser.cpp b/Foundation/src/DateTimeParser.cpp index ba09e8ed0d..383f85c8d5 100644 --- a/Foundation/src/DateTimeParser.cpp +++ b/Foundation/src/DateTimeParser.cpp @@ -57,6 +57,9 @@ void DateTimeParser::parse(const std::string& fmt, const std::string& str, DateT int micros = 0; int tzd = 0; + bool dayParsed = false; + bool monthParsed = false; + std::string::const_iterator it = str.begin(); std::string::const_iterator end = str.end(); std::string::const_iterator itf = fmt.begin(); @@ -78,18 +81,21 @@ void DateTimeParser::parse(const std::string& fmt, const std::string& str, DateT case 'b': case 'B': month = parseMonth(it, end); + monthParsed = true; break; case 'd': case 'e': case 'f': SKIP_JUNK(); PARSE_NUMBER_N(day, 2); + dayParsed = true; break; case 'm': case 'n': case 'o': SKIP_JUNK(); PARSE_NUMBER_N(month, 2); + monthParsed = true; break; case 'y': SKIP_JUNK(); @@ -167,12 +173,13 @@ void DateTimeParser::parse(const std::string& fmt, const std::string& str, DateT } else ++itf; } - if (month == 0) month = 1; - if (day == 0) day = 1; + if (!monthParsed) month = 1; + if (!dayParsed) day = 1; if (DateTime::isValid(year, month, day, hour, minute, second, millis, micros)) dateTime.assign(year, month, day, hour, minute, second, millis, micros); else throw SyntaxException("date/time component out of range"); + timeZoneDifferential = tzd; } @@ -191,7 +198,7 @@ bool DateTimeParser::tryParse(const std::string& fmt, const std::string& str, Da { parse(fmt, str, dateTime, timeZoneDifferential); } - catch (Exception&) + catch (const Exception&) { return false; } @@ -245,53 +252,55 @@ int DateTimeParser::parseTZD(std::string::const_iterator& it, const std::string: { const char* designator; int timeZoneDifferential; + bool allowsDifference; }; - static Zone zones[] = + static const Zone zones[] = { - {"Z", 0}, - {"UT", 0}, - {"GMT", 0}, - {"BST", 1*3600}, - {"IST", 1*3600}, - {"WET", 0}, - {"WEST", 1*3600}, - {"CET", 1*3600}, - {"CEST", 2*3600}, - {"EET", 2*3600}, - {"EEST", 3*3600}, - {"MSK", 3*3600}, - {"MSD", 4*3600}, - {"NST", -3*3600-1800}, - {"NDT", -2*3600-1800}, - {"AST", -4*3600}, - {"ADT", -3*3600}, - {"EST", -5*3600}, - {"EDT", -4*3600}, - {"CST", -6*3600}, - {"CDT", -5*3600}, - {"MST", -7*3600}, - {"MDT", -6*3600}, - {"PST", -8*3600}, - {"PDT", -7*3600}, - {"AKST", -9*3600}, - {"AKDT", -8*3600}, - {"HST", -10*3600}, - {"AEST", 10*3600}, - {"AEDT", 11*3600}, - {"ACST", 9*3600+1800}, - {"ACDT", 10*3600+1800}, - {"AWST", 8*3600}, - {"AWDT", 9*3600} + {"Z", 0, true}, + {"UT", 0, true}, + {"GMT", 0, true}, + {"BST", 1*3600, false}, + {"IST", 1*3600, false}, + {"WET", 0, false}, + {"WEST", 1*3600, false}, + {"CET", 1*3600, false}, + {"CEST", 2*3600, false}, + {"EET", 2*3600, false}, + {"EEST", 3*3600, false}, + {"MSK", 3*3600, false}, + {"MSD", 4*3600, false}, + {"NST", -3*3600-1800, false}, + {"NDT", -2*3600-1800, false}, + {"AST", -4*3600, false}, + {"ADT", -3*3600, false}, + {"EST", -5*3600, false}, + {"EDT", -4*3600, false}, + {"CST", -6*3600, false}, + {"CDT", -5*3600, false}, + {"MST", -7*3600, false}, + {"MDT", -6*3600, false}, + {"PST", -8*3600, false}, + {"PDT", -7*3600, false}, + {"AKST", -9*3600, false}, + {"AKDT", -8*3600, false}, + {"HST", -10*3600, false}, + {"AEST", 10*3600, false}, + {"AEDT", 11*3600, false}, + {"ACST", 9*3600+1800, false}, + {"ACDT", 10*3600+1800, false}, + {"AWST", 8*3600, false}, + {"AWDT", 9*3600, false} }; int tzd = 0; while (it != end && Ascii::isSpace(*it)) ++it; + const Zone* zone = nullptr; + std::string designator; if (it != end) { if (Ascii::isAlpha(*it)) { - std::string designator; designator += *it++; if (it != end && Ascii::isAlpha(*it)) designator += *it++; if (it != end && Ascii::isAlpha(*it)) designator += *it++; @@ -300,20 +309,33 @@ int DateTimeParser::parseTZD(std::string::const_iterator& it, const std::string: { if (designator == zones[i].designator) { - tzd = zones[i].timeZoneDifferential; + zone = &(zones[i]); + tzd = zone->timeZoneDifferential; break; } } } + if (!designator.empty() && !zone) + throw SyntaxException("Unknown timezone designator "s + designator); + if (it != end && (*it == '+' || *it == '-')) { + // Time difference is allowed only for some timezone designators in general + // Some formats prevent even that with regular expression + if (zone && !zone->allowsDifference) + throw SyntaxException("Timezone does not allow difference "s + zone->designator); + int sign = *it == '+' ? 1 : -1; ++it; int hours = 0; PARSE_NUMBER_N(hours, 2); + if (hours < 0 || hours > 23) + throw SyntaxException("Timezone difference hours out of range"); if (it != end && *it == ':') ++it; int minutes = 0; PARSE_NUMBER_N(minutes, 2); + if (minutes < 0 || minutes > 59) + throw SyntaxException("Timezone difference minutes out of range"); tzd += sign*(hours*3600 + minutes*60); } } diff --git a/Foundation/testsuite/src/DateTimeParserTest.cpp b/Foundation/testsuite/src/DateTimeParserTest.cpp index 5da558c22c..e78b6bb65f 100644 --- a/Foundation/testsuite/src/DateTimeParserTest.cpp +++ b/Foundation/testsuite/src/DateTimeParserTest.cpp @@ -24,6 +24,7 @@ using Poco::DateTimeParser; using Poco::Timestamp; using Poco::SyntaxException; +using namespace std::string_literals; DateTimeParserTest::DateTimeParserTest(const std::string& name): CppUnit::TestCase(name) { @@ -47,6 +48,16 @@ void DateTimeParserTest::testISO8601() assertTrue (dt.second() == 0); assertTrue (tzd == 0); testBad(DateTimeFormat::ISO8601_FRAC_FORMAT, "2005-01-08T12.30:00Z", tzd); + testBad(DateTimeFormat::ISO8601_FRAC_FORMAT, "2005-00-08T12:30:00Z", tzd); + testBad(DateTimeFormat::ISO8601_FRAC_FORMAT, "2005-01-00T12:30:00Z", tzd); + testBad(DateTimeFormat::ISO8601_FRAC_FORMAT, "2005-01-00T33:30:00Z", tzd); + testBad(DateTimeFormat::ISO8601_FRAC_FORMAT, "2005-01-00T12:80:00Z", tzd); + testBad(DateTimeFormat::ISO8601_FRAC_FORMAT, "2005-01-00T12:30:90Z", tzd); + testBad(DateTimeFormat::ISO8601_FRAC_FORMAT, "2005-01-0012:30:90Z", tzd); + testBad(DateTimeFormat::ISO8601_FRAC_FORMAT, "2005-01-00X12:30:90Z", tzd); + testBad(DateTimeFormat::ISO8601_FRAC_FORMAT, "200501-00T12:30:90Z", tzd); + testBad(DateTimeFormat::ISO8601_FRAC_FORMAT, "2005-0100T12:30:90Z", tzd); + testBad(DateTimeFormat::ISO8601_FRAC_FORMAT, "2005_01+00T12:30:90Z", tzd); dt = DateTimeParser::parse(DateTimeFormat::ISO8601_FORMAT, "2005-01-08T12:30:00+01:00", tzd); assertTrue (dt.year() == 2005); @@ -57,6 +68,8 @@ void DateTimeParserTest::testISO8601() assertTrue (dt.second() == 0); assertTrue (tzd == 3600); testBad(DateTimeFormat::ISO8601_FRAC_FORMAT, "2005-01-8T12:30:00+01:00", tzd); + testBad(DateTimeFormat::ISO8601_FRAC_FORMAT, "2005-01-08T12:30:00+41:00", tzd); + testBad(DateTimeFormat::ISO8601_FRAC_FORMAT, "2005-01-08T12:30:00+01:70", tzd); dt = DateTimeParser::parse(DateTimeFormat::ISO8601_FORMAT, "2005-01-08T12:30:00-01:00", tzd); assertTrue (dt.year() == 2005); @@ -351,15 +364,7 @@ void DateTimeParserTest::testRFC1123() assertTrue (tzd == -14400); testBad(DateTimeFormat::RFC1123_FORMAT, "Hue, 18 Jan 2005 12:30:00 EDT", tzd); - // Parsing fails? If this format supported at all by the spec? - dt = DateTimeParser::parse(DateTimeFormat::RFC1123_FORMAT, "Sun, 20 Jul 1969 16:17:30 GMT+01:00", tzd); - assertTrue (dt.year() == 1969); - assertTrue (dt.month() == 7); - assertTrue (dt.day() == 20); - assertTrue (dt.hour() == 16); - assertTrue (dt.minute() == 17); - assertTrue (dt.second() == 30); - assertTrue (tzd == 3600); + testBad(DateTimeFormat::RFC1123_FORMAT, "Sun, 20 Jul 1969 16:17:30 GMT+01:00", tzd); testBad(DateTimeFormat::RFC1123_FORMAT, "Sun, 20 Jul 1969 16:17:30 GMT+01?00", tzd); dt = DateTimeParser::parse(DateTimeFormat::RFC1123_FORMAT, "Tue, 18 Jan 2005 12:30:00 CDT", tzd); @@ -391,6 +396,7 @@ void DateTimeParserTest::testRFC1123() assertTrue (dt.second() == 12); assertTrue (tzd == -18000); testBad(DateTimeFormat::RFC1123_FORMAT, "12 Sep 193 02:01:12 EST", tzd); + testBad(DateTimeFormat::RFC1123_FORMAT, "12 Sep 1973 02:01:12 ABC", tzd); } @@ -840,7 +846,7 @@ void DateTimeParserTest::testBad(const std::string& fmt, const std::string& date { DateTime dt; DateTimeParser::parse(fmt, dateStr, dt, tzd); - fail ("must fail"); + fail ("must fail: "s + fmt + ", " + dateStr); } catch(const Poco::Exception) { } }