diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1c972d6d9..e181da581 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -128,6 +128,7 @@ add_boost_test(base base_utility/validateutf8 base_utility/EscapeCreateProcessArg base_utility/TruncateUsingHash + base_utility/FormatDateTime base_value/scalar base_value/convert base_value/format diff --git a/test/base-utility.cpp b/test/base-utility.cpp index 65222e1fd..77d91f47a 100644 --- a/test/base-utility.cpp +++ b/test/base-utility.cpp @@ -135,4 +135,53 @@ BOOST_AUTO_TEST_CASE(TruncateUsingHash) std::string(37, 'a') + "...86f33652fcffd7fa1443e246dd34fe5d00e25ffd"); } +BOOST_AUTO_TEST_CASE(FormatDateTime) { + using time_t_limit = std::numeric_limits; + + // Helper to repeat a given string a number of times. + auto repeat = [](const std::string& s, size_t n) { + std::ostringstream stream; + for (size_t i = 0; i < n; ++i) { + stream << s; + } + return stream.str(); + }; + + // Valid inputs. + const double ts = 1136214245.0; // 2006-01-02 15:04:05 UTC + BOOST_CHECK_EQUAL("2006-01-02 15:04:05", Utility::FormatDateTime("%F %T", ts)); + BOOST_CHECK_EQUAL("2006", Utility::FormatDateTime("%Y", ts)); + BOOST_CHECK_EQUAL("2006#2006", Utility::FormatDateTime("%Y#%Y", ts)); + BOOST_CHECK_EQUAL("%", Utility::FormatDateTime("%%", ts)); + BOOST_CHECK_EQUAL("%Y", Utility::FormatDateTime("%%Y", ts)); + BOOST_CHECK_EQUAL("", Utility::FormatDateTime("", ts)); + BOOST_CHECK_EQUAL("1970-01-01 00:00:00", Utility::FormatDateTime("%F %T", 0.0)); + BOOST_CHECK_EQUAL("2038-01-19 03:14:07", Utility::FormatDateTime("%F %T", 2147483647.0)); // 2^31 - 1 + if constexpr (sizeof(time_t) > sizeof(int32_t)) { + BOOST_CHECK_EQUAL("2100-03-14 13:37:42", Utility::FormatDateTime("%F %T", 4108714662.0)); // Past year 2038 + } else { + BOOST_WARN_MESSAGE(false, "skipping test with past 2038 input due to 32 bit time_t"); + } + + // Negative (pre-1970) timestamps. +#ifdef _MSC_VER + // localtime_s() on Windows doesn't seem to like them and always errors out. + BOOST_CHECK_THROW(Utility::FormatDateTime("%F %T", -1.0), posix_error); + BOOST_CHECK_THROW(Utility::FormatDateTime("%F %T", -2147483648.0), posix_error); // -2^31 +#else /* _MSC_VER */ + BOOST_CHECK_EQUAL("1969-12-31 23:59:59", Utility::FormatDateTime("%F %T", -1.0)); + BOOST_CHECK_EQUAL("1901-12-13 20:45:52", Utility::FormatDateTime("%F %T", -2147483648.0)); // -2^31 +#endif /* _MSC_VER */ + + // Values right at the limits of time_t. + // + // With 64 bit time_t, there may not be an exact double representation of its min/max value, std::nextafter() is + // used to move the value towards 0 so that it's within the range of doubles that can be represented as time_t. + // + // These are expected to result in an error due to the intermediate struct tm not being able to represent these + // timestamps, so localtime_r() returns EOVERFLOW which makes the implementation throw an exception. + BOOST_CHECK_THROW(Utility::FormatDateTime("%Y", std::nextafter(time_t_limit::min(), 0)), posix_error); + BOOST_CHECK_THROW(Utility::FormatDateTime("%Y", std::nextafter(time_t_limit::max(), 0)), posix_error); +} + BOOST_AUTO_TEST_SUITE_END()