take precautions for 32bit time_t/long
authorPhilipp Gesang <philipp.gesang@intra2net.com>
Thu, 1 Feb 2018 09:50:02 +0000 (10:50 +0100)
committerThomas Jarosch <thomas.jarosch@intra2net.com>
Wed, 27 Mar 2019 09:31:38 +0000 (10:31 +0100)
Direct handling of time_t or conversions to it from other
representations will cause failures on 32 bit systems. Introduce
an exception ``conversion_error'' to communicate these conditions
from constructors.

src/timefunc.cpp
src/timefunc.hxx
test/test_timefunc.cpp

index e360bb4..f111763 100644 (file)
@@ -1056,15 +1056,30 @@ namespace clock {
         , err     (0)
     { }
 
+    /*
+     * Ctor from *struct tm*. On 32 bit systems the conversion to *time_t* will
+     * fail with years outside the range from epoch to 2038.
+     */
     Time::Time (const struct tm          &tm,
                 const enum type::id       id,
-                const enum type::variant  var) NOEXCEPT
+                const enum type::variant  var)
     {
         struct tm tmp_tm; /* dummy for mktime(3) */
         Time   tmp_time;
 
         memcpy (&tmp_tm, &tm, sizeof (tmp_tm));
-        tmp_time = Time (mktime (&tmp_tm), 0l, id, var);
+
+        errno = 0;
+        const 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,
+                                    std::string ("mktime: from struct tm {")
+                                    + std::string (datestr, 0, strlen(datestr)-1)
+                                    + "}");
+        }
+
+        tmp_time = Time (t, 0l, id, var);
 
         this->swap (tmp_time);
     }
@@ -1265,7 +1280,12 @@ namespace clock {
             return boost::none;
         }
 
-        return Time (*tm, id, var);
+        try {
+            return Time (*tm, id, var);
+        }
+        catch (conversion_error &_unused) { }
+
+        return boost::none;
     }
 
 } /* [namespace clock] */
index 4b4e3c9..159d0ec 100644 (file)
@@ -254,6 +254,26 @@ namespace I2n {
 
 namespace clock {
 
+    class conversion_error : public std::exception
+    {
+        public:
+            const int         err;
+            const std::string context;
+
+            conversion_error (const int err, const std::string &context)
+                : err     (err)
+                , context (context)
+            { }
+
+            virtual ~conversion_error (void) throw() { };
+
+            operator std::string (void)
+            {
+                return std::string ("errno=") + I2n::to_string (this->err)
+                     + " [" + this->context + "]";
+            }
+    };
+
     namespace {
 
         /* helper for ctor initializer list; we still lack aggregate initializers */
@@ -355,20 +375,22 @@ namespace clock {
                 , err     (t.err)
             { }
 
-            inline Time (const time_t sec,
-                         const long               nsec = 0,
-                         const enum type::id      id   = type::mono,
-                         const enum type::variant var  = type::dflt,
-                         const int                err  = 0) NOEXCEPT
+            inline
+            Time (const time_t sec,
+                  const long               nsec = 0,
+                  const enum type::id      id   = type::mono,
+                  const enum type::variant var  = type::dflt,
+                  const int                err  = 0) NOEXCEPT
                 : value   (timespec_of_parts (sec, nsec))
                 , id      (id)
                 , variant (var)
                 , err     (err)
             { this->carry_nsec (); }
 
+            explicit
             Time (const struct tm          &tm,
                   const enum type::id       id  = type::mono,
-                  const enum type::variant  var = type::dflt) NOEXCEPT;
+                  const enum type::variant  var = type::dflt);
 
         /* value read access *************************************************/
         public:
index 9217261..d286e50 100644 (file)
@@ -706,12 +706,21 @@ BOOST_AUTO_TEST_SUITE(Clock)
         I2n::clock::Time tsum;
 
         t1.set (2187, 2187);
+# if LONG_BIT == 32
+        t2.set (1300,  2L * 1000 * 1000 * 1000 + 1337);
+# else
         t2.set (1300, 37L * 1000 * 1000 * 1000 + 1337);
+# endif
 
         tsum = t1 + t2;
 
+# if LONG_BIT == 32
+        BOOST_CHECK_EQUAL(tsum.get_sec  (), 3489);
+        BOOST_CHECK_EQUAL(tsum.get_nsec (), 3524);
+# else
         BOOST_CHECK_EQUAL(tsum.get_sec  (), 3524);
         BOOST_CHECK_EQUAL(tsum.get_nsec (), 3524);
+# endif
     }
 
     BOOST_AUTO_TEST_CASE(op_add_time_t)
@@ -1028,10 +1037,14 @@ BOOST_AUTO_TEST_SUITE(Clock)
         boost::optional<I2n::clock::Time> t1 = I2n::clock::time_of_iso8601 (in1);
         boost::optional<I2n::clock::Time> t2 = I2n::clock::time_of_iso8601 (in2);
 
+# if LONG_BIT == 32
+        BOOST_CHECK(!t1);
+# else
         BOOST_CHECK(t1);
-        BOOST_CHECK(t2);
-
         BOOST_CHECK_EQUAL(*t1->format_iso8601 (), in1);
+# endif
+
+        BOOST_CHECK(t2);
         BOOST_CHECK_EQUAL(*t2->format_iso8601 (), in2);
     }
 
@@ -1043,29 +1056,32 @@ BOOST_AUTO_TEST_SUITE(Clock)
         boost::optional<I2n::clock::Time> t1 = I2n::clock::time_of_iso8601 (in1);
         boost::optional<I2n::clock::Time> t2 = I2n::clock::time_of_iso8601 (in2);
 
+# if LONG_BIT == 32
+        BOOST_CHECK(!t1);
+        BOOST_CHECK(!t2);
+# else
         BOOST_CHECK(t1);
         BOOST_CHECK(t2);
-
         BOOST_CHECK_EQUAL(*t1->format_iso8601 (), in1);
         BOOST_CHECK_EQUAL(*t2->format_iso8601 (), in2);
+# endif
     }
 
     BOOST_AUTO_TEST_CASE(FromString_iso8601_partial)
     {
         const std::string in1 ("2018-11-11T11:11:11");
         const std::string in2 ("2018-11-11");
-        const std::string in3 ("11:11:11");
+
+        setenv ("TZ", "UTC", 1);
+        tzset();
 
         boost::optional<I2n::clock::Time> t1 =
             I2n::clock::time_of_iso8601 (in1, true, true, false);
         boost::optional<I2n::clock::Time> t2 =
             I2n::clock::time_of_iso8601 (in2, true, false, false);
-        boost::optional<I2n::clock::Time> t3 =
-            I2n::clock::time_of_iso8601 (in3, false, true, false);
 
         BOOST_CHECK(t1);
         BOOST_CHECK(t2);
-        BOOST_CHECK(t3);
         /*
          * We test for the difference here which is zero if the number is
          * correct but causes the difference from the expected value to be
@@ -1073,7 +1089,52 @@ BOOST_AUTO_TEST_SUITE(Clock)
          */
         BOOST_CHECK_EQUAL(*t1->format_iso8601 (true,  true,  true, false), in1);
         BOOST_CHECK_EQUAL(*t2->format_iso8601 (true,  true, false, false), in2);
-        BOOST_CHECK_EQUAL(*t3->format_iso8601 (true, false,  true, false), in3);
+    }
+
+    BOOST_AUTO_TEST_CASE(FromString_iso8601_32bit_time_t_err)
+    {
+        const std::string timeless ("11:11:11");
+        boost::optional<I2n::clock::Time> untimely = boost::none;
+
+        untimely = I2n::clock::time_of_iso8601 (timeless, false, true, false);
+
+# if LONG_BIT == 32
+        BOOST_CHECK(!untimely);
+# else
+        BOOST_CHECK(untimely);
+        BOOST_CHECK_EQUAL(*untimely->format_iso8601 (true, false,  true, false),
+                          timeless);
+# endif
+    }
+
+    BOOST_AUTO_TEST_CASE(Ctor_32bit_time_t_err)
+    {
+        boost::optional<std::string> threw = boost::none;
+
+        struct tm tm;
+        memset (&tm, 0, sizeof (tm));
+
+        tm.tm_sec    = 11;
+        tm.tm_min    = 11;
+        tm.tm_hour   = 11;
+        tm.tm_mday   = 11;
+        tm.tm_mon    = 10;
+        tm.tm_year   = -789;
+        tm.tm_gmtoff = 0;
+
+        try {
+            I2n::clock::Time untimely (tm);
+        } catch (I2n::clock::conversion_error &exn) {
+            threw = std::string (exn);
+        }
+
+
+# if LONG_BIT == 32
+        BOOST_CHECK_EQUAL(*threw,
+                          "errno=0 [mktime: from struct tm {Sun Nov 11 11:11:11 1111}]");
+# else
+        BOOST_CHECK(!threw);
+# endif
     }
 
     BOOST_AUTO_TEST_CASE(containers_list)
@@ -1196,10 +1257,11 @@ BOOST_AUTO_TEST_SUITE(Clock)
         ts.push_back (I2n::clock::Time (42));
         ts.push_back (I2n::clock::Time (1337));
         ts.push_back (I2n::clock::Time (2187));
-        ts.push_back (I2n::clock::Time (0xcafebabe));
-        ts.push_back (I2n::clock::Time (0xdeadbeef));
+        ts.push_back (I2n::clock::Time (0xdead));
+        ts.push_back (I2n::clock::Time (0xbeef));
 
-        BOOST_CHECK_EQUAL(I2n::clock::median (ts), I2n::clock::Time (2187));
+        BOOST_CHECK_EQUAL(I2n::clock::median (ts),
+                          I2n::clock::Time (2187));
     }
 
     BOOST_AUTO_TEST_CASE(containers_list_median_multi_evensize)
@@ -1210,8 +1272,8 @@ BOOST_AUTO_TEST_SUITE(Clock)
         ts.push_back (I2n::clock::Time (1337));
         ts.push_back (I2n::clock::Time (2187));
         ts.push_back (I2n::clock::Time (0xf00d));
-        ts.push_back (I2n::clock::Time (0xcafebabe));
-        ts.push_back (I2n::clock::Time (0xdeadbeef));
+        ts.push_back (I2n::clock::Time (0xfeed));
+        ts.push_back (I2n::clock::Time (0xdeadf0e));
 
         BOOST_CHECK_EQUAL(I2n::clock::median (ts), I2n::clock::Time (0xf00d));
     }