Implement ToString function for temporal datatypes (#429)
* Modify `toString` to be able to handle `Date`, `LocalTime`, `LocalDateTime` and `Duration` * Add unit tests * Make `operator<<` use the `ToString()` implementations * Add tests to verify the correctness of negative durations * Add more tests to look for cases when the individual duration entities overflow.
This commit is contained in:
parent
063e297e1e
commit
7fc0fb6520
@ -882,21 +882,36 @@ TypedValue Id(const TypedValue *args, int64_t nargs, const FunctionContext &ctx)
|
||||
}
|
||||
|
||||
TypedValue ToString(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
|
||||
FType<Or<Null, String, Number, Bool>>("toString", args, nargs);
|
||||
FType<Or<Null, String, Number, Date, LocalTime, LocalDateTime, Duration, Bool>>("toString", args, nargs);
|
||||
const auto &arg = args[0];
|
||||
if (arg.IsNull()) {
|
||||
return TypedValue(ctx.memory);
|
||||
} else if (arg.IsString()) {
|
||||
}
|
||||
if (arg.IsString()) {
|
||||
return TypedValue(arg, ctx.memory);
|
||||
} else if (arg.IsInt()) {
|
||||
}
|
||||
if (arg.IsInt()) {
|
||||
// TODO: This is making a pointless copy of std::string, we may want to
|
||||
// use a different conversion to string
|
||||
return TypedValue(std::to_string(arg.ValueInt()), ctx.memory);
|
||||
} else if (arg.IsDouble()) {
|
||||
return TypedValue(std::to_string(arg.ValueDouble()), ctx.memory);
|
||||
} else {
|
||||
return TypedValue(arg.ValueBool() ? "true" : "false", ctx.memory);
|
||||
}
|
||||
if (arg.IsDouble()) {
|
||||
return TypedValue(std::to_string(arg.ValueDouble()), ctx.memory);
|
||||
}
|
||||
if (arg.IsDate()) {
|
||||
return TypedValue(arg.ValueDate().ToString(), ctx.memory);
|
||||
}
|
||||
if (arg.IsLocalTime()) {
|
||||
return TypedValue(arg.ValueLocalTime().ToString(), ctx.memory);
|
||||
}
|
||||
if (arg.IsLocalDateTime()) {
|
||||
return TypedValue(arg.ValueLocalDateTime().ToString(), ctx.memory);
|
||||
}
|
||||
if (arg.IsDuration()) {
|
||||
return TypedValue(arg.ValueDuration().ToString(), ctx.memory);
|
||||
}
|
||||
|
||||
return TypedValue(arg.ValueBool() ? "true" : "false", ctx.memory);
|
||||
}
|
||||
|
||||
TypedValue Timestamp(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "utils/exceptions.hpp"
|
||||
@ -175,6 +176,10 @@ int64_t Date::MicrosecondsSinceEpoch() const {
|
||||
|
||||
int64_t Date::DaysSinceEpoch() const { return utils::DaysSinceEpoch(year, month, day).count(); }
|
||||
|
||||
std::string Date::ToString() const {
|
||||
return fmt::format("{:0>4}-{:0>2}-{:0>2}", year, static_cast<int>(month), static_cast<int>(day));
|
||||
}
|
||||
|
||||
size_t DateHash::operator()(const Date &date) const {
|
||||
utils::HashCombine<uint64_t, uint64_t> hasher;
|
||||
size_t result = hasher(0, date.year);
|
||||
@ -377,6 +382,15 @@ int64_t LocalTime::NanosecondsSinceEpoch() const {
|
||||
return chrono::duration_cast<chrono::nanoseconds>(SumLocalTimeParts()).count();
|
||||
}
|
||||
|
||||
std::string LocalTime::ToString() const {
|
||||
using milli = std::chrono::milliseconds;
|
||||
using micro = std::chrono::microseconds;
|
||||
const auto subseconds = milli(millisecond) + micro(microsecond);
|
||||
|
||||
return fmt::format("{:0>2}:{:0>2}:{:0>2}.{:0>6}", static_cast<int>(hour), static_cast<int>(minute),
|
||||
static_cast<int>(second), subseconds.count());
|
||||
}
|
||||
|
||||
size_t LocalTimeHash::operator()(const LocalTime &local_time) const {
|
||||
utils::HashCombine<uint64_t, uint64_t> hasher;
|
||||
size_t result = hasher(0, local_time.hour);
|
||||
@ -486,6 +500,8 @@ int64_t LocalDateTime::SubSecondsAsNanoseconds() const {
|
||||
return (milli_as_nanos + micros_as_nanos).count();
|
||||
}
|
||||
|
||||
std::string LocalDateTime::ToString() const { return date.ToString() + 'T' + local_time.ToString(); }
|
||||
|
||||
LocalDateTime::LocalDateTime(const DateParameters &date_parameters, const LocalTimeParameters &local_time_parameters)
|
||||
: date(date_parameters), local_time(local_time_parameters) {}
|
||||
|
||||
@ -699,6 +715,23 @@ int64_t Duration::SubSecondsAsNanoseconds() const {
|
||||
return chrono::duration_cast<chrono::nanoseconds>(micros - secs).count();
|
||||
}
|
||||
|
||||
std::string Duration::ToString() const {
|
||||
// Format [nD]T[nH]:[nM]:[nS].
|
||||
namespace chrono = std::chrono;
|
||||
auto micros = chrono::microseconds(microseconds);
|
||||
const auto dd = GetAndSubtractDuration<chrono::days>(micros);
|
||||
const auto h = GetAndSubtractDuration<chrono::hours>(micros);
|
||||
const auto m = GetAndSubtractDuration<chrono::minutes>(micros);
|
||||
const auto s = GetAndSubtractDuration<chrono::seconds>(micros);
|
||||
|
||||
auto first_half = fmt::format("P{}DT{}H{}M", dd, h, m);
|
||||
auto second_half = fmt::format("{}.{:0>6}S", s, std::abs(micros.count()));
|
||||
if (s == 0 && micros.count() < 0) {
|
||||
return first_half + '-' + second_half;
|
||||
}
|
||||
return first_half + second_half;
|
||||
}
|
||||
|
||||
Duration Duration::operator-() const {
|
||||
if (microseconds == std::numeric_limits<decltype(microseconds)>::min()) [[unlikely]] {
|
||||
throw temporal::InvalidArgumentException("Duration arithmetic overflows");
|
||||
|
@ -87,20 +87,9 @@ struct Duration {
|
||||
int64_t SubDaysAsNanoseconds() const;
|
||||
int64_t SubSecondsAsNanoseconds() const;
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &os, const Duration &dur) {
|
||||
// Format [nD]T[nH]:[nM]:[nS].
|
||||
namespace chrono = std::chrono;
|
||||
auto micros = chrono::microseconds(dur.microseconds);
|
||||
const auto dd = GetAndSubtractDuration<chrono::days>(micros);
|
||||
const auto h = GetAndSubtractDuration<chrono::hours>(micros);
|
||||
const auto m = GetAndSubtractDuration<chrono::minutes>(micros);
|
||||
const auto s = GetAndSubtractDuration<chrono::seconds>(micros);
|
||||
os << fmt::format("P{}DT{}H{}M", dd, h, m);
|
||||
if (s == 0 && micros.count() < 0) {
|
||||
os << '-';
|
||||
}
|
||||
return os << fmt::format("{}.{:0>6}S", s, std::abs(micros.count()));
|
||||
}
|
||||
std::string ToString() const;
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &os, const Duration &dur) { return os << dur.ToString(); }
|
||||
|
||||
Duration operator-() const;
|
||||
|
||||
@ -155,13 +144,11 @@ struct Date {
|
||||
explicit Date(int64_t microseconds);
|
||||
explicit Date(const DateParameters &date_parameters);
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &os, const Date &date) {
|
||||
return os << fmt::format("{:0>2}-{:0>2}-{:0>2}", date.year, static_cast<int>(date.month),
|
||||
static_cast<int>(date.day));
|
||||
}
|
||||
friend std::ostream &operator<<(std::ostream &os, const Date &date) { return os << date.ToString(); }
|
||||
|
||||
int64_t MicrosecondsSinceEpoch() const;
|
||||
int64_t DaysSinceEpoch() const;
|
||||
std::string ToString() const;
|
||||
|
||||
friend Date operator+(const Date &date, const Duration &dur) {
|
||||
namespace chrono = std::chrono;
|
||||
@ -217,17 +204,11 @@ struct LocalTime {
|
||||
// Epoch means the start of the day, i,e, midnight
|
||||
int64_t MicrosecondsSinceEpoch() const;
|
||||
int64_t NanosecondsSinceEpoch() const;
|
||||
std::string ToString() const;
|
||||
|
||||
auto operator<=>(const LocalTime &) const = default;
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &os, const LocalTime <) {
|
||||
namespace chrono = std::chrono;
|
||||
using milli = chrono::milliseconds;
|
||||
using micro = chrono::microseconds;
|
||||
const auto subseconds = milli(lt.millisecond) + micro(lt.microsecond);
|
||||
return os << fmt::format("{:0>2}:{:0>2}:{:0>2}.{:0>6}", static_cast<int>(lt.hour), static_cast<int>(lt.minute),
|
||||
static_cast<int>(lt.second), subseconds.count());
|
||||
}
|
||||
friend std::ostream &operator<<(std::ostream &os, const LocalTime <) { return os << lt.ToString(); }
|
||||
|
||||
friend LocalTime operator+(const LocalTime &local_time, const Duration &dur) {
|
||||
namespace chrono = std::chrono;
|
||||
@ -279,13 +260,11 @@ struct LocalDateTime {
|
||||
int64_t MicrosecondsSinceEpoch() const;
|
||||
int64_t SecondsSinceEpoch() const; // seconds since epoch
|
||||
int64_t SubSecondsAsNanoseconds() const;
|
||||
std::string ToString() const;
|
||||
|
||||
auto operator<=>(const LocalDateTime &) const = default;
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &os, const LocalDateTime &ldt) {
|
||||
os << ldt.date << 'T' << ldt.local_time;
|
||||
return os;
|
||||
}
|
||||
friend std::ostream &operator<<(std::ostream &os, const LocalDateTime &ldt) { return os << ldt.ToString(); }
|
||||
|
||||
friend LocalDateTime operator+(const LocalDateTime &dt, const Duration &dur) {
|
||||
const auto local_date_time_as_duration = Duration(dt.MicrosecondsSinceEpoch());
|
||||
|
@ -1747,6 +1747,26 @@ TEST_F(FunctionTest, ToStringBool) {
|
||||
EXPECT_EQ(EvaluateFunction("TOSTRING", false).ValueString(), "false");
|
||||
}
|
||||
|
||||
TEST_F(FunctionTest, ToStringDate) {
|
||||
const auto date = memgraph::utils::Date({1970, 1, 2});
|
||||
EXPECT_EQ(EvaluateFunction("TOSTRING", date).ValueString(), "1970-01-02");
|
||||
}
|
||||
|
||||
TEST_F(FunctionTest, ToStringLocalTime) {
|
||||
const auto lt = memgraph::utils::LocalTime({13, 2, 40, 100, 50});
|
||||
EXPECT_EQ(EvaluateFunction("TOSTRING", lt).ValueString(), "13:02:40.100050");
|
||||
}
|
||||
|
||||
TEST_F(FunctionTest, ToStringLocalDateTime) {
|
||||
const auto ldt = memgraph::utils::LocalDateTime({1970, 1, 2}, {23, 02, 59});
|
||||
EXPECT_EQ(EvaluateFunction("TOSTRING", ldt).ValueString(), "1970-01-02T23:02:59.000000");
|
||||
}
|
||||
|
||||
TEST_F(FunctionTest, ToStringDuration) {
|
||||
memgraph::utils::Duration duration{{.minute = 2, .second = 2, .microsecond = 33}};
|
||||
EXPECT_EQ(EvaluateFunction("TOSTRING", duration).ValueString(), "P0DT0H2M2.000033S");
|
||||
}
|
||||
|
||||
TEST_F(FunctionTest, ToStringExceptions) { EXPECT_THROW(EvaluateFunction("TOSTRING", 1, 2, 3), QueryRuntimeException); }
|
||||
|
||||
TEST_F(FunctionTest, TimestampVoid) {
|
||||
|
@ -609,3 +609,73 @@ TEST(TemporalTest, LocalDateTimeDelta) {
|
||||
ASSERT_EQ(two_years_after_unix_epoch - unix_epoch,
|
||||
memgraph::utils::Duration({.day = 761, .millisecond = 20, .microsecond = 34}));
|
||||
}
|
||||
|
||||
TEST(TemporalTest, DateConvertsToString) {
|
||||
const auto date1 = memgraph::utils::Date({1970, 1, 2});
|
||||
const std::string date1_expected_str = "1970-01-02";
|
||||
const auto date2 = memgraph::utils::Date({0000, 1, 1});
|
||||
const std::string date2_expected_str = "0000-01-01";
|
||||
const auto date3 = memgraph::utils::Date({2022, 7, 4});
|
||||
const std::string date3_expected_str = "2022-07-04";
|
||||
|
||||
ASSERT_EQ(date1_expected_str, date1.ToString());
|
||||
ASSERT_EQ(date2_expected_str, date2.ToString());
|
||||
ASSERT_EQ(date3_expected_str, date3.ToString());
|
||||
}
|
||||
|
||||
TEST(TemporalTest, LocalTimeConvertsToString) {
|
||||
const auto lt1 = memgraph::utils::LocalTime({13, 2, 40, 100, 50});
|
||||
const std::string lt1_expected_str = "13:02:40.100050";
|
||||
const auto lt2 = memgraph::utils::LocalTime({13, 2, 40});
|
||||
const std::string lt2_expected_str = "13:02:40.000000";
|
||||
const auto lt3 = memgraph::utils::LocalTime({0, 0, 0});
|
||||
const std::string lt3_expected_str = "00:00:00.000000";
|
||||
const auto lt4 = memgraph::utils::LocalTime({3, 2, 4, 6, 7});
|
||||
const std::string lt4_expected_str = "03:02:04.006007";
|
||||
|
||||
ASSERT_EQ(lt1_expected_str, lt1.ToString());
|
||||
ASSERT_EQ(lt2_expected_str, lt2.ToString());
|
||||
ASSERT_EQ(lt3_expected_str, lt3.ToString());
|
||||
ASSERT_EQ(lt4_expected_str, lt4.ToString());
|
||||
}
|
||||
|
||||
TEST(TemporalTest, LocalDateTimeConvertsToString) {
|
||||
const auto ldt1 = memgraph::utils::LocalDateTime({1970, 1, 2}, {23, 02, 59});
|
||||
const std::string ldt1_expected_str = "1970-01-02T23:02:59.000000";
|
||||
const auto ldt2 = memgraph::utils::LocalDateTime({1970, 1, 2}, {23, 02, 59, 456, 123});
|
||||
const std::string ldt2_expected_str = "1970-01-02T23:02:59.456123";
|
||||
const auto ldt3 = memgraph::utils::LocalDateTime({1997, 8, 24}, {16, 32, 9});
|
||||
const std::string ldt3_expected_str = "1997-08-24T16:32:09.000000";
|
||||
|
||||
ASSERT_EQ(ldt1_expected_str, ldt1.ToString());
|
||||
ASSERT_EQ(ldt2_expected_str, ldt2.ToString());
|
||||
ASSERT_EQ(ldt3_expected_str, ldt3.ToString());
|
||||
}
|
||||
|
||||
TEST(TemporalTest, DurationConvertsToString) {
|
||||
memgraph::utils::Duration duration1{{.minute = 2, .second = 2, .microsecond = 33}};
|
||||
const std::string duration1_expected_str = "P0DT0H2M2.000033S";
|
||||
memgraph::utils::Duration duration2{{.hour = 2.5, .minute = 2, .second = 2, .microsecond = 33}};
|
||||
const std::string duration2_expected_str = "P0DT2H32M2.000033S";
|
||||
memgraph::utils::Duration duration3{{.hour = 1.25, .minute = 2, .second = 2}};
|
||||
const std::string duration3_expected_str = "P0DT1H17M2.000000S";
|
||||
memgraph::utils::Duration duration4{{.day = 20, .hour = 1.25, .minute = 2, .second = 2}};
|
||||
const std::string duration4_expected_str = "P20DT1H17M2.000000S";
|
||||
memgraph::utils::Duration duration5{{.hour = -3, .minute = 2, .second = 2, .microsecond = -33}};
|
||||
const std::string duration5_expected_str = "P0DT-2H-57M-58.000033S";
|
||||
memgraph::utils::Duration duration6{{.day = -2, .hour = 3, .minute = 2, .second = 2, .microsecond = 33}};
|
||||
const std::string duration6_expected_str = "P-1DT-20H-57M-57.999967S";
|
||||
memgraph::utils::Duration duration7{{.day = 20, .hour = 72, .minute = 154, .second = 312}};
|
||||
const std::string duration7_expected_str = "P23DT2H39M12.000000S";
|
||||
memgraph::utils::Duration duration8{{.day = 1, .hour = 23, .minute = 59, .second = 60}};
|
||||
const std::string duration8_expected_str = "P2DT0H0M0.000000S";
|
||||
|
||||
ASSERT_EQ(duration1_expected_str, duration1.ToString());
|
||||
ASSERT_EQ(duration2_expected_str, duration2.ToString());
|
||||
ASSERT_EQ(duration3_expected_str, duration3.ToString());
|
||||
ASSERT_EQ(duration4_expected_str, duration4.ToString());
|
||||
ASSERT_EQ(duration5_expected_str, duration5.ToString());
|
||||
ASSERT_EQ(duration6_expected_str, duration6.ToString());
|
||||
ASSERT_EQ(duration7_expected_str, duration7.ToString());
|
||||
ASSERT_EQ(duration8_expected_str, duration8.ToString());
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user