#include #include #include #include #include #include "utils/exceptions.hpp" #include "utils/temporal.hpp" namespace { std::string ToString(const utils::DateParameters &date_parameters) { return fmt::format("{:04d}-{:02d}-{:02d}", date_parameters.years, date_parameters.months, date_parameters.days); } std::string ToString(const utils::LocalTimeParameters &local_time_parameters) { return fmt::format("{:02}:{:02d}:{:02d}", local_time_parameters.hours, local_time_parameters.minutes, local_time_parameters.seconds); } struct TestDateParameters { utils::DateParameters date_parameters; bool should_throw; }; constexpr std::array test_dates{TestDateParameters{{-1996, 11, 22}, true}, TestDateParameters{{1996, -11, 22}, true}, TestDateParameters{{1996, 11, -22}, true}, TestDateParameters{{1, 13, 3}, true}, TestDateParameters{{1, 12, 32}, true}, TestDateParameters{{1, 2, 29}, true}, TestDateParameters{{2020, 2, 29}, false}, TestDateParameters{{1700, 2, 29}, true}, TestDateParameters{{1200, 2, 29}, false}, TestDateParameters{{10000, 12, 3}, true}}; struct TestLocalTimeParameters { utils::LocalTimeParameters local_time_parameters; bool should_throw; }; constexpr std::array test_local_times{ TestLocalTimeParameters{{.hours = 24}, true}, TestLocalTimeParameters{{.hours = -1}, true}, TestLocalTimeParameters{{.minutes = -1}, true}, TestLocalTimeParameters{{.minutes = 60}, true}, TestLocalTimeParameters{{.seconds = -1}, true}, TestLocalTimeParameters{{.minutes = 60}, true}, TestLocalTimeParameters{{.milliseconds = -1}, true}, TestLocalTimeParameters{{.milliseconds = 1000}, true}, TestLocalTimeParameters{{.microseconds = -1}, true}, TestLocalTimeParameters{{.microseconds = 1000}, true}, TestLocalTimeParameters{{23, 59, 59, 999, 999}, false}, TestLocalTimeParameters{{0, 0, 0, 0, 0}, false}}; } // namespace TEST(TemporalTest, DateConstruction) { std::optional test_date; for (const auto [date_parameters, should_throw] : test_dates) { if (should_throw) { EXPECT_THROW(test_date.emplace(date_parameters), utils::BasicException) << ToString(date_parameters); } else { EXPECT_NO_THROW(test_date.emplace(date_parameters)) << ToString(date_parameters); } } } TEST(TemporalTest, DateMicrosecondsSinceEpochConversion) { const auto check_microseconds = [](const auto date_parameters) { utils::Date initial_date{date_parameters}; const auto microseconds = initial_date.MicrosecondsSinceEpoch(); utils::Date new_date{microseconds}; ASSERT_EQ(initial_date, new_date); }; check_microseconds(utils::DateParameters{2020, 11, 22}); check_microseconds(utils::DateParameters{1900, 2, 22}); check_microseconds(utils::DateParameters{0, 1, 1}); check_microseconds(utils::DateParameters{1994, 12, 7}); ASSERT_THROW(check_microseconds(utils::DateParameters{-10, 1, 1}), utils::BasicException); { utils::Date date{utils::DateParameters{1970, 1, 1}}; ASSERT_EQ(date.MicrosecondsSinceEpoch(), 0); } { utils::Date date{utils::DateParameters{1910, 1, 1}}; ASSERT_LT(date.MicrosecondsSinceEpoch(), 0); } { utils::Date date{utils::DateParameters{2021, 1, 1}}; ASSERT_GT(date.MicrosecondsSinceEpoch(), 0); } } TEST(TemporalTest, LocalTimeConstruction) { std::optional test_local_time; for (const auto [local_time_parameters, should_throw] : test_local_times) { if (should_throw) { ASSERT_THROW(test_local_time.emplace(local_time_parameters), utils::BasicException); } else { ASSERT_NO_THROW(test_local_time.emplace(local_time_parameters)); } } } TEST(TemporalTest, LocalTimeMicrosecondsSinceEpochConversion) { const auto check_microseconds = [](const utils::LocalTimeParameters ¶meters) { utils::LocalTime initial_local_time{parameters}; const auto microseconds = initial_local_time.MicrosecondsSinceEpoch(); utils::LocalTime new_local_time{microseconds}; ASSERT_EQ(initial_local_time, new_local_time); }; check_microseconds(utils::LocalTimeParameters{23, 59, 59, 999, 999}); check_microseconds(utils::LocalTimeParameters{0, 0, 0, 0, 0}); check_microseconds(utils::LocalTimeParameters{14, 8, 55, 321, 452}); } TEST(TemporalTest, LocalDateTimeMicrosecondsSinceEpochConversion) { const auto check_microseconds = [](const utils::DateParameters date_parameters, const utils::LocalTimeParameters &local_time_parameters) { utils::LocalDateTime initial_local_date_time{date_parameters, local_time_parameters}; const auto microseconds = initial_local_date_time.MicrosecondsSinceEpoch(); utils::LocalDateTime new_local_date_time{microseconds}; ASSERT_EQ(initial_local_date_time, new_local_date_time); }; check_microseconds(utils::DateParameters{2020, 11, 22}, utils::LocalTimeParameters{0, 0, 0, 0, 0}); check_microseconds(utils::DateParameters{1900, 2, 22}, utils::LocalTimeParameters{0, 0, 0, 0, 0}); check_microseconds(utils::DateParameters{0, 1, 1}, utils::LocalTimeParameters{0, 0, 0, 0, 0}); { utils::LocalDateTime local_date_time(utils::DateParameters{1970, 1, 1}, utils::LocalTimeParameters{0, 0, 0, 0, 0}); ASSERT_EQ(local_date_time.MicrosecondsSinceEpoch(), 0); } { utils::LocalDateTime local_date_time(utils::DateParameters{1970, 1, 1}, utils::LocalTimeParameters{0, 0, 0, 0, 1}); ASSERT_GT(local_date_time.MicrosecondsSinceEpoch(), 0); } { utils::LocalTimeParameters local_time_parameters{12, 10, 40, 42, 42}; utils::LocalDateTime local_date_time{utils::DateParameters{1970, 1, 1}, local_time_parameters}; ASSERT_EQ(local_date_time.MicrosecondsSinceEpoch(), utils::LocalTime{local_time_parameters}.MicrosecondsSinceEpoch()); } { utils::LocalDateTime local_date_time(utils::DateParameters{1910, 1, 1}, utils::LocalTimeParameters{0, 0, 0, 0, 0}); ASSERT_LT(local_date_time.MicrosecondsSinceEpoch(), 0); } { utils::LocalDateTime local_date_time(utils::DateParameters{2021, 1, 1}, utils::LocalTimeParameters{0, 0, 0, 0, 0}); ASSERT_GT(local_date_time.MicrosecondsSinceEpoch(), 0); } } TEST(TemporalTest, DurationConversion) { { utils::Duration duration{{.years = 2.5}}; const auto microseconds = duration.microseconds; utils::LocalDateTime local_date_time{microseconds}; ASSERT_EQ(local_date_time.date.years, 2 + 1970); ASSERT_EQ(local_date_time.date.months, 6 + 1); }; { utils::Duration duration{{.months = 26}}; const auto microseconds = duration.microseconds; utils::LocalDateTime local_date_time{microseconds}; ASSERT_EQ(local_date_time.date.years, 2 + 1970); ASSERT_EQ(local_date_time.date.months, 2 + 1); }; { utils::Duration duration{{.minutes = 123.25}}; const auto microseconds = duration.microseconds; utils::LocalDateTime local_date_time{microseconds}; ASSERT_EQ(local_date_time.date.years, 1970); ASSERT_EQ(local_date_time.date.months, 1); ASSERT_EQ(local_date_time.date.days, 1); ASSERT_EQ(local_date_time.local_time.hours, 2); ASSERT_EQ(local_date_time.local_time.minutes, 3); ASSERT_EQ(local_date_time.local_time.seconds, 15); }; } namespace { using namespace std::literals; constexpr std::array parsing_test_dates_extended{ std::make_pair("2020-11-22"sv, utils::DateParameters{2020, 11, 22}), std::make_pair("2020-11"sv, utils::DateParameters{2020, 11}), }; constexpr std::array parsing_test_dates_basic{std::make_pair("20201122"sv, utils::DateParameters{2020, 11, 22})}; constexpr std::array parsing_test_local_time_extended{ std::make_pair("19:23:21.123456"sv, utils::LocalTimeParameters{19, 23, 21, 123, 456}), std::make_pair("19:23:21.123"sv, utils::LocalTimeParameters{19, 23, 21, 123}), std::make_pair("19:23:21"sv, utils::LocalTimeParameters{19, 23, 21}), std::make_pair("19:23"sv, utils::LocalTimeParameters{19, 23}), std::make_pair("00:00:00.000000"sv, utils::LocalTimeParameters{0, 0, 0, 0, 0}), std::make_pair("01:02:03.004005"sv, utils::LocalTimeParameters{1, 2, 3, 4, 5}), }; constexpr std::array parsing_test_local_time_basic{ std::make_pair("192321.123456"sv, utils::LocalTimeParameters{19, 23, 21, 123, 456}), std::make_pair("192321.123"sv, utils::LocalTimeParameters{19, 23, 21, 123}), std::make_pair("192321"sv, utils::LocalTimeParameters{19, 23, 21}), std::make_pair("1923"sv, utils::LocalTimeParameters{19, 23}), std::make_pair("19"sv, utils::LocalTimeParameters{19}), std::make_pair("000000.000000"sv, utils::LocalTimeParameters{0, 0, 0, 0, 0}), std::make_pair("010203.004005"sv, utils::LocalTimeParameters{1, 2, 3, 4, 5}), }; } // namespace TEST(TemporalTest, DateParsing) { for (const auto &[string, date_parameters] : parsing_test_dates_extended) { ASSERT_EQ(utils::ParseDateParameters(string).first, date_parameters); } for (const auto &[string, date_parameters] : parsing_test_dates_basic) { ASSERT_EQ(utils::ParseDateParameters(string).first, date_parameters); } ASSERT_THROW(utils::ParseDateParameters("202-011-22"), utils::BasicException); ASSERT_THROW(utils::ParseDateParameters("2020-1-022"), utils::BasicException); ASSERT_THROW(utils::ParseDateParameters("2020-11-2-"), utils::BasicException); } TEST(TemporalTest, LocalTimeParsing) { for (const auto &[string, local_time_parameters] : parsing_test_local_time_extended) { ASSERT_EQ(utils::ParseLocalTimeParameters(string).first, local_time_parameters) << ToString(local_time_parameters); ASSERT_EQ(utils::ParseLocalTimeParameters(fmt::format("T{}", string)).first, local_time_parameters) << ToString(local_time_parameters); } for (const auto &[string, local_time_parameters] : parsing_test_local_time_basic) { ASSERT_EQ(utils::ParseLocalTimeParameters(string).first, local_time_parameters) << ToString(local_time_parameters); ASSERT_EQ(utils::ParseLocalTimeParameters(fmt::format("T{}", string)).first, local_time_parameters) << ToString(local_time_parameters); } ASSERT_THROW(utils::ParseLocalTimeParameters("19:20:21s"), utils::BasicException); ASSERT_THROW(utils::ParseLocalTimeParameters("1920:21"), utils::BasicException); } TEST(TemporalTest, LocalDateTimeParsing) { const auto check_local_date_time_combinations = [](const auto &dates, const auto &local_times, const bool is_valid) { for (const auto &[date_string, date_parameters] : dates) { for (const auto &[local_time_string, local_time_parameters] : local_times) { const auto local_date_time_string = fmt::format("{}T{}", date_string, local_time_string); if (is_valid) { EXPECT_EQ(utils::ParseLocalDateTimeParameters(local_date_time_string), (std::pair{date_parameters, local_time_parameters})); } } } }; check_local_date_time_combinations(parsing_test_dates_basic, parsing_test_local_time_basic, true); check_local_date_time_combinations(parsing_test_dates_extended, parsing_test_local_time_extended, true); check_local_date_time_combinations(parsing_test_dates_basic, parsing_test_local_time_extended, false); check_local_date_time_combinations(parsing_test_dates_extended, parsing_test_local_time_basic, false); } void CheckDurationParameters(const auto &values, const auto &expected) { ASSERT_NEAR(values.years, expected.years, 0.01); ASSERT_NEAR(values.months, expected.months, 0.01); ASSERT_NEAR(values.days, expected.days, 0.01); ASSERT_NEAR(values.hours, expected.hours, 0.01); ASSERT_NEAR(values.minutes, expected.minutes, 0.01); ASSERT_NEAR(values.seconds, expected.seconds, 0.01); } TEST(TemporalTest, DurationParsing) { CheckDurationParameters(utils::ParseDurationParameters("P12Y"), utils::DurationParameters{.years = 12.0}); CheckDurationParameters(utils::ParseDurationParameters("P12Y32DT2M"), utils::DurationParameters{.years = 12.0, .days = 32.0, .minutes = 2.0}); CheckDurationParameters(utils::ParseDurationParameters("PT2M"), utils::DurationParameters{.minutes = 2.0}); CheckDurationParameters(utils::ParseDurationParameters("PT2M3S"), utils::DurationParameters{.minutes = 2.0, .seconds = 3.0}); CheckDurationParameters(utils::ParseDurationParameters("PT2.5H"), utils::DurationParameters{.hours = 2.5}); CheckDurationParameters(utils::ParseDurationParameters("P2DT2.5H"), utils::DurationParameters{.days = 2.0, .hours = 2.5}); ASSERT_THROW(utils::ParseDurationParameters("P2M3S"), utils::BasicException); ASSERT_THROW(utils::ParseDurationParameters("PTM3S"), utils::BasicException); ASSERT_THROW(utils::ParseDurationParameters("P2M3Y"), utils::BasicException); ASSERT_THROW(utils::ParseDurationParameters("PT2Y3M"), utils::BasicException); ASSERT_THROW(utils::ParseDurationParameters("12Y32DT2M"), utils::BasicException); ASSERT_THROW(utils::ParseDurationParameters("PY"), utils::BasicException); ASSERT_THROW(utils::ParseDurationParameters(""), utils::BasicException); ASSERT_THROW(utils::ParseDurationParameters("PT2M3SX"), utils::BasicException); ASSERT_THROW(utils::ParseDurationParameters("PT2M3S32"), utils::BasicException); ASSERT_THROW(utils::ParseDurationParameters("PT2.5M3S"), utils::BasicException); ASSERT_THROW(utils::ParseDurationParameters("PT2.5M3.5S"), utils::BasicException); CheckDurationParameters(utils::ParseDurationParameters("P20201122T192032"), utils::DurationParameters{2020, 11, 22, 19, 20, 32}); CheckDurationParameters(utils::ParseDurationParameters("P20201122T192032.333"), utils::DurationParameters{2020, 11, 22, 19, 20, 32, 333}); CheckDurationParameters(utils::ParseDurationParameters("P2020-11-22T19:20:32"), utils::DurationParameters{2020, 11, 22, 19, 20, 32}); } TEST(TemporalTest, PrintDate) { const auto unix_epoch = utils::Date(utils::DateParameters{1970, 1, 1}); std::ostringstream stream; stream << unix_epoch; ASSERT_TRUE(stream); ASSERT_EQ(stream.view(), "1970-01-01"); } TEST(TemporalTest, PrintLocalTime) { const auto lt = utils::LocalTime({13, 2, 40, 100, 50}); std::ostringstream stream; stream << lt; ASSERT_TRUE(stream); ASSERT_EQ(stream.view(), "13:02:40.100050"); } TEST(TemporalTest, PrintDuration) { const auto dur = utils::Duration({1, 0, 0, 0, 0, 0, 0, 0}); std::ostringstream stream; stream << dur; ASSERT_TRUE(stream); ASSERT_EQ(stream.view(), "P0001-00-00T00:00:00.000000"); stream.str(""); stream.clear(); const auto complex_dur = utils::Duration({1, 5, 10, 3, 30, 33, 100, 50}); stream << complex_dur; ASSERT_TRUE(stream); ASSERT_EQ(stream.view(), "P0001-05-10T03:30:33.100050"); /// stream.str(""); /// stream.clear(); /// TODO (kostasrim) /// We do not support pasring negative Durations yet. We are the only ones we have access /// to the constructor below. /* const auto negative_dur = utils::Duration({-1, 5, -10, -3, -30, -33}); stream << negative_dur; ASSERT_TRUE(stream); ASSERT_EQ(stream.view(), "P0001-05-10T03:30:33.000000"); */ } TEST(TemporalTest, PrintLocalDateTime) { const auto unix_epoch = utils::Date(utils::DateParameters{1970, 1, 1}); const auto lt = utils::LocalTime({13, 2, 40, 100, 50}); utils::LocalDateTime ldt(unix_epoch, lt); std::ostringstream stream; stream << ldt; ASSERT_TRUE(stream); ASSERT_EQ(stream.view(), "1970-01-01T13:02:40.100050"); }