diff --git a/cpp_utils/include/cpp_utils/macros/macros.hpp b/cpp_utils/include/cpp_utils/macros/macros.hpp index 2bd7c3d2..f0d0e96c 100644 --- a/cpp_utils/include/cpp_utils/macros/macros.hpp +++ b/cpp_utils/include/cpp_utils/macros/macros.hpp @@ -87,5 +87,12 @@ namespace utils { #define _EPROSIMA_WINDOWS_PLATFORM 1 #endif // if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(_MSC_VER) +#if defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(_WIN64) || defined(__x86_64__) || \ + defined(__ppc64__) || defined(__aarch64__) +#define _PLATFORM_64BIT 1 +#else +#define _PLATFORM_32BIT 1 +#endif // if defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(_WIN64) || defined(__x86_64__) || defined(__ppc64__) || defined(__aarch64__) + } /* namespace utils */ } /* namespace eprosima */ diff --git a/cpp_utils/include/cpp_utils/time/time_utils.hpp b/cpp_utils/include/cpp_utils/time/time_utils.hpp index 31818a50..7e41226a 100644 --- a/cpp_utils/include/cpp_utils/time/time_utils.hpp +++ b/cpp_utils/include/cpp_utils/time/time_utils.hpp @@ -29,10 +29,15 @@ namespace utils { //! Type of Duration in milliseconds using Duration_ms = uint32_t; +/** + * Type used to fix the clock to the system clock + */ +using Timeclock = std::chrono::system_clock; + /** * Type used to represent time points */ -using Timestamp = std::chrono::time_point; +using Timestamp = std::chrono::time_point; /** * @brief Now time @@ -98,5 +103,17 @@ CPP_UTILS_DllAPI std::chrono::milliseconds duration_to_ms( CPP_UTILS_DllAPI void sleep_for( const Duration_ms& sleep_time) noexcept; +/** + * @brief Makes sure that the time is clamped into a valid range. + * This is useful for keeping time values within the ranges + * depending on the platform. + * + * @param time time to normalize. + * + * @return normalized time value. + */ +CPP_UTILS_DllAPI time_t normalize( + const time_t& time) noexcept; + } /* namespace utils */ } /* namespace eprosima */ diff --git a/cpp_utils/src/cpp/time/time_utils.cpp b/cpp_utils/src/cpp/time/time_utils.cpp index 6a461719..77a5e026 100644 --- a/cpp_utils/src/cpp/time/time_utils.cpp +++ b/cpp_utils/src/cpp/time/time_utils.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include // These functions has different names in windows @@ -36,17 +37,17 @@ namespace utils { Timestamp now() noexcept { - return std::chrono::system_clock::now(); + return Timeclock::now(); } Timestamp the_end_of_time() noexcept { - return std::chrono::time_point::max(); + return Timestamp::max(); } Timestamp the_beginning_of_time() noexcept { - return std::chrono::time_point::min(); + return Timestamp::min(); } Timestamp date_to_timestamp( @@ -66,7 +67,7 @@ Timestamp date_to_timestamp( tm.tm_mon = static_cast(month) - 1; tm.tm_year = static_cast(year) - 1900; - return std::chrono::system_clock::from_time_t(timegm(&tm)); + return Timeclock::from_time_t(normalize(timegm(&tm))); } Timestamp time_to_timestamp( @@ -77,16 +78,14 @@ Timestamp time_to_timestamp( std::tm tm; // Initialise with current timestamp to set date - auto current_ts = now(); - std::chrono::high_resolution_clock::time_point::duration duration = current_ts.time_since_epoch(); - time_t duration_seconds = std::chrono::duration_cast(duration).count(); + time_t duration_seconds = normalize(Timeclock::to_time_t(now())); tm = *std::gmtime(&duration_seconds); tm.tm_sec = static_cast(second); tm.tm_min = static_cast(minute); tm.tm_hour = static_cast(hour); - return std::chrono::system_clock::from_time_t(timegm(&tm)); + return Timeclock::from_time_t(timegm(&tm)); } std::string timestamp_to_string( @@ -95,8 +94,7 @@ std::string timestamp_to_string( bool local_time /* = false */) { std::ostringstream ss; - const std::chrono::high_resolution_clock::time_point::duration duration = timestamp.time_since_epoch(); - const time_t duration_seconds = std::chrono::duration_cast(duration).count(); + time_t duration_seconds = normalize(Timeclock::to_time_t(timestamp)); std::tm* tm = nullptr; if (local_time) @@ -153,7 +151,14 @@ Timestamp string_to_timestamp( { utc_time = timegm(&tm); } - return std::chrono::system_clock::from_time_t(utc_time); + + if ((time_t)-1 == utc_time) + { + throw ValueNotAllowedException( + STR_ENTRY << "Failed to convert string to timestamp"); + } + + return Timeclock::from_time_t(utc_time); } std::chrono::milliseconds duration_to_ms( @@ -168,5 +173,29 @@ void sleep_for( std::this_thread::sleep_for(duration_to_ms(sleep_time)); } +time_t normalize( + const time_t& time) noexcept +{ + time_t normalized_time = time; +#if _EPROSIMA_WINDOWS_PLATFORM // In Windows std::gmtime does not support negative values + time_t max_value; + +#if _PLATFORM_64BIT + max_value = 32535215999; // In WIN64, max value is 3000-12-31_23-59-59 +#else + max_value = std::numeric_limits::max(); // In WIN32, values greater than 2^32 are not supported +#endif // if PLATFORM_64BIT + + if (0 > time || time > max_value) + { + EPROSIMA_LOG_WARNING(TIME_UTILS, + "Timestamp value: " << time << " is out of range for Windows, clamping to 0 and " << + max_value); + normalized_time = std::max((time_t) 0, std::min(max_value, time)); + } +#endif // if _EPROSIMA_WINDOWS_PLATFORM + return normalized_time; +} + } /* namespace utils */ } /* namespace eprosima */ diff --git a/cpp_utils/test/unittest/time/time_utils_test.cpp b/cpp_utils/test/unittest/time/time_utils_test.cpp index 530fcc85..0121c0fd 100644 --- a/cpp_utils/test/unittest/time/time_utils_test.cpp +++ b/cpp_utils/test/unittest/time/time_utils_test.cpp @@ -31,7 +31,11 @@ using namespace eprosima::utils; * - now * - now with alternative format * - old time + * - date before 1970 + * - the beginning of time * - future time + * - date after 2038 + * - the end of time * - some time today */ TEST(time_utils_test, timestamp_to_string_to_timestamp) @@ -112,14 +116,81 @@ TEST(time_utils_test, timestamp_to_string_to_timestamp) ASSERT_EQ(timestamp_to_string(old_time_from_str), expected_string_os.str()); } + // date before 1970 + { + Timestamp old_time = date_to_timestamp(1959u, 7u, 20u, 6u, 39u, 42u); + std::string old_time_str = timestamp_to_string(old_time); + + std::ostringstream expected_string_os; +#if _EPROSIMA_WINDOWS_PLATFORM + expected_string_os + << 1970 + << "-" << "01" + << "-" << "01" + << "_" << "00" + << "-" << "00" + << "-" << "00"; +#else + expected_string_os + << 1959 + << "-" << "07" + << "-" << 20 + << "_" << "06" + << "-" << 39 + << "-" << 42; +#endif // _EPROSIMA_WINDOWS_PLATFORM + + // Test timestamp_to_string + ASSERT_EQ(old_time_str, expected_string_os.str()); + + // Test string_to_timestamp + Timestamp old_time_from_str = string_to_timestamp(old_time_str); + // NOTE: cannot directly compare timestamps because some precision is lost during ts->str conversion + ASSERT_EQ(timestamp_to_string(old_time_from_str), expected_string_os.str()); + } + + // the begininng of time + { + Timestamp beginning_time = the_beginning_of_time(); + std::string beginning_time_str = timestamp_to_string(beginning_time); + + std::ostringstream expected_string_os; +#if _EPROSIMA_WINDOWS_PLATFORM + expected_string_os + << 1970 + << "-" << "01" + << "-" << "01" + << "_" << "00" + << "-" << "00" + << "-" << "00"; +#else //1677-09-21_00-12-44 + expected_string_os + << 1677 + << "-" << "09" + << "-" << "21" + << "_" << "00" + << "-" << "12" + << "-" << "44"; +#endif // _EPROSIMA_WINDOWS_PLATFORM + + + // Test timestamp_to_string + ASSERT_EQ(beginning_time_str, expected_string_os.str()); + + // Test string_to_timestamp + Timestamp beginning_time_from_str = string_to_timestamp(beginning_time_str); + // NOTE: cannot directly compare timestamps because some precision is lost during ts->str conversion + ASSERT_EQ(timestamp_to_string(beginning_time_from_str), expected_string_os.str()); + } + // future time { - Timestamp future_time = date_to_timestamp(2233u, 5u, 22u); + Timestamp future_time = date_to_timestamp(2033u, 5u, 22u); std::string future_time_str = timestamp_to_string(future_time); std::ostringstream expected_string_os; expected_string_os - << 2233 + << 2033 << "-" << "05" << "-" << 22 << "_" << "00" @@ -135,6 +206,81 @@ TEST(time_utils_test, timestamp_to_string_to_timestamp) ASSERT_EQ(timestamp_to_string(future_time_from_str), expected_string_os.str()); } + // date after 2038 + { + Timestamp future_time = date_to_timestamp(2049u, 5u, 22u); + std::string future_time_str = timestamp_to_string(future_time); + + std::ostringstream expected_string_os; +#if _EPROSIMA_WINDOWS_PLATFORM && PLATFORM_32BIT + expected_string_os + << 2038 + << "-" << "01" + << "-" << 19 + << "_" << "03" + << "-" << "14" + << "-" << "07"; +#else + expected_string_os + << 2049 + << "-" << "05" + << "-" << 22 + << "_" << "00" + << "-" << "00" + << "-" << "00"; +#endif // _EPROSIMA_WINDOWS_PLATFORM + + // Test timestamp_to_string + ASSERT_EQ(future_time_str, expected_string_os.str()); + + // Test string_to_timestamp + Timestamp future_time_from_str = string_to_timestamp(future_time_str); + // NOTE: cannot directly compare timestamps because some precision is lost during ts->str conversion + ASSERT_EQ(timestamp_to_string(future_time_from_str), expected_string_os.str()); + } + + // the end of time + { + Timestamp end_time = the_end_of_time(); + std::string end_time_str = timestamp_to_string(end_time); + + std::ostringstream expected_string_os; +#if _EPROSIMA_WINDOWS_PLATFORM && _PLATFORM_32BIT + expected_string_os + << 2038 + << "-" << "01" + << "-" << 19 + << "_" << "03" + << "-" << "14" + << "-" << "07"; +#elif _EPROSIMA_WINDOWS_PLATFORM && _PLATFORM_64BIT + expected_string_os + << 3000 + << "-" << "12" + << "-" << 31 + << "_" << "23" + << "-" << "59" + << "-" << "59"; +#else // 2262-04-11 23:47:16 + expected_string_os + << 2262 + << "-" << "04" + << "-" << 11 + << "_" << "23" + << "-" << "47" + << "-" << "16"; +#endif // _EPROSIMA_WINDOWS_PLATFORM + + + // Test timestamp_to_string + ASSERT_EQ(end_time_str, expected_string_os.str()); + + // Test string_to_timestamp + Timestamp end_time_from_str = string_to_timestamp(end_time_str); + // NOTE: cannot directly compare timestamps because some precision is lost during ts->str conversion + ASSERT_EQ(timestamp_to_string(end_time_from_str), expected_string_os.str()); + } + // some time today { Timestamp some_time_today = time_to_timestamp(13u, 13u, 13u);