From cf960f736f6733f3cab2d518df82915b50a8fab2 Mon Sep 17 00:00:00 2001 From: Philipp Gesang Date: Thu, 1 Feb 2018 10:50:02 +0100 Subject: [PATCH] take precautions for 32bit time_t/long 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 | 26 ++++++++++++-- src/timefunc.hxx | 34 +++++++++++++++--- test/test_timefunc.cpp | 88 ++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 126 insertions(+), 22 deletions(-) diff --git a/src/timefunc.cpp b/src/timefunc.cpp index e360bb4..f111763 100644 --- a/src/timefunc.cpp +++ b/src/timefunc.cpp @@ -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] */ diff --git a/src/timefunc.hxx b/src/timefunc.hxx index 4b4e3c9..159d0ec 100644 --- a/src/timefunc.hxx +++ b/src/timefunc.hxx @@ -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: diff --git a/test/test_timefunc.cpp b/test/test_timefunc.cpp index 9217261..d286e50 100644 --- a/test/test_timefunc.cpp +++ b/test/test_timefunc.cpp @@ -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 t1 = I2n::clock::time_of_iso8601 (in1); boost::optional 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 t1 = I2n::clock::time_of_iso8601 (in1); boost::optional 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 t1 = I2n::clock::time_of_iso8601 (in1, true, true, false); boost::optional t2 = I2n::clock::time_of_iso8601 (in2, true, false, false); - boost::optional 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 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 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)); } -- 1.7.1