Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support parsing datetime with timezone. #362

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
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 @@ -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<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
103 changes: 103 additions & 0 deletions trantor/utils/Date.cc
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,109 @@ Date Date::fromDbString(const std::string &datetime)
static_cast<double>(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<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);
}
}

// 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]);
}
}

// 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
{
Expand Down
11 changes: 11 additions & 0 deletions trantor/utils/Date.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading