apply UTC offset during timestamp conversion
authorPhilipp Gesang <philipp.gesang@intra2net.com>
Fri, 26 Apr 2019 07:47:30 +0000 (09:47 +0200)
committerPhilipp Gesang <philipp.gesang@intra2net.com>
Fri, 26 Apr 2019 14:29:43 +0000 (16:29 +0200)
Glibc strptime(3) does not automatically apply the UTC offset
when present but stores it in the member tm_gmtoff. (Probably
because the standard mandates different, insufficient means
of timezone handling that need to be supported for portability.)

Thus we do that right in the constructor immediately after we
have obtained a valid struct tm.

src/timefunc.cpp
test/test_timefunc.cpp

index 805ffae..6fe2b98 100644 (file)
@@ -1110,7 +1110,8 @@ namespace clock {
         memcpy (&tmp_tm, &tm, sizeof (tmp_tm));
 
         errno = 0;
-        const time_t t = mktime (&tmp_tm);
+
+        time_t t = mktime (&tmp_tm);
         if (t == - 1) { /* Glibc does not set errno on out-of-range here! */
             const char *datestr = asctime (&tm);
             throw conversion_error (errno,
@@ -1119,6 +1120,14 @@ namespace clock {
                                     + "}");
         }
 
+        /*
+         * The timezone shenanigans are there to force we end up using UTC
+         * internally. The trouble with mktime(3) is that the unix timestamp
+         * is defined as “seconds since epoch” but “expressed as local time”.
+         */
+        t += tm.tm_gmtoff;
+        t -= timezone;
+
         tmp_time = Time (t, 0l, id, var);
 
         this->swap (tmp_time);
index 1ee6e47..5fc58e5 100644 (file)
@@ -1229,6 +1229,43 @@ BOOST_AUTO_TEST_SUITE(Clock)
         BOOST_CHECK_EQUAL(*t2->format_iso8601 (true,  true, false, false), in2);
     }
 
+    BOOST_AUTO_TEST_CASE(FromString_iso8601_offset)
+    {
+        const std::string in1 ("2019-04-25T13:41:47Z");
+        const std::string in2 ("2019-04-25T13:41:47+0200"); /* = UTC(in1 + 2h) */
+        const std::string in3 ("2019-04-25T15:41:47+0000"); /* = UTC(in2) */
+
+        this->set_utc ();
+        boost::optional<I2n::clock::Time> t1 = I2n::clock::time_of_iso8601 (in1, true, true, true);
+        boost::optional<I2n::clock::Time> t2 = I2n::clock::time_of_iso8601 (in2, true, true, true);
+        boost::optional<I2n::clock::Time> t3 = I2n::clock::time_of_iso8601 (in3, true, true, true);
+
+        BOOST_CHECK(t1);
+        BOOST_CHECK(t2);
+        BOOST_CHECK(t3);
+
+        BOOST_CHECK_EQUAL(*t1->format_iso8601 (), "2019-04-25T13:41:47+0000");
+        BOOST_CHECK_EQUAL(t1->get_sec (), 1556199707);
+        BOOST_CHECK_EQUAL(*t2->format_iso8601 (), "2019-04-25T15:41:47+0000");
+        BOOST_CHECK_EQUAL(t2->get_sec (), t1->get_sec () + 2 * 60 * 60);
+        BOOST_CHECK_EQUAL(*t2, *t3);
+        BOOST_CHECK_EQUAL(*t3->format_iso8601 (), "2019-04-25T15:41:47+0000");
+    }
+
+    BOOST_AUTO_TEST_CASE(FromString_iso8601_tzdiff)
+    {
+        const std::string in ("2019-04-26T13:45:02+0000");
+
+        this->set_tz ("UTC");
+        boost::optional<I2n::clock::Time> t1 = I2n::clock::time_of_iso8601 (in, true, true, false);
+
+        this->set_tz ("CET");
+        boost::optional<I2n::clock::Time> t2 = I2n::clock::time_of_iso8601 (in, true, true, false);
+
+        BOOST_CHECK_EQUAL(*t1->format_iso8601 (true, true, true, true), in);
+        BOOST_CHECK_EQUAL(*t2->format_iso8601 (true, true, true, true), "2019-04-26T13:45:02+0000");
+    }
+
     BOOST_AUTO_TEST_CASE(FromString_iso8601_32bit_time_t_err)
     {
         const std::string timeless ("11:11:11");