From 545e3f54b104364b316318876a3d4e515de758b1 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Tue, 15 Aug 2023 12:28:21 +0200 Subject: [PATCH] Fix dcp::LocalTime constructor to cope with longer fractional second parts (DoM #2597). --- src/local_time.cc | 78 ++++++++++++++++++++++------------------- test/local_time_test.cc | 26 ++++++++++++++ 2 files changed, 68 insertions(+), 36 deletions(-) diff --git a/src/local_time.cc b/src/local_time.cc index cadb7137..46718a75 100644 --- a/src/local_time.cc +++ b/src/local_time.cc @@ -124,43 +124,19 @@ LocalTime::set_local_time_zone () LocalTime::LocalTime (string s) { - /* 2013-01-05T18:06:59 or 2013-01-05T18:06:59.123 or 2013-01-05T18:06:59+04:00 or 2013-01-05T18:06:59.123+04:00 */ - /* 0123456789012345678 or 01234567890123456789012 or 0123456789012345678901234 or 01234567890123456789012345678 */ + /* 2013-01-05T18:06:59[.frac][TZ] + * Where .frac is fractional seconds + * TZ is something like +04:00 + */ if (s.length() < 19) { throw TimeFormatError (s); } - bool with_millisecond = false; - bool with_tz = false; - - switch (s.length ()) { - case 19: - break; - case 23: - with_millisecond = true; - break; - case 25: - with_tz = true; - break; - case 29: - with_millisecond = with_tz = true; - break; - default: - throw TimeFormatError (s); - } - - int const tz_pos = with_millisecond ? 23 : 19; + /* Date and time with whole seconds */ - /* Check incidental characters */ if (s[4] != '-' || s[7] != '-' || s[10] != 'T' || s[13] != ':' || s[16] != ':') { - throw TimeFormatError (s); - } - if (with_millisecond && s[19] != '.') { - throw TimeFormatError (s); - } - if (with_tz && s[tz_pos] != '+' && s[tz_pos] != '-') { - throw TimeFormatError (s); + throw TimeFormatError(s); } _year = lexical_cast(s.substr(0, 4)); @@ -169,14 +145,44 @@ LocalTime::LocalTime (string s) _hour = lexical_cast(s.substr(11, 2)); _minute = lexical_cast(s.substr(14, 2)); _second = lexical_cast(s.substr(17, 2)); - _millisecond = with_millisecond ? lexical_cast(s.substr(20, 3)) : 0; - _offset.set_hour(with_tz ? lexical_cast(s.substr(tz_pos + 1, 2)) : 0); - _offset.set_minute(with_tz ? lexical_cast(s.substr(tz_pos + 4, 2)) : 0); + size_t pos = 19; + + /* Fractional seconds */ + if (s.length() > pos && s[pos] == '.') { + auto end = s.find('+', pos); + if (end == std::string::npos) { + end = s.find('-', pos); + } + if (end == std::string::npos) { + end = s.length(); + } + auto const length = end - pos; + _millisecond = lexical_cast(s.substr(pos + 1, std::min(static_cast(3), length - 1))); + pos = end; + } else { + _millisecond = 0; + } - if (with_tz && s[tz_pos] == '-') { - _offset.set_hour(-_offset.hour()); - _offset.set_minute(-_offset.minute()); + /* Timezone */ + if (pos != s.length()) { + if (s[pos] != '+' && s[pos] != '-') { + throw TimeFormatError(s); + } + if ((s.length() - pos) != 6) { + throw TimeFormatError(s); + } + + _offset.set_hour(lexical_cast(s.substr(pos + 1, 2))); + _offset.set_minute(lexical_cast(s.substr(pos + 4, 2))); + + if (s[pos] == '-') { + _offset.set_hour(-_offset.hour()); + _offset.set_minute(-_offset.minute()); + } + } else { + _offset.set_hour(0); + _offset.set_minute(0); } } diff --git a/test/local_time_test.cc b/test/local_time_test.cc index 8aedd47b..f7f5b133 100644 --- a/test/local_time_test.cc +++ b/test/local_time_test.cc @@ -50,6 +50,18 @@ BOOST_AUTO_TEST_CASE (local_time_basic_test) /* Correctly-formatted */ + { + dcp::LocalTime t("2013-01-05T18:06:59"); + BOOST_CHECK_EQUAL(t._year, 2013); + BOOST_CHECK_EQUAL(t._month, 1); + BOOST_CHECK_EQUAL(t._day, 5); + BOOST_CHECK_EQUAL(t._hour, 18); + BOOST_CHECK_EQUAL(t._minute, 6); + BOOST_CHECK_EQUAL(t._second, 59); + BOOST_CHECK(t._offset == dcp::UTCOffset(0, 0)); + BOOST_CHECK_EQUAL(t.as_string(), "2013-01-05T18:06:59+00:00"); + } + { dcp::LocalTime t ("2013-01-05T18:06:59+04:00"); BOOST_CHECK_EQUAL (t._year, 2013); @@ -113,6 +125,20 @@ BOOST_AUTO_TEST_CASE (local_time_basic_test) BOOST_CHECK_EQUAL (t.as_string(false, false), "2011-11-20T01:06:59"); } + { + dcp::LocalTime t("2011-11-20T01:06:59.45678901-09:30"); + BOOST_CHECK_EQUAL(t._year, 2011); + BOOST_CHECK_EQUAL(t._month, 11); + BOOST_CHECK_EQUAL(t._day, 20); + BOOST_CHECK_EQUAL(t._hour, 1); + BOOST_CHECK_EQUAL(t._minute, 6); + BOOST_CHECK_EQUAL(t._second, 59); + /* The fractional seconds here is truncated rather than rounded, for better or worse */ + BOOST_CHECK_EQUAL(t._millisecond, 456); + BOOST_CHECK(t._offset == dcp::UTCOffset(-9, -30)); + BOOST_CHECK_EQUAL(t.as_string(false, false), "2011-11-20T01:06:59"); + } + { /* Construction from boost::posix_time::ptime */ dcp::LocalTime b (boost::posix_time::time_from_string ("2002-01-20 19:03:56")); -- 2.30.2