diff --git a/trantor/unittests/DateUnittest.cc b/trantor/unittests/DateUnittest.cc index 4583bc98..a1c7c17f 100644 --- a/trantor/unittests/DateUnittest.cc +++ b/trantor/unittests/DateUnittest.cc @@ -1,6 +1,7 @@ #include #include #include +#include #include using namespace trantor; TEST(Date, constructorTest) @@ -107,6 +108,28 @@ TEST(Date, DatabaseStringTest) us = (dbDate.microSecondsSinceEpoch() % 1000000); EXPECT_EQ(us, 3); } +TEST(Date, TimezoneTest) +{ + std::string str0 = "2024-01-01 04:00:00.123"; + std::vector strs{"2024-01-01 12:00:00.123 +08:00", + "2024-01-01 11:00:00.123+0700", + "2024-01-01 10:00:00.123 0600", + "2024-01-01 03:00:00.123 -01:00", + "2024-01-01 02:00:00.123-02:00", + "2024-01-01 01:00:00.123 -0300", + "2024-01-01 12:00:00.123 08", + "2024-01-01 02:00:00.123-02", + "2024-01-01 14:00:00.123+10"}; + + auto date = trantor::Date::fromDbString(str0); + for (auto &s : strs) + { + auto dateTz = trantor::Date::parseDatetimeTz(s); + EXPECT_EQ(date.microSecondsSinceEpoch(), + dateTz.microSecondsSinceEpoch()); + } +} + int main(int argc, char **argv) { testing::InitGoogleTest(&argc, argv); diff --git a/trantor/utils/Date.cc b/trantor/utils/Date.cc index 0da38731..a7e9cf46 100644 --- a/trantor/utils/Date.cc +++ b/trantor/utils/Date.cc @@ -323,6 +323,109 @@ Date Date::fromDbString(const std::string &datetime) static_cast(timezoneOffset())); } +Date Date::parseDatetimeTz(const std::string &datetime) +{ + unsigned int year = {0}, month = {0}, day = {0}, hour = {0}, minute = {0}, + second = {0}, microSecond = {0}; + int tzSign{0}, tzOffset{0}; + std::vector v = splitString(datetime, " "); + if (v.empty()) + { + throw std::invalid_argument("Invalid date string: " + datetime); + } + + // parse date + const std::vector date = splitString(v[0], "-"); + if (date.size() != 3) + { + throw std::invalid_argument("Invalid date string: " + datetime); + } + year = std::stol(date[0]); + month = std::stol(date[1]); + day = std::stol(date[2]); + + // only have date part + if (v.size() <= 1) + { + return trantor::Date{year, month, day}; + } + + // check timezone without space seperated + if (v.size() == 2) + { + auto pos = v[1].find('+'); + if (pos != std::string::npos) + { + tzSign = 1; + v.push_back(v[1].substr(pos + 1)); + v[1] = v[1].substr(0, pos); + } + else if ((pos = v[1].find('-')) != std::string::npos) + { + tzSign = -1; + v.push_back(v[1].substr(pos + 1)); + v[1] = v[1].substr(0, pos); + } + } + + // parse time + std::vector timeParts = splitString(v[1], ":"); + if (timeParts.size() < 2 || timeParts.size() > 3) + { + throw std::invalid_argument("Invalid time string: " + datetime); + } + hour = std::stol(timeParts[0]); + minute = std::stol(timeParts[1]); + if (timeParts.size() == 3) + { + auto secParts = splitString(timeParts[2], "."); + second = std::stol(secParts[0]); + // micro seconds + if (secParts.size() > 1) + { + if (secParts[1].length() > 6) + { + secParts[1].resize(6); + } + else if (secParts[1].length() < 6) + { + secParts[1].append(6 - secParts[1].length(), '0'); + } + microSecond = std::stol(secParts[1]); + } + } + + // timezone + if (v.size() >= 3) + { + std::string &tz = v[2]; + if (tzSign == 0) + { + if (tz[0] == '-') + { + tz = tz.substr(1); + tzSign = -1; + } + else + { + tzSign = 1; + } + } + + auto tzParts = splitString(tz, ":"); + if (tzParts.size() == 1 && tz.size() == 4) + { + tzParts = {tz.substr(0, 2), tz.substr(2)}; // 0800 + } + int tzHour = std::stoi(tzParts[0]); + int tzMin = tzParts.size() > 1 ? std::stoi(tzParts[1]) : 0; + tzOffset = tzSign * (tzHour * 3600 + tzMin * 60); + } + + return trantor::Date(year, month, day, hour, minute, second, microSecond) + .after(timezoneOffset() - tzOffset); +} + std::string Date::toCustomFormattedStringLocal(const std::string &fmtStr, bool showMicroseconds) const { diff --git a/trantor/utils/Date.h b/trantor/utils/Date.h index 4b1c8e60..a90a9e45 100644 --- a/trantor/utils/Date.h +++ b/trantor/utils/Date.h @@ -295,6 +295,17 @@ class TRANTOR_EXPORT Date */ static Date fromDbString(const std::string &datetime); + /** + * @brief Parse a datetime string + * Could be following format: + * - yyyy-mm-dd + * - yyyy-mm-dd HH:MM[:SS[.ffffff]] + * - yyyy-mm-dd HH:MM[:SS[.ffffff]][ ][+-]08 + * - yyyy-mm-dd HH:MM[:SS[.ffffff]][ ][+-]08:00 + * - yyyy-mm-dd HH:MM[:SS[.ffffff]][ ][+-]0800 + */ + static Date parseDatetimeTz(const std::string &datetime); + /* clang-format off */ /** * @brief Generate a UTC time string.