Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions trantor/unittests/DateUnittest.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <trantor/utils/Date.h>
#include <gtest/gtest.h>
#include <string>
#include <vector>
#include <iostream>
using namespace trantor;
TEST(Date, constructorTest)
Expand Down Expand Up @@ -112,6 +113,28 @@ TEST(Date, DatabaseStringTest)
auto epoch = dbDateGMT.microSecondsSinceEpoch();
EXPECT_EQ(epoch, 0);
}
TEST(Date, TimezoneTest)
{
std::string str0 = "2024-01-01 04:00:00.123";
std::vector<std::string> 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);
Expand Down
129 changes: 129 additions & 0 deletions trantor/utils/Date.cc
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ const Date Date::date()
return Date(seconds * MICRO_SECONDS_PER_SEC + tv.tv_usec);
#endif
}

int64_t Date::timezoneOffset()
{
static int64_t offset = -Date(1970, 1, 1).secondsSinceEpoch();
return offset;
}

const Date Date::after(double second) const
{
return Date(static_cast<int64_t>(microSecondsSinceEpoch_ +
Expand Down Expand Up @@ -355,6 +362,128 @@ Date Date::fromDbString(const std::string &datetime)
static_cast<double>(timezoneOffset()));
}

int parseTzOffset(std::string &tz, int tzSign)
{
if (tzSign == 0)
{
if (tz[0] == '-')
{
tz = tz.substr(1);
tzSign = -1;
}
else
{
tzSign = 1;
}
}

if (tz == "Z")
{
return 0;
}

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;
return tzSign * (tzHour * 3600 + tzMin * 60);
}

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<std::string> v = splitString(datetime, " ");
if (v.empty())
{
throw std::invalid_argument("Invalid date string: " + datetime);
}

// parse date
const std::vector<std::string> 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);
}
else if (!v[1].empty() && v[1].back() == 'Z')
{
v[1].pop_back();
tzSign = 1;
v.emplace_back("Z");
}
}

// parse time
std::vector<std::string> 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]);
}
}

trantor::Date dt(year, month, day, hour, minute, second, microSecond);

// timezone
if (v.size() >= 3)
{
tzOffset = parseTzOffset(v[2], tzSign);
return dt.after(timezoneOffset() - tzOffset);
}
else
{
return dt;
}
}

std::string Date::toCustomFormattedStringLocal(const std::string &fmtStr,
bool showMicroseconds) const
{
Expand Down
17 changes: 10 additions & 7 deletions trantor/utils/Date.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,7 @@ class TRANTOR_EXPORT Date
return Date::date();
}

static int64_t timezoneOffset()
{
static int64_t offset = -(
Date::fromDbStringLocal("1970-01-03 00:00:00").secondsSinceEpoch() -
2LL * 3600LL * 24LL);
return offset;
}
static int64_t timezoneOffset();

/**
* @brief Return a new Date instance that represents the time after some
Expand Down Expand Up @@ -293,6 +287,15 @@ 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[[:]00]
*/
static Date parseDatetimeTz(const std::string &datetime);

/* clang-format off */
/**
* @brief Generate a UTC time string.
Expand Down