Compare commits
4 Commits
master
...
add-zoned-
Author | SHA1 | Date | |
---|---|---|---|
|
6f228ad1a1 | ||
|
848212f73e | ||
|
0d4b057491 | ||
|
1e04831295 |
@ -1225,7 +1225,7 @@ TypedValue LocalTime(const TypedValue *args, int64_t nargs, const FunctionContex
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (args[0].IsString()) {
|
if (args[0].IsString()) {
|
||||||
const auto &[local_time_parameters, is_extended] = utils::ParseLocalTimeParameters(args[0].ValueString());
|
const auto &[local_time_parameters, is_extended, _] = utils::ParseLocalTimeParameters(args[0].ValueString());
|
||||||
return TypedValue(utils::LocalTime(local_time_parameters), ctx.memory);
|
return TypedValue(utils::LocalTime(local_time_parameters), ctx.memory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1252,7 +1252,7 @@ TypedValue LocalDateTime(const TypedValue *args, int64_t nargs, const FunctionCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (args[0].IsString()) {
|
if (args[0].IsString()) {
|
||||||
const auto &[date_parameters, local_time_parameters] = ParseLocalDateTimeParameters(args[0].ValueString());
|
const auto &[date_parameters, local_time_parameters, _] = ParseLocalDateTimeParameters(args[0].ValueString());
|
||||||
return TypedValue(utils::LocalDateTime(date_parameters, local_time_parameters), ctx.memory);
|
return TypedValue(utils::LocalDateTime(date_parameters, local_time_parameters), ctx.memory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,7 +195,7 @@ struct mgp_local_time {
|
|||||||
static_assert(std::is_nothrow_copy_constructible_v<memgraph::utils::LocalTime>);
|
static_assert(std::is_nothrow_copy_constructible_v<memgraph::utils::LocalTime>);
|
||||||
|
|
||||||
mgp_local_time(const std::string_view string, memgraph::utils::MemoryResource *memory)
|
mgp_local_time(const std::string_view string, memgraph::utils::MemoryResource *memory)
|
||||||
: memory(memory), local_time(memgraph::utils::ParseLocalTimeParameters(string).first) {}
|
: memory(memory), local_time(memgraph::utils::ParseLocalTimeParameters(string).parameters) {}
|
||||||
|
|
||||||
mgp_local_time(const mgp_local_time_parameters *parameters, memgraph::utils::MemoryResource *memory)
|
mgp_local_time(const mgp_local_time_parameters *parameters, memgraph::utils::MemoryResource *memory)
|
||||||
: memory(memory), local_time(MapLocalTimeParameters(parameters)) {}
|
: memory(memory), local_time(MapLocalTimeParameters(parameters)) {}
|
||||||
@ -229,7 +229,7 @@ struct mgp_local_time {
|
|||||||
};
|
};
|
||||||
|
|
||||||
inline memgraph::utils::LocalDateTime CreateLocalDateTimeFromString(const std::string_view string) {
|
inline memgraph::utils::LocalDateTime CreateLocalDateTimeFromString(const std::string_view string) {
|
||||||
const auto &[date_parameters, local_time_parameters] = memgraph::utils::ParseLocalDateTimeParameters(string);
|
const auto &[date_parameters, local_time_parameters, _] = memgraph::utils::ParseLocalDateTimeParameters(string);
|
||||||
return memgraph::utils::LocalDateTime{date_parameters, local_time_parameters};
|
return memgraph::utils::LocalDateTime{date_parameters, local_time_parameters};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 Memgraph Ltd.
|
// Copyright 2024 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// Use of this software is governed by the Business Source License
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@ -215,7 +215,7 @@ First 3 digits represent milliseconds, while the second 3 digits represent micro
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
std::pair<LocalTimeParameters, bool> ParseLocalTimeParameters(std::string_view local_time_string) {
|
LocalTimeParsing ParseLocalTimeParameters(std::string_view local_time_string, bool in_zoned_dt) {
|
||||||
// https://en.wikipedia.org/wiki/ISO_8601#Times
|
// https://en.wikipedia.org/wiki/ISO_8601#Times
|
||||||
// supported formats:
|
// supported formats:
|
||||||
// hh:mm:ss.ssssss | hhmmss.ssssss
|
// hh:mm:ss.ssssss | hhmmss.ssssss
|
||||||
@ -261,7 +261,7 @@ std::pair<LocalTimeParameters, bool> ParseLocalTimeParameters(std::string_view l
|
|||||||
local_time_string.remove_prefix(2);
|
local_time_string.remove_prefix(2);
|
||||||
|
|
||||||
if (local_time_string.empty()) {
|
if (local_time_string.empty()) {
|
||||||
return {local_time_parameters, false};
|
return {local_time_parameters, false, 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
process_optional_colon();
|
process_optional_colon();
|
||||||
@ -274,7 +274,7 @@ std::pair<LocalTimeParameters, bool> ParseLocalTimeParameters(std::string_view l
|
|||||||
local_time_string.remove_prefix(2);
|
local_time_string.remove_prefix(2);
|
||||||
|
|
||||||
if (local_time_string.empty()) {
|
if (local_time_string.empty()) {
|
||||||
return {local_time_parameters, *using_colon};
|
return {local_time_parameters, *using_colon, 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
process_optional_colon();
|
process_optional_colon();
|
||||||
@ -287,7 +287,7 @@ std::pair<LocalTimeParameters, bool> ParseLocalTimeParameters(std::string_view l
|
|||||||
local_time_string.remove_prefix(2);
|
local_time_string.remove_prefix(2);
|
||||||
|
|
||||||
if (local_time_string.empty()) {
|
if (local_time_string.empty()) {
|
||||||
return {local_time_parameters, *using_colon};
|
return {local_time_parameters, *using_colon, 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (local_time_string.front() != '.') {
|
if (local_time_string.front() != '.') {
|
||||||
@ -304,7 +304,7 @@ std::pair<LocalTimeParameters, bool> ParseLocalTimeParameters(std::string_view l
|
|||||||
local_time_string.remove_prefix(3);
|
local_time_string.remove_prefix(3);
|
||||||
|
|
||||||
if (local_time_string.empty()) {
|
if (local_time_string.empty()) {
|
||||||
return {local_time_parameters, *using_colon};
|
return {local_time_parameters, *using_colon, 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto maybe_microseconds = ParseNumber<int64_t>(local_time_string, 3);
|
const auto maybe_microseconds = ParseNumber<int64_t>(local_time_string, 3);
|
||||||
@ -315,11 +315,11 @@ std::pair<LocalTimeParameters, bool> ParseLocalTimeParameters(std::string_view l
|
|||||||
local_time_parameters.microsecond = *maybe_microseconds;
|
local_time_parameters.microsecond = *maybe_microseconds;
|
||||||
local_time_string.remove_prefix(3);
|
local_time_string.remove_prefix(3);
|
||||||
|
|
||||||
if (!local_time_string.empty()) {
|
if (!in_zoned_dt && !local_time_string.empty()) {
|
||||||
throw temporal::InvalidArgumentException("Extra characters present at the end of the string.");
|
throw temporal::InvalidArgumentException("Extra characters present at the end of the string.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return {local_time_parameters, *using_colon};
|
return LocalTimeParsing{local_time_parameters, *using_colon, local_time_string.length()};
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalTime::LocalTime(const int64_t microseconds) {
|
LocalTime::LocalTime(const int64_t microseconds) {
|
||||||
@ -403,7 +403,7 @@ size_t LocalTimeHash::operator()(const LocalTime &local_time) const {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
inline constexpr auto *kSupportedLocalDateTimeFormatsHelpMessage = R"help(
|
inline constexpr auto *kSupportedLocalDateTimeFormatsHelpMessage = R"help(
|
||||||
String representing the LocalDateTime should be in one of the following formats:
|
String representing LocalDateTime should be in one of the following formats:
|
||||||
|
|
||||||
- YYYY-MM-DDThh:mm:ss
|
- YYYY-MM-DDThh:mm:ss
|
||||||
- YYYY-MM-DDThh:mm
|
- YYYY-MM-DDThh:mm
|
||||||
@ -436,7 +436,8 @@ It's important to note that the date and time parts should use both the correspo
|
|||||||
or both parts should be written in their basic forms without the separators.)help";
|
or both parts should be written in their basic forms without the separators.)help";
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
std::pair<DateParameters, LocalTimeParameters> ParseLocalDateTimeParameters(std::string_view string) {
|
|
||||||
|
LocalDateTimeParsing ParseLocalDateTimeParameters(std::string_view string, bool in_zoned_dt) {
|
||||||
auto t_position = string.find('T');
|
auto t_position = string.find('T');
|
||||||
if (t_position == std::string_view::npos) {
|
if (t_position == std::string_view::npos) {
|
||||||
throw temporal::InvalidArgumentException("Invalid LocalDateTime format. {}",
|
throw temporal::InvalidArgumentException("Invalid LocalDateTime format. {}",
|
||||||
@ -455,7 +456,8 @@ std::pair<DateParameters, LocalTimeParameters> ParseLocalDateTimeParameters(std:
|
|||||||
kSupportedLocalDateTimeFormatsHelpMessage);
|
kSupportedLocalDateTimeFormatsHelpMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto [local_time_parameters, extended_time_format] = ParseLocalTimeParameters(local_time_substring);
|
auto [local_time_parameters, extended_time_format, remainder_length] =
|
||||||
|
ParseLocalTimeParameters(local_time_substring, in_zoned_dt);
|
||||||
|
|
||||||
if (extended_date_format ^ extended_time_format) {
|
if (extended_date_format ^ extended_time_format) {
|
||||||
throw temporal::InvalidArgumentException(
|
throw temporal::InvalidArgumentException(
|
||||||
@ -463,7 +465,7 @@ std::pair<DateParameters, LocalTimeParameters> ParseLocalDateTimeParameters(std:
|
|||||||
kSupportedLocalDateTimeFormatsHelpMessage);
|
kSupportedLocalDateTimeFormatsHelpMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {date_parameters, local_time_parameters};
|
return {date_parameters, local_time_parameters, remainder_length};
|
||||||
} catch (const temporal::InvalidArgumentException &e) {
|
} catch (const temporal::InvalidArgumentException &e) {
|
||||||
throw temporal::InvalidArgumentException("Invalid LocalDateTime format. {}",
|
throw temporal::InvalidArgumentException("Invalid LocalDateTime format. {}",
|
||||||
kSupportedLocalDateTimeFormatsHelpMessage);
|
kSupportedLocalDateTimeFormatsHelpMessage);
|
||||||
@ -514,6 +516,252 @@ size_t LocalDateTimeHash::operator()(const LocalDateTime &local_date_time) const
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
inline constexpr auto *kSupportedZonedDateTimeFormatsHelpMessage = R"help(
|
||||||
|
A string representing ZonedDateTime should have the following structure:
|
||||||
|
|
||||||
|
- <DateTime><timezone>
|
||||||
|
|
||||||
|
The <DateTime> substring should use one of the following formats:
|
||||||
|
|
||||||
|
- YYYY-MM-DDThh:mm:ss
|
||||||
|
- YYYY-MM-DDThh:mm
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
- YYYYMMDDThhmmss
|
||||||
|
- YYYYMMDDThhmm
|
||||||
|
- YYYYMMDDThh
|
||||||
|
|
||||||
|
The <timezone> substring should use one of the following formats:
|
||||||
|
|
||||||
|
- Z
|
||||||
|
- ±hh:mm
|
||||||
|
- ±hh:mm[ZoneName]
|
||||||
|
- ±hhmm
|
||||||
|
- ±hhmm[ZoneName]
|
||||||
|
- ±hh
|
||||||
|
- ±hh[ZoneName]
|
||||||
|
- [ZoneName]
|
||||||
|
|
||||||
|
Symbol table:
|
||||||
|
|---|---------|
|
||||||
|
| Y | YEAR |
|
||||||
|
|---|---------|
|
||||||
|
| M | MONTH |
|
||||||
|
|---|---------|
|
||||||
|
| D | DAY |
|
||||||
|
|---|---------|
|
||||||
|
| h | HOURS |
|
||||||
|
|---|---------|
|
||||||
|
| m | MINUTES |
|
||||||
|
|---|---------|
|
||||||
|
| s | SECONDS |
|
||||||
|
|---|---------|
|
||||||
|
| Z | UTC |
|
||||||
|
|---|---------|
|
||||||
|
|
||||||
|
ZoneName is a standardized timezone name from the IANA time zone database, given without quote marks.
|
||||||
|
|
||||||
|
Additionally, seconds can be defined as decimal fractions with 3 or 6 digits after the decimal point.
|
||||||
|
First 3 digits represent milliseconds, while the second 3 digits represent microseconds.
|
||||||
|
|
||||||
|
It's important to note that the date and time parts should use both the corresponding separators
|
||||||
|
or both parts should be written in their basic forms without the separators.)help";
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Timezone ParseTimezoneFromName(std::string_view timezone_string) {
|
||||||
|
auto extract_timezone_name = [](std::string_view &string) {
|
||||||
|
if (string.front() != '[' or string.back() != ']') {
|
||||||
|
throw temporal::InvalidArgumentException("Timezone name is not enclosed by '[' ']'.");
|
||||||
|
}
|
||||||
|
string.remove_prefix(1);
|
||||||
|
string.remove_suffix(1);
|
||||||
|
return string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto *timezone = [&]() {
|
||||||
|
try {
|
||||||
|
return std::chrono::locate_zone(extract_timezone_name(timezone_string));
|
||||||
|
} catch (const std::exception &_) {
|
||||||
|
throw temporal::InvalidArgumentException("Timezone name is not in the IANA time zone database.");
|
||||||
|
}
|
||||||
|
return (const std::chrono::time_zone *)nullptr;
|
||||||
|
}();
|
||||||
|
|
||||||
|
return Timezone(timezone);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<Timezone, uint64_t> ParseTimezoneFromOffset(std::string_view timezone_offset_string) {
|
||||||
|
// Supported formats:
|
||||||
|
// ±hh:mm
|
||||||
|
// ±hhmm
|
||||||
|
// ±hh
|
||||||
|
|
||||||
|
const auto process_optional_colon = [&] {
|
||||||
|
const bool has_colon = timezone_offset_string.front() == ':';
|
||||||
|
if (has_colon) {
|
||||||
|
timezone_offset_string.remove_prefix(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timezone_offset_string.empty()) {
|
||||||
|
throw temporal::InvalidArgumentException("Invalid format for the timezone offset. {}",
|
||||||
|
kSupportedZonedDateTimeFormatsHelpMessage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto compute_offset = [](const char sign, const int64_t hours, const int64_t minutes) {
|
||||||
|
return std::chrono::minutes{(sign == '+' ? 1 : -1) * (60 * hours + minutes)};
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto sign = timezone_offset_string.front();
|
||||||
|
if (!(sign == '+' || sign == '-')) {
|
||||||
|
throw temporal::InvalidArgumentException("The timezone offset starts with either a '+' sign or a '-' sign.");
|
||||||
|
}
|
||||||
|
|
||||||
|
timezone_offset_string.remove_prefix(1);
|
||||||
|
|
||||||
|
const auto maybe_hours = ParseNumber<int64_t>(timezone_offset_string, 2);
|
||||||
|
if (!maybe_hours) {
|
||||||
|
throw temporal::InvalidArgumentException("Invalid hours in the string. {}",
|
||||||
|
kSupportedZonedDateTimeFormatsHelpMessage);
|
||||||
|
}
|
||||||
|
timezone_offset_string.remove_prefix(2);
|
||||||
|
|
||||||
|
if (timezone_offset_string.empty()) {
|
||||||
|
return {Timezone(compute_offset(sign, maybe_hours.value(), 0)), 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timezone_offset_string.front() == '[') {
|
||||||
|
return {Timezone(compute_offset(sign, maybe_hours.value(), 0)), timezone_offset_string.length()};
|
||||||
|
}
|
||||||
|
|
||||||
|
process_optional_colon();
|
||||||
|
|
||||||
|
const auto maybe_minutes = ParseNumber<int64_t>(timezone_offset_string, 2);
|
||||||
|
if (!maybe_minutes) {
|
||||||
|
throw temporal::InvalidArgumentException("Invalid minutes in the string. {}",
|
||||||
|
kSupportedZonedDateTimeFormatsHelpMessage);
|
||||||
|
}
|
||||||
|
timezone_offset_string.remove_prefix(2);
|
||||||
|
|
||||||
|
return {Timezone(compute_offset(sign, maybe_hours.value(), maybe_minutes.value())), timezone_offset_string.length()};
|
||||||
|
}
|
||||||
|
|
||||||
|
ZonedDateTimeParameters ParseZonedDateTimeParameters(std::string_view string) {
|
||||||
|
auto [date_parameters, local_time_parameters, remainder_length] = ParseLocalDateTimeParameters(string, true);
|
||||||
|
string.remove_prefix(string.length() - remainder_length);
|
||||||
|
|
||||||
|
if (string.empty()) {
|
||||||
|
throw temporal::InvalidArgumentException("Timezone is not designated.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.front() == 'Z') {
|
||||||
|
if (string.length() != 1) {
|
||||||
|
throw temporal::InvalidArgumentException("Invalid timezone format. {}",
|
||||||
|
kSupportedZonedDateTimeFormatsHelpMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ZonedDateTimeParameters{
|
||||||
|
.date = date_parameters,
|
||||||
|
.local_time = local_time_parameters,
|
||||||
|
.timezone = Timezone(std::chrono::locate_zone("Etc/UTC")),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.front() == '[') {
|
||||||
|
return ZonedDateTimeParameters{
|
||||||
|
.date = date_parameters,
|
||||||
|
.local_time = local_time_parameters,
|
||||||
|
.timezone = ParseTimezoneFromName(string),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto [timezone_from_offset, maybe_timezone_name_length] = ParseTimezoneFromOffset(string);
|
||||||
|
string.remove_prefix(string.length() - maybe_timezone_name_length);
|
||||||
|
|
||||||
|
if (string.empty()) {
|
||||||
|
return ZonedDateTimeParameters{
|
||||||
|
.date = date_parameters,
|
||||||
|
.local_time = local_time_parameters,
|
||||||
|
.timezone = timezone_from_offset,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto timezone_from_name = ParseTimezoneFromName(string);
|
||||||
|
|
||||||
|
auto unzoned_date_time = std::chrono::sys_time<std::chrono::microseconds>{
|
||||||
|
std::chrono::microseconds{LocalDateTime(date_parameters, local_time_parameters).MicrosecondsSinceEpoch()}};
|
||||||
|
if (timezone_from_name.OffsetInMinutes(unzoned_date_time) !=
|
||||||
|
timezone_from_offset.OffsetInMinutes(unzoned_date_time)) {
|
||||||
|
throw temporal::InvalidArgumentException("The number offset doesn’t match the timezone offset.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ZonedDateTimeParameters{
|
||||||
|
.date = date_parameters,
|
||||||
|
.local_time = local_time_parameters,
|
||||||
|
.timezone = timezone_from_name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ZonedDateTime::ZonedDateTime(const ZonedDateTimeParameters &zoned_date_time_parameters) {
|
||||||
|
auto timezone = zoned_date_time_parameters.timezone;
|
||||||
|
std::chrono::sys_time<std::chrono::microseconds> duration{std::chrono::microseconds(
|
||||||
|
LocalDateTime(zoned_date_time_parameters.date, zoned_date_time_parameters.local_time).MicrosecondsSinceEpoch())};
|
||||||
|
zoned_time = std::chrono::zoned_time(timezone, duration);
|
||||||
|
// TODO antepusic if time ambiguous, e.g. when 3->2 AM, round up/down?
|
||||||
|
}
|
||||||
|
|
||||||
|
ZonedDateTime::ZonedDateTime(const ZonedDateTime &zoned_date_time) : zoned_time(zoned_date_time.zoned_time) {}
|
||||||
|
|
||||||
|
ZonedDateTime::ZonedDateTime(const std::chrono::zoned_time<std::chrono::microseconds, Timezone> &zoned_time)
|
||||||
|
: zoned_time(zoned_time) {}
|
||||||
|
|
||||||
|
int64_t ZonedDateTime::MicrosecondsSinceEpoch() const { return zoned_time.get_sys_time().time_since_epoch().count(); }
|
||||||
|
|
||||||
|
int64_t ZonedDateTime::SecondsSinceEpoch() const {
|
||||||
|
return std::chrono::duration_cast<std::chrono::seconds>(zoned_time.get_sys_time().time_since_epoch()).count();
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t ZonedDateTime::SubSecondsAsNanoseconds() const {
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
const auto time_since_epoch = zoned_time.get_sys_time().time_since_epoch();
|
||||||
|
const auto full_seconds = std::chrono::duration_cast<std::chrono::seconds>(time_since_epoch);
|
||||||
|
|
||||||
|
return (time_since_epoch - full_seconds).count();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ZonedDateTime::ToString() const {
|
||||||
|
const auto without_timezone_name = std::format("{0:%Y}-{0:%m}-{0:%d}T{0:%H}:{0:%M}:{0:%S}{0:%Ez}", zoned_time);
|
||||||
|
const auto timezone_name = zoned_time.get_time_zone().TimezoneName();
|
||||||
|
|
||||||
|
if (timezone_name.empty()) {
|
||||||
|
return without_timezone_name;
|
||||||
|
}
|
||||||
|
return std::format("{0}[{1}]", without_timezone_name, timezone_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ZonedDateTime::operator==(const ZonedDateTime &other) const {
|
||||||
|
return MicrosecondsSinceEpoch() == other.MicrosecondsSinceEpoch() && OffsetInMinutes() == other.OffsetInMinutes() &&
|
||||||
|
TimezoneName() == other.TimezoneName();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::strong_ordering ZonedDateTime::operator<=>(const ZonedDateTime &other) const {
|
||||||
|
const auto duration_ordering = MicrosecondsSinceEpoch() <=> other.MicrosecondsSinceEpoch();
|
||||||
|
if (duration_ordering != 0) {
|
||||||
|
return duration_ordering;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto offset_ordering = OffsetInMinutes() <=> other.OffsetInMinutes();
|
||||||
|
if (offset_ordering != 0) {
|
||||||
|
return offset_ordering;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto timezone_name_ordering = TimezoneName() <=> other.TimezoneName();
|
||||||
|
return timezone_name_ordering;
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
std::optional<DurationParameters> TryParseDurationString(std::string_view string) {
|
std::optional<DurationParameters> TryParseDurationString(std::string_view string) {
|
||||||
DurationParameters duration_parameters;
|
DurationParameters duration_parameters;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 Memgraph Ltd.
|
// Copyright 2024 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// Use of this software is governed by the Business Source License
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@ -22,6 +22,8 @@
|
|||||||
|
|
||||||
namespace memgraph::utils {
|
namespace memgraph::utils {
|
||||||
|
|
||||||
|
class Timezone;
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
concept Chrono = requires(T) {
|
concept Chrono = requires(T) {
|
||||||
typename T::rep;
|
typename T::rep;
|
||||||
@ -192,8 +194,13 @@ struct LocalTimeParameters {
|
|||||||
bool operator==(const LocalTimeParameters &) const = default;
|
bool operator==(const LocalTimeParameters &) const = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
// boolean indicates whether the parsed string was in extended format
|
struct LocalTimeParsing {
|
||||||
std::pair<LocalTimeParameters, bool> ParseLocalTimeParameters(std::string_view string);
|
LocalTimeParameters parameters;
|
||||||
|
bool extended_format;
|
||||||
|
uint64_t remainder_length;
|
||||||
|
};
|
||||||
|
|
||||||
|
LocalTimeParsing ParseLocalTimeParameters(std::string_view string, bool in_zoned_dt = false);
|
||||||
|
|
||||||
struct LocalTime {
|
struct LocalTime {
|
||||||
explicit LocalTime() : LocalTime{LocalTimeParameters{}} {}
|
explicit LocalTime() : LocalTime{LocalTimeParameters{}} {}
|
||||||
@ -251,7 +258,13 @@ struct LocalTimeHash {
|
|||||||
size_t operator()(const LocalTime &local_time) const;
|
size_t operator()(const LocalTime &local_time) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::pair<DateParameters, LocalTimeParameters> ParseLocalDateTimeParameters(std::string_view string);
|
struct LocalDateTimeParsing {
|
||||||
|
DateParameters date_parameters;
|
||||||
|
LocalTimeParameters local_time_parameters;
|
||||||
|
uint64_t remainder_length;
|
||||||
|
};
|
||||||
|
|
||||||
|
LocalDateTimeParsing ParseLocalDateTimeParameters(std::string_view string, bool in_zoned_dt = false);
|
||||||
|
|
||||||
struct LocalDateTime {
|
struct LocalDateTime {
|
||||||
explicit LocalDateTime(int64_t microseconds);
|
explicit LocalDateTime(int64_t microseconds);
|
||||||
@ -294,6 +307,132 @@ struct LocalDateTimeHash {
|
|||||||
size_t operator()(const LocalDateTime &local_date_time) const;
|
size_t operator()(const LocalDateTime &local_date_time) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class Timezone {
|
||||||
|
private:
|
||||||
|
std::variant<std::chrono::minutes, const std::chrono::time_zone *> offset_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Timezone(const std::chrono::minutes offset) : offset_{offset} {}
|
||||||
|
explicit Timezone(const std::chrono::time_zone *timezone) : offset_{timezone} {}
|
||||||
|
explicit Timezone(const std::string &timezone_name) : offset_{std::chrono::locate_zone(timezone_name)} {}
|
||||||
|
|
||||||
|
const Timezone *operator->() const { return this; }
|
||||||
|
|
||||||
|
bool operator==(const Timezone &) const = default;
|
||||||
|
|
||||||
|
template <class DurationT>
|
||||||
|
std::chrono::minutes OffsetInMinutes(std::chrono::sys_time<DurationT> time_point) const {
|
||||||
|
if (std::holds_alternative<std::chrono::minutes>(offset_)) {
|
||||||
|
return std::get<std::chrono::minutes>(offset_);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::chrono::duration_cast<std::chrono::minutes>(
|
||||||
|
std::get<const std::chrono::time_zone *>(offset_)->get_info(time_point).offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class DurationT>
|
||||||
|
std::chrono::sys_info get_info(std::chrono::sys_time<DurationT> time_point) const {
|
||||||
|
if (std::holds_alternative<std::chrono::minutes>(offset_)) {
|
||||||
|
const auto offset = std::get<std::chrono::minutes>(offset_);
|
||||||
|
return std::chrono::sys_info{
|
||||||
|
.begin = std::chrono::sys_seconds::min(),
|
||||||
|
.end = std::chrono::sys_seconds::max(),
|
||||||
|
.offset = std::chrono::duration_cast<std::chrono::seconds>(offset),
|
||||||
|
.save = std::chrono::minutes{0},
|
||||||
|
.abbrev = "",
|
||||||
|
// offset >= std::chrono::minutes{0} ? "+" + std::format("{0:%H}:{0:%M}", offset)
|
||||||
|
// : "-" + std::format("{0:%H}:{0:%M}", -offset)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return std::get<const std::chrono::time_zone *>(offset_)->get_info(time_point);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class DurationT>
|
||||||
|
auto to_local(std::chrono::sys_time<DurationT> time_point) const {
|
||||||
|
if (std::holds_alternative<std::chrono::minutes>(offset_)) {
|
||||||
|
using local_time = std::chrono::local_time<std::common_type_t<DurationT, std::chrono::minutes>>;
|
||||||
|
return local_time{(time_point + OffsetInMinutes(time_point)).time_since_epoch()};
|
||||||
|
}
|
||||||
|
return std::get<const std::chrono::time_zone *>(offset_)->to_local(time_point);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class DurationT>
|
||||||
|
auto to_sys(std::chrono::local_time<DurationT> time_point) const {
|
||||||
|
if (std::holds_alternative<std::chrono::minutes>(offset_)) {
|
||||||
|
using sys_time = std::chrono::sys_time<std::common_type_t<DurationT, std::chrono::minutes>>;
|
||||||
|
return sys_time{(time_point - std::get<std::chrono::minutes>(offset_)).time_since_epoch()};
|
||||||
|
}
|
||||||
|
return std::get<const std::chrono::time_zone *>(offset_)->to_sys(time_point);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view TimezoneName() const {
|
||||||
|
if (std::holds_alternative<std::chrono::minutes>(offset_)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return std::get<const std::chrono::time_zone *>(offset_)->name();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace memgraph::utils
|
||||||
|
|
||||||
|
namespace std::chrono {
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct zoned_traits<memgraph::utils::Timezone> {
|
||||||
|
static memgraph::utils::Timezone default_zone() { return memgraph::utils::Timezone{minutes{0}}; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace std::chrono
|
||||||
|
|
||||||
|
namespace memgraph::utils {
|
||||||
|
|
||||||
|
struct ZonedDateTimeParameters {
|
||||||
|
DateParameters date;
|
||||||
|
LocalTimeParameters local_time;
|
||||||
|
Timezone timezone;
|
||||||
|
|
||||||
|
bool operator==(const ZonedDateTimeParameters &) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
ZonedDateTimeParameters ParseZonedDateTimeParameters(std::string_view string);
|
||||||
|
|
||||||
|
struct ZonedDateTime {
|
||||||
|
explicit ZonedDateTime(const ZonedDateTimeParameters &zoned_date_time_parameters);
|
||||||
|
explicit ZonedDateTime(const ZonedDateTime &zoned_date_time);
|
||||||
|
explicit ZonedDateTime(const std::chrono::zoned_time<std::chrono::microseconds, Timezone> &zoned_time);
|
||||||
|
|
||||||
|
int64_t MicrosecondsSinceEpoch() const;
|
||||||
|
int64_t SecondsSinceEpoch() const;
|
||||||
|
int64_t SubSecondsAsNanoseconds() const;
|
||||||
|
std::string ToString() const;
|
||||||
|
|
||||||
|
bool operator==(const ZonedDateTime &other) const;
|
||||||
|
|
||||||
|
std::strong_ordering operator<=>(const ZonedDateTime &other) const;
|
||||||
|
|
||||||
|
std::chrono::minutes OffsetInMinutes() const {
|
||||||
|
return zoned_time.get_time_zone().OffsetInMinutes(zoned_time.get_sys_time());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view TimezoneName() const { return zoned_time.get_time_zone().TimezoneName(); }
|
||||||
|
|
||||||
|
friend std::ostream &operator<<(std::ostream &os, const ZonedDateTime &zdt) { return os << zdt.ToString(); }
|
||||||
|
|
||||||
|
friend ZonedDateTime operator+(const ZonedDateTime &zdt, const Duration &dur) {
|
||||||
|
return ZonedDateTime(std::chrono::zoned_time(
|
||||||
|
zdt.zoned_time.get_time_zone(), zdt.zoned_time.get_sys_time() + std::chrono::microseconds(dur.microseconds)));
|
||||||
|
}
|
||||||
|
|
||||||
|
friend ZonedDateTime operator+(const Duration &dur, const ZonedDateTime &zdt) { return zdt + dur; }
|
||||||
|
|
||||||
|
friend ZonedDateTime operator-(const ZonedDateTime &zdt, const Duration &dur) { return zdt + (-dur); }
|
||||||
|
|
||||||
|
friend Duration operator-(const ZonedDateTime &lhs, const ZonedDateTime &rhs) {
|
||||||
|
return Duration(lhs.MicrosecondsSinceEpoch()) - Duration(rhs.MicrosecondsSinceEpoch());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::chrono::zoned_time<std::chrono::microseconds, Timezone> zoned_time;
|
||||||
|
};
|
||||||
|
|
||||||
Date CurrentDate();
|
Date CurrentDate();
|
||||||
LocalTime CurrentLocalTime();
|
LocalTime CurrentLocalTime();
|
||||||
LocalDateTime CurrentLocalDateTime();
|
LocalDateTime CurrentLocalDateTime();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
// Copyright 2024 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// Use of this software is governed by the Business Source License
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@ -127,7 +127,7 @@ void TestLocalTime(mg::Client &client) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const auto parse = [](const std::string_view query) {
|
const auto parse = [](const std::string_view query) {
|
||||||
return memgraph::utils::LocalTime(memgraph::utils::ParseLocalTimeParameters(fmt::format("{}", query)).first);
|
return memgraph::utils::LocalTime(memgraph::utils::ParseLocalTimeParameters(fmt::format("{}", query)).parameters);
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto str1 = lt(1, 3, 3, 33);
|
const auto str1 = lt(1, 3, 3, 33);
|
||||||
@ -154,7 +154,7 @@ void TestLocalDateTime(mg::Client &client) {
|
|||||||
return fmt::format("{:0>2}-{:0>2}-{:0>2}T{:0>2}:{:0>2}:{:0>2}", y, mo, d, h, m, s);
|
return fmt::format("{:0>2}-{:0>2}-{:0>2}T{:0>2}:{:0>2}:{:0>2}", y, mo, d, h, m, s);
|
||||||
};
|
};
|
||||||
auto parse = [](const std::string_view str) {
|
auto parse = [](const std::string_view str) {
|
||||||
const auto [dt, lt] = memgraph::utils::ParseLocalDateTimeParameters(str);
|
const auto [dt, lt, _] = memgraph::utils::ParseLocalDateTimeParameters(str);
|
||||||
return memgraph::utils::LocalDateTime(dt, lt);
|
return memgraph::utils::LocalDateTime(dt, lt);
|
||||||
};
|
};
|
||||||
auto ldt_query = [](const std::string_view str) { return fmt::format("\"{}\"", str); };
|
auto ldt_query = [](const std::string_view str) { return fmt::format("\"{}\"", str); };
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 Memgraph Ltd.
|
// Copyright 2024 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// Use of this software is governed by the Business Source License
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@ -10,6 +10,7 @@
|
|||||||
// licenses/APL.txt.
|
// licenses/APL.txt.
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <format>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
@ -61,6 +62,11 @@ inline constexpr std::array test_local_times{TestLocalTimeParameters{{.hour = 24
|
|||||||
TestLocalTimeParameters{{.microsecond = 1000}, true},
|
TestLocalTimeParameters{{.microsecond = 1000}, true},
|
||||||
TestLocalTimeParameters{{23, 59, 59, 999, 999}, false},
|
TestLocalTimeParameters{{23, 59, 59, 999, 999}, false},
|
||||||
TestLocalTimeParameters{{0, 0, 0, 0, 0}, false}};
|
TestLocalTimeParameters{{0, 0, 0, 0, 0}, false}};
|
||||||
|
|
||||||
|
// const std::chrono::sys_time LocalDateTimeToSysTime(const memgraph::utils::DateParameters &date_parameters,
|
||||||
|
// const memgraph::utils::LocalTimeParameters &local_time_parameters)
|
||||||
|
// {}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST(TemporalTest, DateConstruction) {
|
TEST(TemporalTest, DateConstruction) {
|
||||||
@ -180,6 +186,30 @@ TEST(TemporalTest, LocalDateTimeMicrosecondsSinceEpochConversion) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(TemporalTest, ZonedDateTimeMicrosecondsSinceEpochConversion) {
|
||||||
|
using namespace memgraph::utils;
|
||||||
|
|
||||||
|
const auto date_parameters = DateParameters{2024, 3, 22};
|
||||||
|
const auto local_time_parameters = LocalTimeParameters{12, 06, 03, 500, 500};
|
||||||
|
|
||||||
|
const auto local_date_time = LocalDateTime{date_parameters, local_time_parameters};
|
||||||
|
|
||||||
|
std::array timezone_offsets{
|
||||||
|
std::chrono::minutes{0}, std::chrono::minutes{60}, std::chrono::minutes{75}, std::chrono::minutes{90},
|
||||||
|
std::chrono::minutes{-60}, std::chrono::minutes{-75}, std::chrono::minutes{-90},
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto check_faulty_timezones = [&date_parameters, &local_time_parameters, &local_date_time](const auto &cases) {
|
||||||
|
for (const auto &timezone_offset : cases) {
|
||||||
|
const auto zdt = ZonedDateTime({date_parameters, local_time_parameters, Timezone(timezone_offset)});
|
||||||
|
|
||||||
|
EXPECT_EQ(zdt.MicrosecondsSinceEpoch(),
|
||||||
|
local_date_time.MicrosecondsSinceEpoch() +
|
||||||
|
std::chrono::duration_cast<std::chrono::microseconds>(timezone_offset).count());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
TEST(TemporalTest, DurationConversion) {
|
TEST(TemporalTest, DurationConversion) {
|
||||||
{
|
{
|
||||||
memgraph::utils::Duration duration{{.minute = 123.25}};
|
memgraph::utils::Duration duration{{.minute = 123.25}};
|
||||||
@ -260,17 +290,17 @@ TEST(TemporalTest, DateParsing) {
|
|||||||
|
|
||||||
TEST(TemporalTest, LocalTimeParsing) {
|
TEST(TemporalTest, LocalTimeParsing) {
|
||||||
for (const auto &[string, local_time_parameters] : parsing_test_local_time_extended) {
|
for (const auto &[string, local_time_parameters] : parsing_test_local_time_extended) {
|
||||||
ASSERT_EQ(memgraph::utils::ParseLocalTimeParameters(string).first, local_time_parameters)
|
ASSERT_EQ(memgraph::utils::ParseLocalTimeParameters(string).parameters, local_time_parameters)
|
||||||
<< ToString(local_time_parameters);
|
<< ToString(local_time_parameters);
|
||||||
const auto time_string = fmt::format("T{}", string);
|
const auto time_string = fmt::format("T{}", string);
|
||||||
ASSERT_EQ(memgraph::utils::ParseLocalTimeParameters(time_string).first, local_time_parameters)
|
ASSERT_EQ(memgraph::utils::ParseLocalTimeParameters(time_string).parameters, local_time_parameters)
|
||||||
<< ToString(local_time_parameters);
|
<< ToString(local_time_parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto &[string, local_time_parameters] : parsing_test_local_time_basic) {
|
for (const auto &[string, local_time_parameters] : parsing_test_local_time_basic) {
|
||||||
ASSERT_EQ(memgraph::utils::ParseLocalTimeParameters(string).first, local_time_parameters)
|
ASSERT_EQ(memgraph::utils::ParseLocalTimeParameters(string).parameters, local_time_parameters)
|
||||||
<< ToString(local_time_parameters);
|
<< ToString(local_time_parameters);
|
||||||
ASSERT_EQ(memgraph::utils::ParseLocalTimeParameters(fmt::format("T{}", string)).first, local_time_parameters)
|
ASSERT_EQ(memgraph::utils::ParseLocalTimeParameters(fmt::format("T{}", string)).parameters, local_time_parameters)
|
||||||
<< ToString(local_time_parameters);
|
<< ToString(local_time_parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,8 +314,9 @@ TEST(TemporalTest, LocalDateTimeParsing) {
|
|||||||
for (const auto &[local_time_string, local_time_parameters] : local_times) {
|
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);
|
const auto local_date_time_string = fmt::format("{}T{}", date_string, local_time_string);
|
||||||
if (is_valid) {
|
if (is_valid) {
|
||||||
EXPECT_EQ(memgraph::utils::ParseLocalDateTimeParameters(local_date_time_string),
|
const auto parsed = memgraph::utils::ParseLocalDateTimeParameters(local_date_time_string);
|
||||||
(std::pair{date_parameters, local_time_parameters}));
|
EXPECT_EQ(parsed.date_parameters, date_parameters);
|
||||||
|
EXPECT_EQ(parsed.local_time_parameters, local_time_parameters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -297,6 +328,71 @@ TEST(TemporalTest, LocalDateTimeParsing) {
|
|||||||
check_local_date_time_combinations(parsing_test_dates_extended, parsing_test_local_time_basic, false);
|
check_local_date_time_combinations(parsing_test_dates_extended, parsing_test_local_time_basic, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(TemporalTest, ZonedDateTimeParsing) {
|
||||||
|
// The ZonedDateTime format is the LocalDateTime format & the timezone designation. As the first part is parsed with
|
||||||
|
// the existing LocalDateTime parser, the LocalDateTime data is shared and the test cases focus on timezone parsing.
|
||||||
|
|
||||||
|
using namespace memgraph::utils;
|
||||||
|
|
||||||
|
const auto shared_date_time = "2020-11-22T19:23:21.123456"sv;
|
||||||
|
const auto shared_expected_date_params = DateParameters{2020, 11, 22};
|
||||||
|
const auto shared_expected_local_time_params = LocalTimeParameters{19, 23, 21, 123, 456};
|
||||||
|
|
||||||
|
std::array timezone_parsing_cases{
|
||||||
|
std::make_pair("Z"sv, Timezone("Etc/UTC")),
|
||||||
|
std::make_pair("+01:00"sv, Timezone(std::chrono::minutes{60})),
|
||||||
|
std::make_pair("+01:00[Europe/Zagreb]"sv, Timezone("Europe/Zagreb")),
|
||||||
|
std::make_pair("-08:00"sv, Timezone(std::chrono::minutes{-480})),
|
||||||
|
std::make_pair("-08:00[America/Los_Angeles]"sv, Timezone("America/Los_Angeles")),
|
||||||
|
std::make_pair("+0100"sv, Timezone(std::chrono::minutes{60})),
|
||||||
|
std::make_pair("+0100[Europe/Zagreb]"sv, Timezone("Europe/Zagreb")),
|
||||||
|
std::make_pair("-0800"sv, Timezone(std::chrono::minutes{-480})),
|
||||||
|
std::make_pair("-0800[America/Los_Angeles]"sv, Timezone("America/Los_Angeles")),
|
||||||
|
std::make_pair("+01"sv, Timezone(std::chrono::minutes{60})),
|
||||||
|
std::make_pair("+01[Europe/Zagreb]"sv, Timezone("Europe/Zagreb")),
|
||||||
|
std::make_pair("-08"sv, Timezone(std::chrono::minutes{-480})),
|
||||||
|
std::make_pair("-08[America/Los_Angeles]"sv, Timezone("America/Los_Angeles")),
|
||||||
|
std::make_pair("[Europe/Zagreb]"sv, Timezone("Europe/Zagreb")),
|
||||||
|
std::make_pair("[US/Pacific]"sv, Timezone("America/Los_Angeles")), // US/Pacific links to America/Los_Angeles
|
||||||
|
std::make_pair("[GMT]"sv, Timezone("GMT")),
|
||||||
|
};
|
||||||
|
|
||||||
|
std::array faulty_timezone_cases{
|
||||||
|
"Z_extra_text"sv,
|
||||||
|
"+01:00_extra_text"sv,
|
||||||
|
"+01:00[Europe/Zagreb]_extra_text"sv,
|
||||||
|
"+01:00[America/Los_Angeles]"sv,
|
||||||
|
"+01:00[America/Los_Angeles"sv,
|
||||||
|
"+01:00[nonexistent/timezone]"sv,
|
||||||
|
"+01.44"sv,
|
||||||
|
"01:00"sv,
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto join_strings = [](const auto &date_time, const auto &timezone) {
|
||||||
|
return std::format("{0}{1}", date_time, timezone);
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto check_timezone_parsing_cases = [&shared_date_time, &shared_expected_date_params,
|
||||||
|
&shared_expected_local_time_params, &join_strings](const auto &cases) {
|
||||||
|
for (const auto &[timezone_string, timezone_parameter] : cases) {
|
||||||
|
auto zdt_string = join_strings(shared_date_time, timezone_string);
|
||||||
|
auto zdt_parameters =
|
||||||
|
ZonedDateTimeParameters{shared_expected_date_params, shared_expected_local_time_params, timezone_parameter};
|
||||||
|
EXPECT_EQ(ParseZonedDateTimeParameters(zdt_string), zdt_parameters);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto check_faulty_timezones = [&shared_date_time, &join_strings](const auto &cases) {
|
||||||
|
for (const auto &timezone_string : cases) {
|
||||||
|
auto zdt_string = join_strings(shared_date_time, timezone_string);
|
||||||
|
EXPECT_ANY_THROW(ParseZonedDateTimeParameters(zdt_string));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
check_timezone_parsing_cases(timezone_parsing_cases);
|
||||||
|
check_faulty_timezones(faulty_timezone_cases);
|
||||||
|
}
|
||||||
|
|
||||||
void CheckDurationParameters(const auto &values, const auto &expected) {
|
void CheckDurationParameters(const auto &values, const auto &expected) {
|
||||||
ASSERT_EQ(values.day, expected.day);
|
ASSERT_EQ(values.day, expected.day);
|
||||||
ASSERT_EQ(values.hour, expected.hour);
|
ASSERT_EQ(values.hour, expected.hour);
|
||||||
@ -412,6 +508,16 @@ TEST(TemporalTest, PrintLocalDateTime) {
|
|||||||
ASSERT_EQ(stream.view(), "1970-01-01T13:02:40.100050");
|
ASSERT_EQ(stream.view(), "1970-01-01T13:02:40.100050");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(TemporalTest, PrintZonedDateTime) {
|
||||||
|
using namespace memgraph::utils;
|
||||||
|
|
||||||
|
const auto zdt = ZonedDateTime({{1970, 1, 1}, {13, 2, 40, 100, 50}, Timezone("Europe/Zagreb")});
|
||||||
|
std::ostringstream stream;
|
||||||
|
stream << zdt;
|
||||||
|
ASSERT_TRUE(stream);
|
||||||
|
ASSERT_EQ(stream.view(), "1970-01-01T14:02:40.100050+01:00[Europe/Zagreb]");
|
||||||
|
}
|
||||||
|
|
||||||
TEST(TemporalTest, DurationAddition) {
|
TEST(TemporalTest, DurationAddition) {
|
||||||
// a >= 0 && b >= 0
|
// a >= 0 && b >= 0
|
||||||
const auto zero = memgraph::utils::Duration(0);
|
const auto zero = memgraph::utils::Duration(0);
|
||||||
@ -620,6 +726,18 @@ TEST(TemporalTest, LocalDateTimeAdditionSubtraction) {
|
|||||||
memgraph::utils::BasicException);
|
memgraph::utils::BasicException);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(TemporalTest, ZonedDateTimeAdditionSubtraction) {
|
||||||
|
using namespace memgraph::utils;
|
||||||
|
|
||||||
|
const auto zdt = ZonedDateTime({{2024, 3, 22}, {12, 00, 00}, Timezone("Europe/Zagreb")});
|
||||||
|
const auto one_day = Duration({.day = 1});
|
||||||
|
|
||||||
|
EXPECT_EQ(zdt + one_day, ZonedDateTime({{2024, 3, 23}, {12, 00, 00}, Timezone("Europe/Zagreb")}));
|
||||||
|
EXPECT_EQ(one_day + zdt, ZonedDateTime({{2024, 3, 23}, {12, 00, 00}, Timezone("Europe/Zagreb")}));
|
||||||
|
|
||||||
|
EXPECT_EQ(zdt - one_day, ZonedDateTime({{2024, 3, 21}, {12, 00, 00}, Timezone("Europe/Zagreb")}));
|
||||||
|
}
|
||||||
|
|
||||||
TEST(TemporalTest, LocalDateTimeDelta) {
|
TEST(TemporalTest, LocalDateTimeDelta) {
|
||||||
const auto unix_epoch = memgraph::utils::LocalDateTime({1970, 1, 1}, {1, 1, 1});
|
const auto unix_epoch = memgraph::utils::LocalDateTime({1970, 1, 1}, {1, 1, 1});
|
||||||
const auto one_year_after_unix_epoch = memgraph::utils::LocalDateTime({1971, 2, 1}, {12, 1, 1});
|
const auto one_year_after_unix_epoch = memgraph::utils::LocalDateTime({1971, 2, 1}, {12, 1, 1});
|
||||||
@ -630,6 +748,16 @@ TEST(TemporalTest, LocalDateTimeDelta) {
|
|||||||
memgraph::utils::Duration({.day = 761, .millisecond = 20, .microsecond = 34}));
|
memgraph::utils::Duration({.day = 761, .millisecond = 20, .microsecond = 34}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(TemporalTest, ZonedDateTimeDelta) {
|
||||||
|
using namespace memgraph::utils;
|
||||||
|
|
||||||
|
const auto zdt = ZonedDateTime({{2024, 3, 22}, {12, 00, 00}, Timezone("Europe/Zagreb")});
|
||||||
|
const auto zdt_plus_time = ZonedDateTime({{2024, 3, 25}, {14, 18, 13, 206, 22}, Timezone("Europe/Zagreb")});
|
||||||
|
|
||||||
|
EXPECT_EQ(zdt_plus_time - zdt,
|
||||||
|
Duration({.day = 3, .hour = 2, .minute = 18, .second = 13, .millisecond = 206, .microsecond = 22}));
|
||||||
|
}
|
||||||
|
|
||||||
TEST(TemporalTest, DateConvertsToString) {
|
TEST(TemporalTest, DateConvertsToString) {
|
||||||
const auto date1 = memgraph::utils::Date({1970, 1, 2});
|
const auto date1 = memgraph::utils::Date({1970, 1, 2});
|
||||||
const std::string date1_expected_str = "1970-01-02";
|
const std::string date1_expected_str = "1970-01-02";
|
||||||
@ -672,6 +800,26 @@ TEST(TemporalTest, LocalDateTimeConvertsToString) {
|
|||||||
ASSERT_EQ(ldt3_expected_str, ldt3.ToString());
|
ASSERT_EQ(ldt3_expected_str, ldt3.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(TemporalTest, ZonedDateTimeConvertsToString) {
|
||||||
|
using namespace memgraph::utils;
|
||||||
|
|
||||||
|
const auto zdt1 = ZonedDateTime({{1970, 1, 2}, {23, 02, 59}, Timezone("Europe/Zagreb")});
|
||||||
|
const std::string zdt1_expected_str = "1970-01-03T00:02:59.000000+01:00[Europe/Zagreb]";
|
||||||
|
const auto zdt2 = ZonedDateTime({{1970, 1, 2}, {23, 02, 59, 456, 123}, Timezone("Europe/Zagreb")});
|
||||||
|
const std::string zdt2_expected_str = "1970-01-03T00:02:59.456123+01:00[Europe/Zagreb]";
|
||||||
|
const auto zdt3 = ZonedDateTime({{1997, 8, 24}, {16, 32, 9}, Timezone("US/Pacific")});
|
||||||
|
const std::string zdt3_expected_str = "1997-08-24T09:32:09.000000-07:00[America/Los_Angeles]";
|
||||||
|
// expected (US/Pacific links to America/Los_Angeles, see
|
||||||
|
// https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List)
|
||||||
|
const auto zdt4 = ZonedDateTime({{1997, 8, 24}, {16, 32, 9}, Timezone(std::chrono::minutes(90))});
|
||||||
|
const std::string zdt4_expected_str = "1997-08-24T18:02:09.000000+01:30";
|
||||||
|
|
||||||
|
ASSERT_EQ(zdt1_expected_str, zdt1.ToString());
|
||||||
|
ASSERT_EQ(zdt2_expected_str, zdt2.ToString());
|
||||||
|
ASSERT_EQ(zdt3_expected_str, zdt3.ToString());
|
||||||
|
ASSERT_EQ(zdt4_expected_str, zdt4.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
TEST(TemporalTest, DurationConvertsToString) {
|
TEST(TemporalTest, DurationConvertsToString) {
|
||||||
memgraph::utils::Duration duration1{{.minute = 2, .second = 2, .microsecond = 33}};
|
memgraph::utils::Duration duration1{{.minute = 2, .second = 2, .microsecond = 33}};
|
||||||
const std::string duration1_expected_str = "P0DT0H2M2.000033S";
|
const std::string duration1_expected_str = "P0DT0H2M2.000033S";
|
||||||
|
Loading…
Reference in New Issue
Block a user