Fix dcp::LocalTime constructor to cope with longer fractional second parts (DoM ...
authorCarl Hetherington <cth@carlh.net>
Tue, 15 Aug 2023 10:28:21 +0000 (12:28 +0200)
committerCarl Hetherington <cth@carlh.net>
Tue, 15 Aug 2023 10:28:21 +0000 (12:28 +0200)
src/local_time.cc
test/local_time_test.cc

index cadb71379fdda0ad8bb0ab3c954df2637a6ea102..46718a756b7d89615ada90d154127b14f92772c8 100644 (file)
@@ -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<int>(s.substr(0, 4));
@@ -169,14 +145,44 @@ LocalTime::LocalTime (string s)
        _hour = lexical_cast<int>(s.substr(11, 2));
        _minute = lexical_cast<int>(s.substr(14, 2));
        _second = lexical_cast<int>(s.substr(17, 2));
-       _millisecond = with_millisecond ? lexical_cast<int>(s.substr(20, 3)) : 0;
 
-       _offset.set_hour(with_tz ? lexical_cast<int>(s.substr(tz_pos + 1, 2)) : 0);
-       _offset.set_minute(with_tz ? lexical_cast<int>(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<int>(s.substr(pos + 1, std::min(static_cast<size_t>(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<int>(s.substr(pos + 1, 2)));
+               _offset.set_minute(lexical_cast<int>(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);
        }
 }
 
index 8aedd47b2d3d5fdad559656d5ffeb24bb7d11b7f..f7f5b133f048e1ce703a2a22b3210d29cc5bbdfe 100644 (file)
@@ -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"));