Add support for parsing temporal types (#187)

This commit is contained in:
antonio2368 2021-07-14 14:40:47 +02:00 committed by Antonio Andelic
parent e628b5ba6e
commit 4e604de9d7
12 changed files with 1047 additions and 426 deletions

View File

@ -1,5 +1,6 @@
---
Checks: '*,
-abseil-string-find-str-contains,
-altera-struct-pack-align,
-android-*,
-cert-err58-cpp,

View File

@ -33,7 +33,6 @@ set(mg_query_sources
procedure/py_module.cpp
serialization/property_value.cpp
streams.cpp
temporal.cpp
trigger.cpp
trigger_context.cpp
typed_value.cpp)

View File

@ -1,198 +0,0 @@
#include "query/temporal.hpp"
#include <chrono>
#include "utils/exceptions.hpp"
#include "utils/fnv.hpp"
namespace query {
namespace {
template <typename T>
concept Chrono = requires(T) {
typename T::rep;
typename T::period;
};
template <Chrono TFirst, Chrono TSecond>
constexpr auto GetAndSubtractDuration(TSecond &base_duration) {
const auto duration = std::chrono::duration_cast<TFirst>(base_duration);
base_duration -= duration;
return duration.count();
}
constexpr std::chrono::microseconds epoch{std::chrono::years{1970} + std::chrono::months{1} + std::chrono::days{1}};
constexpr bool IsInBounds(const auto low, const auto high, const auto value) { return low <= value && value <= high; }
constexpr bool IsValidDay(const auto day, const auto month, const auto year) {
constexpr std::array<uint8_t, 12> days{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
MG_ASSERT(IsInBounds(1, 12, month), "Invalid month!");
if (day <= 0) {
return false;
}
const auto is_leap_year = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
uint8_t leap_day = month == 2 && is_leap_year;
return day <= (days[month - 1] + leap_day);
}
} // namespace
Date::Date(const int64_t microseconds) {
auto chrono_microseconds = std::chrono::microseconds(microseconds);
chrono_microseconds += epoch;
MG_ASSERT(chrono_microseconds.count() >= 0, "Invalid Date specified in microseconds");
years = GetAndSubtractDuration<std::chrono::years>(chrono_microseconds);
months = GetAndSubtractDuration<std::chrono::months>(chrono_microseconds);
days = GetAndSubtractDuration<std::chrono::days>(chrono_microseconds);
}
Date::Date(const DateParameters &date_parameters) {
if (!IsInBounds(0, 9999, date_parameters.years)) {
throw utils::BasicException("Creating a Date with invalid year parameter.");
}
// TODO(antonio2368): Replace with year_month_day when it's implemented
// https://en.cppreference.com/w/cpp/chrono/year_month_day/ok
if (!IsInBounds(1, 12, date_parameters.months)) {
throw utils::BasicException("Creating a Date with invalid month parameter.");
}
if (!IsValidDay(date_parameters.days, date_parameters.months, date_parameters.years)) {
throw utils::BasicException("Creating a Date with invalid day parameter.");
}
years = date_parameters.years;
months = date_parameters.months;
days = date_parameters.days;
}
int64_t Date::MicrosecondsSinceEpoch() const {
auto result = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::years{years} + std::chrono::months{months} + std::chrono::days{days});
result -= epoch;
return result.count();
}
size_t DateHash::operator()(const Date &date) const {
utils::HashCombine<uint64_t, uint64_t> hasher;
size_t result = hasher(0, date.years);
result = hasher(result, date.months);
result = hasher(result, date.days);
return result;
}
LocalTime::LocalTime(const int64_t microseconds) {
auto chrono_microseconds = std::chrono::microseconds(microseconds);
MG_ASSERT(chrono_microseconds.count() >= 0, "Negative LocalTime specified in microseconds");
const auto parsed_hours = GetAndSubtractDuration<std::chrono::hours>(chrono_microseconds);
MG_ASSERT(parsed_hours <= 23, "Invalid LocalTime specified in microseconds");
hours = parsed_hours;
minutes = GetAndSubtractDuration<std::chrono::minutes>(chrono_microseconds);
seconds = GetAndSubtractDuration<std::chrono::seconds>(chrono_microseconds);
milliseconds = GetAndSubtractDuration<std::chrono::milliseconds>(chrono_microseconds);
this->microseconds = chrono_microseconds.count();
}
LocalTime::LocalTime(const LocalTimeParameters &local_time_parameters) {
if (!IsInBounds(0, 23, local_time_parameters.hours)) {
throw utils::BasicException("Creating a LocalTime with invalid hour parameter.");
}
if (!IsInBounds(0, 59, local_time_parameters.minutes)) {
throw utils::BasicException("Creating a LocalTime with invalid minutes parameter.");
}
if (!IsInBounds(0, 59, local_time_parameters.seconds)) {
throw utils::BasicException("Creating a LocalTime with invalid seconds parameter.");
}
if (!IsInBounds(0, 999, local_time_parameters.milliseconds)) {
throw utils::BasicException("Creating a LocalTime with invalid seconds parameter.");
}
if (!IsInBounds(0, 999, local_time_parameters.microseconds)) {
throw utils::BasicException("Creating a LocalTime with invalid seconds parameter.");
}
hours = local_time_parameters.hours;
minutes = local_time_parameters.minutes;
seconds = local_time_parameters.seconds;
milliseconds = local_time_parameters.milliseconds;
microseconds = local_time_parameters.microseconds;
}
int64_t LocalTime::MicrosecondsSinceEpoch() const {
return std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::hours{hours} + std::chrono::minutes{minutes} + std::chrono::seconds{seconds} +
std::chrono::milliseconds{milliseconds} + std::chrono::microseconds{microseconds})
.count();
}
size_t LocalTimeHash::operator()(const LocalTime &local_time) const {
utils::HashCombine<uint64_t, uint64_t> hasher;
size_t result = hasher(0, local_time.hours);
result = hasher(result, local_time.minutes);
result = hasher(result, local_time.seconds);
result = hasher(result, local_time.milliseconds);
result = hasher(result, local_time.microseconds);
return result;
}
LocalDateTime::LocalDateTime(const int64_t microseconds) {
auto chrono_microseconds = std::chrono::microseconds(microseconds);
date = Date(chrono_microseconds.count());
chrono_microseconds -= std::chrono::microseconds{date.MicrosecondsSinceEpoch()};
local_time = LocalTime(chrono_microseconds.count());
}
// return microseconds normilized with regard to epoch time point
int64_t LocalDateTime::MicrosecondsSinceEpoch() const {
return date.MicrosecondsSinceEpoch() + local_time.MicrosecondsSinceEpoch();
}
LocalDateTime::LocalDateTime(const DateParameters date_parameters, const LocalTimeParameters &local_time_parameters)
: date(date_parameters), local_time(local_time_parameters) {}
size_t LocalDateTimeHash::operator()(const LocalDateTime &local_date_time) const {
utils::HashCombine<uint64_t, uint64_t> hasher;
size_t result = hasher(0, LocalTimeHash{}(local_date_time.local_time));
result = hasher(result, DateHash{}(local_date_time.date));
return result;
}
Duration::Duration(int64_t microseconds) { this->microseconds = microseconds; }
namespace {
template <Chrono From, Chrono To>
constexpr To CastChronoDouble(const double value) {
return std::chrono::duration_cast<To>(std::chrono::duration<double, typename From::period>(value));
};
} // namespace
Duration::Duration(const DurationParameters &parameters) {
microseconds = (CastChronoDouble<std::chrono::years, std::chrono::microseconds>(parameters.years) +
CastChronoDouble<std::chrono::months, std::chrono::microseconds>(parameters.months) +
CastChronoDouble<std::chrono::days, std::chrono::microseconds>(parameters.days) +
CastChronoDouble<std::chrono::hours, std::chrono::microseconds>(parameters.hours) +
CastChronoDouble<std::chrono::minutes, std::chrono::microseconds>(parameters.minutes) +
CastChronoDouble<std::chrono::seconds, std::chrono::microseconds>(parameters.seconds))
.count();
}
Duration Duration::operator-() const {
Duration result{-microseconds};
return result;
}
size_t DurationHash::operator()(const Duration &duration) const { return std::hash<int64_t>{}(duration.microseconds); }
} // namespace query

View File

@ -8,7 +8,6 @@
#include <string_view>
#include <utility>
#include "query/temporal.hpp"
#include "storage/v2/temporal.hpp"
#include "utils/exceptions.hpp"
#include "utils/fnv.hpp"
@ -60,22 +59,22 @@ TypedValue::TypedValue(const storage::PropertyValue &value, utils::MemoryResourc
switch (temporal_data.type) {
case storage::TemporalType::Date: {
type_ = Type::Date;
new (&date_v) Date(temporal_data.microseconds);
new (&date_v) utils::Date(temporal_data.microseconds);
break;
}
case storage::TemporalType::LocalTime: {
type_ = Type::LocalTime;
new (&local_time_v) LocalTime(temporal_data.microseconds);
new (&local_time_v) utils::LocalTime(temporal_data.microseconds);
break;
}
case storage::TemporalType::LocalDateTime: {
type_ = Type::LocalDateTime;
new (&local_date_time_v) LocalDateTime(temporal_data.microseconds);
new (&local_date_time_v) utils::LocalDateTime(temporal_data.microseconds);
break;
}
case storage::TemporalType::Duration: {
type_ = Type::Duration;
new (&duration_v) Duration(temporal_data.microseconds);
new (&duration_v) utils::Duration(temporal_data.microseconds);
break;
}
}
@ -130,22 +129,22 @@ TypedValue::TypedValue(storage::PropertyValue &&other, utils::MemoryResource *me
switch (temporal_data.type) {
case storage::TemporalType::Date: {
type_ = Type::Date;
new (&date_v) Date(temporal_data.microseconds);
new (&date_v) utils::Date(temporal_data.microseconds);
break;
}
case storage::TemporalType::LocalTime: {
type_ = Type::LocalTime;
new (&local_time_v) LocalTime(temporal_data.microseconds);
new (&local_time_v) utils::LocalTime(temporal_data.microseconds);
break;
}
case storage::TemporalType::LocalDateTime: {
type_ = Type::LocalDateTime;
new (&local_date_time_v) LocalDateTime(temporal_data.microseconds);
new (&local_date_time_v) utils::LocalDateTime(temporal_data.microseconds);
break;
}
case storage::TemporalType::Duration: {
type_ = Type::Duration;
new (&duration_v) Duration(temporal_data.microseconds);
new (&duration_v) utils::Duration(temporal_data.microseconds);
break;
}
}
@ -193,16 +192,16 @@ TypedValue::TypedValue(const TypedValue &other, utils::MemoryResource *memory) :
new (&path_v) Path(other.path_v, memory_);
return;
case Type::Date:
new (&date_v) Date(other.date_v);
new (&date_v) utils::Date(other.date_v);
return;
case Type::LocalTime:
new (&local_time_v) LocalTime(other.local_time_v);
new (&local_time_v) utils::LocalTime(other.local_time_v);
return;
case Type::LocalDateTime:
new (&local_date_time_v) LocalDateTime(other.local_date_time_v);
new (&local_date_time_v) utils::LocalDateTime(other.local_date_time_v);
return;
case Type::Duration:
new (&duration_v) Duration(other.duration_v);
new (&duration_v) utils::Duration(other.duration_v);
return;
}
LOG_FATAL("Unsupported TypedValue::Type");
@ -242,16 +241,16 @@ TypedValue::TypedValue(TypedValue &&other, utils::MemoryResource *memory) : memo
new (&path_v) Path(std::move(other.path_v), memory_);
break;
case Type::Date:
new (&date_v) Date(other.date_v);
new (&date_v) utils::Date(other.date_v);
break;
case Type::LocalTime:
new (&local_time_v) LocalTime(other.local_time_v);
new (&local_time_v) utils::LocalTime(other.local_time_v);
break;
case Type::LocalDateTime:
new (&local_date_time_v) LocalDateTime(other.local_date_time_v);
new (&local_date_time_v) utils::LocalDateTime(other.local_date_time_v);
break;
case Type::Duration:
new (&duration_v) Duration(other.duration_v);
new (&duration_v) utils::Duration(other.duration_v);
break;
}
other.DestroyValue();
@ -317,10 +316,10 @@ DEFINE_VALUE_AND_TYPE_GETTERS(TypedValue::TMap, Map, map_v)
DEFINE_VALUE_AND_TYPE_GETTERS(VertexAccessor, Vertex, vertex_v)
DEFINE_VALUE_AND_TYPE_GETTERS(EdgeAccessor, Edge, edge_v)
DEFINE_VALUE_AND_TYPE_GETTERS(Path, Path, path_v)
DEFINE_VALUE_AND_TYPE_GETTERS(Date, Date, date_v)
DEFINE_VALUE_AND_TYPE_GETTERS(LocalTime, LocalTime, local_time_v)
DEFINE_VALUE_AND_TYPE_GETTERS(LocalDateTime, LocalDateTime, local_date_time_v)
DEFINE_VALUE_AND_TYPE_GETTERS(Duration, Duration, duration_v)
DEFINE_VALUE_AND_TYPE_GETTERS(utils::Date, Date, date_v)
DEFINE_VALUE_AND_TYPE_GETTERS(utils::LocalTime, LocalTime, local_time_v)
DEFINE_VALUE_AND_TYPE_GETTERS(utils::LocalDateTime, LocalDateTime, local_date_time_v)
DEFINE_VALUE_AND_TYPE_GETTERS(utils::Duration, Duration, duration_v)
#undef DEFINE_VALUE_AND_TYPE_GETTERS
@ -425,10 +424,10 @@ TypedValue &TypedValue::operator=(const std::map<std::string, TypedValue> &other
DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const VertexAccessor &, Vertex, vertex_v)
DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const EdgeAccessor &, Edge, edge_v)
DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const Path &, Path, path_v)
DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const Date &, Date, date_v)
DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const LocalTime &, LocalTime, local_time_v)
DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const LocalDateTime &, LocalDateTime, local_date_time_v)
DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const Duration &, Duration, duration_v)
DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const utils::Date &, Date, date_v)
DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const utils::LocalTime &, LocalTime, local_time_v)
DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const utils::LocalDateTime &, LocalDateTime, local_date_time_v)
DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const utils::Duration &, Duration, duration_v)
#undef DEFINE_TYPED_VALUE_COPY_ASSIGNMENT
@ -513,16 +512,16 @@ TypedValue &TypedValue::operator=(const TypedValue &other) {
new (&path_v) Path(other.path_v, memory_);
return *this;
case Type::Date:
new (&date_v) Date(other.date_v);
new (&date_v) utils::Date(other.date_v);
return *this;
case Type::LocalTime:
new (&local_time_v) LocalTime(other.local_time_v);
new (&local_time_v) utils::LocalTime(other.local_time_v);
return *this;
case Type::LocalDateTime:
new (&local_date_time_v) LocalDateTime(other.local_date_time_v);
new (&local_date_time_v) utils::LocalDateTime(other.local_date_time_v);
return *this;
case Type::Duration:
new (&duration_v) Duration(other.duration_v);
new (&duration_v) utils::Duration(other.duration_v);
return *this;
}
LOG_FATAL("Unsupported TypedValue::Type");
@ -572,16 +571,16 @@ TypedValue &TypedValue::operator=(TypedValue &&other) noexcept(false) {
new (&path_v) Path(std::move(other.path_v), memory_);
break;
case Type::Date:
new (&date_v) Date(other.date_v);
new (&date_v) utils::Date(other.date_v);
break;
case Type::LocalTime:
new (&local_time_v) LocalTime(other.local_time_v);
new (&local_time_v) utils::LocalTime(other.local_time_v);
break;
case Type::LocalDateTime:
new (&local_date_time_v) LocalDateTime(other.local_date_time_v);
new (&local_date_time_v) utils::LocalDateTime(other.local_date_time_v);
break;
case Type::Duration:
new (&duration_v) Duration(other.duration_v);
new (&duration_v) utils::Duration(other.duration_v);
break;
}
other.DestroyValue();
@ -1020,13 +1019,13 @@ size_t TypedValue::Hash::operator()(const TypedValue &value) const {
utils::FnvCollection<decltype(edges), EdgeAccessor>{}(edges);
}
case TypedValue::Type::Date:
return DateHash{}(value.ValueDate());
return utils::DateHash{}(value.ValueDate());
case TypedValue::Type::LocalTime:
return LocalTimeHash{}(value.ValueLocalTime());
return utils::LocalTimeHash{}(value.ValueLocalTime());
case TypedValue::Type::LocalDateTime:
return LocalDateTimeHash{}(value.ValueLocalDateTime());
return utils::LocalDateTimeHash{}(value.ValueLocalDateTime());
case TypedValue::Type::Duration:
return DurationHash{}(value.ValueDuration());
return utils::DurationHash{}(value.ValueDuration());
break;
}
LOG_FATAL("Unhandled TypedValue.type() in hash function");

View File

@ -11,12 +11,12 @@
#include "query/db_accessor.hpp"
#include "query/path.hpp"
#include "query/temporal.hpp"
#include "utils/exceptions.hpp"
#include "utils/memory.hpp"
#include "utils/pmr/map.hpp"
#include "utils/pmr/string.hpp"
#include "utils/pmr/vector.hpp"
#include "utils/temporal.hpp"
namespace query {
@ -140,22 +140,22 @@ class TypedValue {
double_v = value;
}
explicit TypedValue(const Date &value, utils::MemoryResource *memory = utils::NewDeleteResource())
explicit TypedValue(const utils::Date &value, utils::MemoryResource *memory = utils::NewDeleteResource())
: memory_(memory), type_(Type::Date) {
date_v = value;
}
explicit TypedValue(const LocalTime &value, utils::MemoryResource *memory = utils::NewDeleteResource())
explicit TypedValue(const utils::LocalTime &value, utils::MemoryResource *memory = utils::NewDeleteResource())
: memory_(memory), type_(Type::LocalTime) {
local_time_v = value;
}
explicit TypedValue(const LocalDateTime &value, utils::MemoryResource *memory = utils::NewDeleteResource())
explicit TypedValue(const utils::LocalDateTime &value, utils::MemoryResource *memory = utils::NewDeleteResource())
: memory_(memory), type_(Type::LocalDateTime) {
local_date_time_v = value;
}
explicit TypedValue(const Duration &value, utils::MemoryResource *memory = utils::NewDeleteResource())
explicit TypedValue(const utils::Duration &value, utils::MemoryResource *memory = utils::NewDeleteResource())
: memory_(memory), type_(Type::Duration) {
duration_v = value;
}
@ -417,10 +417,10 @@ class TypedValue {
TypedValue &operator=(const VertexAccessor &);
TypedValue &operator=(const EdgeAccessor &);
TypedValue &operator=(const Path &);
TypedValue &operator=(const Date &);
TypedValue &operator=(const LocalTime &);
TypedValue &operator=(const LocalDateTime &);
TypedValue &operator=(const Duration &);
TypedValue &operator=(const utils::Date &);
TypedValue &operator=(const utils::LocalTime &);
TypedValue &operator=(const utils::LocalDateTime &);
TypedValue &operator=(const utils::Duration &);
/** Copy assign other, utils::MemoryResource of `this` is used */
TypedValue &operator=(const TypedValue &other);
@ -471,10 +471,10 @@ class TypedValue {
DECLARE_VALUE_AND_TYPE_GETTERS(EdgeAccessor, Edge)
DECLARE_VALUE_AND_TYPE_GETTERS(Path, Path)
DECLARE_VALUE_AND_TYPE_GETTERS(Date, Date)
DECLARE_VALUE_AND_TYPE_GETTERS(LocalTime, LocalTime)
DECLARE_VALUE_AND_TYPE_GETTERS(LocalDateTime, LocalDateTime)
DECLARE_VALUE_AND_TYPE_GETTERS(Duration, Duration)
DECLARE_VALUE_AND_TYPE_GETTERS(utils::Date, Date)
DECLARE_VALUE_AND_TYPE_GETTERS(utils::LocalTime, LocalTime)
DECLARE_VALUE_AND_TYPE_GETTERS(utils::LocalDateTime, LocalDateTime)
DECLARE_VALUE_AND_TYPE_GETTERS(utils::Duration, Duration)
#undef DECLARE_VALUE_AND_TYPE_GETTERS
@ -513,10 +513,10 @@ class TypedValue {
VertexAccessor vertex_v;
EdgeAccessor edge_v;
Path path_v;
Date date_v;
LocalTime local_time_v;
LocalDateTime local_date_time_v;
Duration duration_v;
utils::Date date_v;
utils::LocalTime local_time_v;
utils::LocalDateTime local_date_time_v;
utils::Duration duration_v;
};
/**

View File

@ -12,6 +12,7 @@ set(utils_src_files
settings.cpp
signals.cpp
sysinfo/memory.cpp
temporal.cpp
thread.cpp
thread_pool.cpp
uuid.cpp)

675
src/utils/temporal.cpp Normal file
View File

@ -0,0 +1,675 @@
#include "utils/temporal.hpp"
#include <charconv>
#include <chrono>
#include <string_view>
#include "utils/exceptions.hpp"
#include "utils/fnv.hpp"
namespace utils {
namespace {
template <typename T>
concept Chrono = requires(T) {
typename T::rep;
typename T::period;
};
template <Chrono TFirst, Chrono TSecond>
constexpr auto GetAndSubtractDuration(TSecond &base_duration) {
const auto duration = std::chrono::duration_cast<TFirst>(base_duration);
base_duration -= duration;
return duration.count();
}
constexpr std::chrono::microseconds epoch{std::chrono::years{1970} + std::chrono::months{1} + std::chrono::days{1}};
constexpr bool IsInBounds(const auto low, const auto high, const auto value) { return low <= value && value <= high; }
constexpr bool IsValidDay(const uint8_t day, const uint8_t month, const uint16_t year) {
return std::chrono::year_month_day(std::chrono::year{year}, std::chrono::month{month}, std::chrono::day{day}).ok();
}
template <typename T>
std::optional<T> ParseNumber(const std::string_view string, const size_t size) {
if (string.size() < size) {
return std::nullopt;
}
T value{};
if (const auto [p, ec] = std::from_chars(string.data(), string.data() + size, value);
ec != std::errc() || p != string.data() + size) {
return std::nullopt;
}
return value;
}
} // namespace
Date::Date(const int64_t microseconds) {
auto chrono_microseconds = std::chrono::microseconds(microseconds);
chrono_microseconds += epoch;
MG_ASSERT(chrono_microseconds.count() >= 0, "Invalid Date specified in microseconds");
years = GetAndSubtractDuration<std::chrono::years>(chrono_microseconds);
months = GetAndSubtractDuration<std::chrono::months>(chrono_microseconds);
days = GetAndSubtractDuration<std::chrono::days>(chrono_microseconds);
}
Date::Date(const DateParameters &date_parameters) {
if (!IsInBounds(0, 9999, date_parameters.years)) {
throw utils::BasicException(
"Creating a Date with invalid year parameter. The value should be an integer between 0 and 9999.");
}
if (!IsInBounds(1, 12, date_parameters.months)) {
throw utils::BasicException(
"Creating a Date with invalid month parameter. The value should be an integer between 1 and 12.");
}
if (!IsInBounds(1, 31, date_parameters.days) ||
!IsValidDay(date_parameters.days, date_parameters.months, date_parameters.years)) {
throw utils::BasicException(
"Creating a Date with invalid day parameter. The value should be an integer between 1 and 31, depending on the "
"month and year.");
}
years = date_parameters.years;
months = date_parameters.months;
days = date_parameters.days;
}
namespace {
constexpr auto *kSupportedDateFormatsHelpMessage = R"help(
String representing the date should be in one of the following formats:
- YYYY-MM-DD
- YYYYMMDD
- YYYY-MM
Symbol table:
|---|-------|
| Y | YEAR |
|---|-------|
| M | MONTH |
|---|-------|
| D | DAY |
|---|-------|)help";
} // namespace
std::pair<DateParameters, bool> ParseDateParameters(std::string_view date_string) {
// https://en.wikipedia.org/wiki/ISO_8601#Dates
// Date string with the '-' as separator are in the EXTENDED format,
// otherwise they are in a BASIC format
constexpr std::array valid_sizes{
10, // YYYY-MM-DD
8, // YYYYMMDD
7 // YYYY-MM
};
if (!std::any_of(
valid_sizes.begin(), valid_sizes.end(),
[date_string_size = date_string.size()](const auto valid_size) { return valid_size == date_string_size; })) {
throw utils::BasicException("Invalid string for date. {}", kSupportedDateFormatsHelpMessage);
}
DateParameters date_parameters;
auto maybe_year = ParseNumber<int64_t>(date_string, 4);
if (!maybe_year) {
throw utils::BasicException("Invalid year in the string. {}", kSupportedDateFormatsHelpMessage);
}
date_parameters.years = *maybe_year;
date_string.remove_prefix(4);
bool is_extended_format = false;
if (date_string.front() == '-') {
is_extended_format = true;
date_string.remove_prefix(1);
}
auto maybe_month = ParseNumber<int64_t>(date_string, 2);
if (!maybe_month) {
throw utils::BasicException("Invalid month in the string. {}", kSupportedDateFormatsHelpMessage);
}
date_parameters.months = *maybe_month;
date_string.remove_prefix(2);
if (!date_string.empty()) {
if (date_string.front() == '-') {
if (!is_extended_format) {
throw utils::BasicException("Invalid format for the date. {}", kSupportedDateFormatsHelpMessage);
}
date_string.remove_prefix(1);
}
auto maybe_day = ParseNumber<int64_t>(date_string, 2);
if (!maybe_day) {
throw utils::BasicException("Invalid month in the string. {}", kSupportedDateFormatsHelpMessage);
}
date_parameters.days = *maybe_day;
date_string.remove_prefix(2);
}
if (!date_string.empty()) {
throw utils::BasicException("Invalid format for the date. {}", kSupportedDateFormatsHelpMessage);
}
return {date_parameters, is_extended_format};
}
int64_t Date::MicrosecondsSinceEpoch() const {
auto result = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::years{years} + std::chrono::months{months} + std::chrono::days{days});
result -= epoch;
return result.count();
}
size_t DateHash::operator()(const Date &date) const {
utils::HashCombine<uint64_t, uint64_t> hasher;
size_t result = hasher(0, date.years);
result = hasher(result, date.months);
result = hasher(result, date.days);
return result;
}
namespace {
constexpr auto *kSupportedTimeFormatsHelpMessage = R"help(
String representing the time should be in one of the following formats:
- [T]hh:mm:ss
- [T]hh:mm
or
- [T]hhmmss
- [T]hhmm
- [T]hh
Symbol table:
|---|---------|
| h | HOURS |
|---|---------|
| m | MINUTES |
|---|---------|
| s | SECONDS |
|---|---------|
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.)help";
} // namespace
std::pair<LocalTimeParameters, bool> ParseLocalTimeParameters(std::string_view local_time_string) {
// https://en.wikipedia.org/wiki/ISO_8601#Times
// supported formats:
// hh:mm:ss.ssssss | hhmmss.ssssss
// hh:mm:ss.sss | hhmmss.sss
// hh:mm | hhmm
// hh
// Times without the separator are in BASIC format
// Times with ':' as a separator are in EXTENDED format.
if (local_time_string.front() == 'T') {
local_time_string.remove_prefix(1);
}
std::optional<bool> using_colon;
const auto process_optional_colon = [&] {
const bool has_colon = local_time_string.front() == ':';
if (!using_colon.has_value()) {
using_colon.emplace(has_colon);
}
if (*using_colon ^ has_colon) {
throw utils::BasicException(
"Invalid format for the local time. A separator should be used consistently or not at all. {}",
kSupportedTimeFormatsHelpMessage);
}
if (has_colon) {
local_time_string.remove_prefix(1);
}
if (local_time_string.empty()) {
throw utils::BasicException("Invalid format for the local time. {}", kSupportedTimeFormatsHelpMessage);
}
};
LocalTimeParameters local_time_parameters;
const auto maybe_hour = ParseNumber<int64_t>(local_time_string, 2);
if (!maybe_hour) {
throw utils::BasicException("Invalid hour in the string. {}", kSupportedTimeFormatsHelpMessage);
}
local_time_parameters.hours = *maybe_hour;
local_time_string.remove_prefix(2);
if (local_time_string.empty()) {
return {local_time_parameters, false};
}
process_optional_colon();
const auto maybe_minute = ParseNumber<int64_t>(local_time_string, 2);
if (!maybe_minute) {
throw utils::BasicException("Invalid minutes in the string. {}", kSupportedTimeFormatsHelpMessage);
}
local_time_parameters.minutes = *maybe_minute;
local_time_string.remove_prefix(2);
if (local_time_string.empty()) {
return {local_time_parameters, *using_colon};
}
process_optional_colon();
const auto maybe_seconds = ParseNumber<int64_t>(local_time_string, 2);
if (!maybe_seconds) {
throw utils::BasicException("Invalid seconds in the string. {}", kSupportedTimeFormatsHelpMessage);
}
local_time_parameters.seconds = *maybe_seconds;
local_time_string.remove_prefix(2);
if (local_time_string.empty()) {
return {local_time_parameters, *using_colon};
}
if (local_time_string.front() != '.') {
throw utils::BasicException("Invalid format for local time. {}", kSupportedTimeFormatsHelpMessage);
}
local_time_string.remove_prefix(1);
const auto maybe_milliseconds = ParseNumber<int64_t>(local_time_string, 3);
if (!maybe_milliseconds) {
throw utils::BasicException("Invalid milliseconds in the string. {}", kSupportedTimeFormatsHelpMessage);
}
local_time_parameters.milliseconds = *maybe_milliseconds;
local_time_string.remove_prefix(3);
if (local_time_string.empty()) {
return {local_time_parameters, *using_colon};
}
const auto maybe_microseconds = ParseNumber<int64_t>(local_time_string, 3);
if (!maybe_microseconds) {
throw utils::BasicException("Invalid microseconds in the string. {}", kSupportedTimeFormatsHelpMessage);
}
local_time_parameters.microseconds = *maybe_microseconds;
local_time_string.remove_prefix(3);
if (!local_time_string.empty()) {
throw utils::BasicException("Extra characters present at the end of the string.");
}
return {local_time_parameters, *using_colon};
}
LocalTime::LocalTime(const int64_t microseconds) {
auto chrono_microseconds = std::chrono::microseconds(microseconds);
MG_ASSERT(chrono_microseconds.count() >= 0, "Negative LocalTime specified in microseconds");
const auto parsed_hours = GetAndSubtractDuration<std::chrono::hours>(chrono_microseconds);
MG_ASSERT(parsed_hours <= 23, "Invalid LocalTime specified in microseconds");
hours = parsed_hours;
minutes = GetAndSubtractDuration<std::chrono::minutes>(chrono_microseconds);
seconds = GetAndSubtractDuration<std::chrono::seconds>(chrono_microseconds);
milliseconds = GetAndSubtractDuration<std::chrono::milliseconds>(chrono_microseconds);
this->microseconds = chrono_microseconds.count();
}
LocalTime::LocalTime(const LocalTimeParameters &local_time_parameters) {
if (!IsInBounds(0, 23, local_time_parameters.hours)) {
throw utils::BasicException("Creating a LocalTime with invalid hour parameter.");
}
if (!IsInBounds(0, 59, local_time_parameters.minutes)) {
throw utils::BasicException("Creating a LocalTime with invalid minutes parameter.");
}
// ISO 8601 supports leap seconds, but we ignore it for now to simplify the implementation
if (!IsInBounds(0, 59, local_time_parameters.seconds)) {
throw utils::BasicException("Creating a LocalTime with invalid seconds parameter.");
}
if (!IsInBounds(0, 999, local_time_parameters.milliseconds)) {
throw utils::BasicException("Creating a LocalTime with invalid seconds parameter.");
}
if (!IsInBounds(0, 999, local_time_parameters.microseconds)) {
throw utils::BasicException("Creating a LocalTime with invalid seconds parameter.");
}
hours = local_time_parameters.hours;
minutes = local_time_parameters.minutes;
seconds = local_time_parameters.seconds;
milliseconds = local_time_parameters.milliseconds;
microseconds = local_time_parameters.microseconds;
}
int64_t LocalTime::MicrosecondsSinceEpoch() const {
return std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::hours{hours} + std::chrono::minutes{minutes} + std::chrono::seconds{seconds} +
std::chrono::milliseconds{milliseconds} + std::chrono::microseconds{microseconds})
.count();
}
size_t LocalTimeHash::operator()(const LocalTime &local_time) const {
utils::HashCombine<uint64_t, uint64_t> hasher;
size_t result = hasher(0, local_time.hours);
result = hasher(result, local_time.minutes);
result = hasher(result, local_time.seconds);
result = hasher(result, local_time.milliseconds);
result = hasher(result, local_time.microseconds);
return result;
}
namespace {
constexpr auto *kSupportedLocalDateTimeFormatsHelpMessage = R"help(
String representing the LocalDateTime should be in one of the following formats:
- YYYY-MM-DDThh:mm:ss
- YYYY-MM-DDThh:mm
or
- YYYYMMDDThhmmss
- YYYYMMDDThhmm
- YYYYMMDDThh
Symbol table:
|---|---------|
| Y | YEAR |
|---|---------|
| M | MONTH |
|---|---------|
| D | DAY |
|---|---------|
| h | HOURS |
|---|---------|
| m | MINUTES |
|---|---------|
| s | SECONDS |
|---|---------|
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
std::pair<DateParameters, LocalTimeParameters> ParseLocalDateTimeParameters(std::string_view string) {
auto t_position = string.find('T');
if (t_position == std::string_view::npos) {
throw utils::BasicException("Invalid LocalDateTime format. {}", kSupportedLocalDateTimeFormatsHelpMessage);
}
try {
auto [date_parameters, extended_date_format] = ParseDateParameters(string.substr(0, t_position));
// https://en.wikipedia.org/wiki/ISO_8601#Combined_date_and_time_representations
// ISO8601 specifies that you cannot mix extended and basic format of date and time
// If the date is in the extended format, same must be true for the time, so we don't send T
// which denotes the basic format. The opposite case also aplies.
auto local_time_substring = string.substr(t_position + 1);
if (local_time_substring.empty()) {
throw utils::BasicException("Invalid LocalDateTime format. {}", kSupportedLocalDateTimeFormatsHelpMessage);
}
auto [local_time_parameters, extended_time_format] = ParseLocalTimeParameters(local_time_substring);
if (extended_date_format ^ extended_time_format) {
throw utils::BasicException(
"Invalid LocalDateTime format. Both date and time should be in the basic or extended format. {}",
kSupportedLocalDateTimeFormatsHelpMessage);
}
return {date_parameters, local_time_parameters};
} catch (const utils::BasicException &e) {
throw utils::BasicException("Invalid LocalDateTime format. {}", kSupportedLocalDateTimeFormatsHelpMessage);
}
}
LocalDateTime::LocalDateTime(const int64_t microseconds) {
auto chrono_microseconds = std::chrono::microseconds(microseconds);
date = Date(chrono_microseconds.count());
chrono_microseconds -= std::chrono::microseconds{date.MicrosecondsSinceEpoch()};
local_time = LocalTime(chrono_microseconds.count());
}
// return microseconds normilized with regard to epoch time point
int64_t LocalDateTime::MicrosecondsSinceEpoch() const {
return date.MicrosecondsSinceEpoch() + local_time.MicrosecondsSinceEpoch();
}
LocalDateTime::LocalDateTime(const DateParameters date_parameters, const LocalTimeParameters &local_time_parameters)
: date(date_parameters), local_time(local_time_parameters) {}
size_t LocalDateTimeHash::operator()(const LocalDateTime &local_date_time) const {
utils::HashCombine<uint64_t, uint64_t> hasher;
size_t result = hasher(0, LocalTimeHash{}(local_date_time.local_time));
result = hasher(result, DateHash{}(local_date_time.date));
return result;
}
namespace {
std::optional<DurationParameters> TryParseIsoDurationString(std::string_view string) {
DurationParameters duration_parameters;
if (string.empty()) {
return std::nullopt;
}
if (string.front() != 'P') {
return std::nullopt;
}
string.remove_prefix(1);
if (string.empty()) {
return std::nullopt;
}
bool decimal_point_used = false;
const auto check_decimal_fraction = [&](const auto substring) {
// only the last number in the string can be a fraction
// if a decimal point was already found, and another number is being parsed
// we are in an invalid state
if (decimal_point_used) {
return false;
}
decimal_point_used = substring.find('.') != std::string_view::npos;
return true;
};
const auto parse_and_assign = [&](std::string_view &string, const char label, double &destination) {
auto label_position = string.find(label);
if (label_position == std::string_view::npos) {
return true;
}
const auto number_substring = string.substr(0, label_position);
if (!check_decimal_fraction(number_substring)) {
return false;
}
const auto maybe_parsed_number = ParseNumber<double>(number_substring, number_substring.size());
if (!maybe_parsed_number) {
return false;
}
destination = *maybe_parsed_number;
// remove number + label
string.remove_prefix(label_position + 1);
return true;
};
const auto parse_duration_date_part = [&](auto date_string) {
if (!std::isdigit(date_string.front())) {
return false;
throw utils::BasicException("Invalid format of duration string");
}
if (!parse_and_assign(date_string, 'Y', duration_parameters.years)) {
return false;
}
if (date_string.empty()) {
return true;
}
if (!parse_and_assign(date_string, 'M', duration_parameters.months)) {
return false;
}
if (date_string.empty()) {
return true;
}
if (!parse_and_assign(date_string, 'D', duration_parameters.days)) {
return false;
}
return date_string.empty();
};
const auto parse_duration_time_part = [&](auto time_string) {
if (!std::isdigit(time_string.front())) {
return false;
}
if (!parse_and_assign(time_string, 'H', duration_parameters.hours)) {
return false;
}
if (time_string.empty()) {
return true;
}
if (!parse_and_assign(time_string, 'M', duration_parameters.minutes)) {
return false;
}
if (time_string.empty()) {
return true;
}
if (!parse_and_assign(time_string, 'S', duration_parameters.seconds)) {
return false;
}
return time_string.empty();
};
auto t_position = string.find('T');
const auto date_string = string.substr(0, t_position);
if (!date_string.empty() && !parse_duration_date_part(date_string)) {
return std::nullopt;
}
if (t_position == std::string_view::npos) {
return duration_parameters;
}
const auto time_string = string.substr(t_position + 1);
if (time_string.empty() || !parse_duration_time_part(time_string)) {
return std::nullopt;
}
return duration_parameters;
}
} // namespace
// clang-format off
const auto kSupportedDurationFormatsHelpMessage = fmt::format(R"help(
"String representing duration should be in the following format:
P[nY][nM][nD]T[nH][nM][nS]
Symbol table:
|---|---------|
| Y | YEAR |
|---|---------|
| M | MONTH |
|---|---------|
| D | DAY |
|---|---------|
| H | HOURS |
|---|---------|
| M | MINUTES |
|---|---------|
| S | SECONDS |
|---|---------|
'n' represents a number that can be an integer of ANY value, or a fraction IF it's the last value in the string.
All the fields are optional.
Alternatively, the string can contain LocalDateTime format:
P<local_date_time_string>
{}
)help", kSupportedLocalDateTimeFormatsHelpMessage);
// clang-format on
DurationParameters ParseDurationParameters(std::string_view string) {
// https://en.wikipedia.org/wiki/ISO_8601#Durations
// The string needs to start with P followed by one of the two options:
// - string in a duration specific format
// - string in a combined date and time format (LocalDateTime string format)
if (string.empty() || string.front() != 'P') {
throw utils::BasicException("Duration string is empty.");
}
if (auto maybe_duration_parameters = TryParseIsoDurationString(string); maybe_duration_parameters) {
return *maybe_duration_parameters;
}
DurationParameters duration_parameters;
// remove P and try to parse local date time
string.remove_prefix(1);
try {
const auto [date_parameters, local_time_parameters] = ParseLocalDateTimeParameters(string);
duration_parameters.years = static_cast<double>(date_parameters.years);
duration_parameters.months = static_cast<double>(date_parameters.months);
duration_parameters.days = static_cast<double>(date_parameters.days);
duration_parameters.hours = static_cast<double>(local_time_parameters.hours);
duration_parameters.minutes = static_cast<double>(local_time_parameters.minutes);
duration_parameters.seconds = static_cast<double>(local_time_parameters.seconds);
duration_parameters.milliseconds = static_cast<double>(local_time_parameters.milliseconds);
duration_parameters.microseconds = static_cast<double>(local_time_parameters.microseconds);
return duration_parameters;
} catch (const utils::BasicException &e) {
throw utils::BasicException("Invalid duration string. {}", kSupportedDurationFormatsHelpMessage);
}
}
Duration::Duration(int64_t microseconds) { this->microseconds = microseconds; }
namespace {
template <Chrono From, Chrono To>
constexpr To CastChronoDouble(const double value) {
return std::chrono::duration_cast<To>(std::chrono::duration<double, typename From::period>(value));
};
} // namespace
Duration::Duration(const DurationParameters &parameters) {
microseconds = (CastChronoDouble<std::chrono::years, std::chrono::microseconds>(parameters.years) +
CastChronoDouble<std::chrono::months, std::chrono::microseconds>(parameters.months) +
CastChronoDouble<std::chrono::days, std::chrono::microseconds>(parameters.days) +
CastChronoDouble<std::chrono::hours, std::chrono::microseconds>(parameters.hours) +
CastChronoDouble<std::chrono::minutes, std::chrono::microseconds>(parameters.minutes) +
CastChronoDouble<std::chrono::seconds, std::chrono::microseconds>(parameters.seconds) +
CastChronoDouble<std::chrono::milliseconds, std::chrono::microseconds>(parameters.milliseconds) +
CastChronoDouble<std::chrono::microseconds, std::chrono::microseconds>(parameters.microseconds))
.count();
}
Duration Duration::operator-() const {
Duration result{-microseconds};
return result;
}
size_t DurationHash::operator()(const Duration &duration) const { return std::hash<int64_t>{}(duration.microseconds); }
} // namespace utils

View File

@ -5,14 +5,19 @@
#include "utils/exceptions.hpp"
#include "utils/logging.hpp"
namespace query {
namespace utils {
struct DateParameters {
int64_t years{0};
int64_t months{1};
int64_t days{1};
bool operator==(const DateParameters &) const = default;
};
// boolean indicates whether the parsed string was in extended format
std::pair<DateParameters, bool> ParseDateParameters(std::string_view date_string);
struct Date {
explicit Date() : Date{DateParameters{}} {}
// we assume we accepted date in microseconds which was normilized using the epoch time point
@ -38,8 +43,13 @@ struct LocalTimeParameters {
int64_t seconds{0};
int64_t milliseconds{0};
int64_t microseconds{0};
bool operator==(const LocalTimeParameters &) const = default;
};
// boolean indicates whether the parsed string was in extended format
std::pair<LocalTimeParameters, bool> ParseLocalTimeParameters(std::string_view string);
struct LocalTime {
explicit LocalTime() : LocalTime{LocalTimeParameters{}} {}
explicit LocalTime(int64_t microseconds);
@ -60,6 +70,8 @@ struct LocalTimeHash {
size_t operator()(const LocalTime &local_time) const;
};
std::pair<DateParameters, LocalTimeParameters> ParseLocalDateTimeParameters(std::string_view string);
struct LocalDateTime {
explicit LocalDateTime(int64_t microseconds);
explicit LocalDateTime(DateParameters date, const LocalTimeParameters &local_time);
@ -83,10 +95,12 @@ struct DurationParameters {
double hours{0};
double minutes{0};
double seconds{0};
// TODO(antonio2368): Check how to include milliseconds/microseconds
// ISO 8601 does not specify string format for them
double milliseconds{0};
double microseconds{0};
};
DurationParameters ParseDurationParameters(std::string_view string);
struct Duration {
explicit Duration(int64_t microseconds);
explicit Duration(const DurationParameters &parameters);
@ -102,4 +116,4 @@ struct DurationHash {
size_t operator()(const Duration &duration) const;
};
} // namespace query
} // namespace utils

View File

@ -125,9 +125,6 @@ target_link_libraries(${test_prefix}query_serialization_property_value mg-query)
add_unit_test(query_streams.cpp)
target_link_libraries(${test_prefix}query_streams mg-query kafka-mock)
add_unit_test(query_temporal query_temporal.cpp)
target_link_libraries(${test_prefix}query_temporal mg-query)
# Test query/procedure
add_unit_test(query_procedure_mgp_type.cpp)
target_link_libraries(${test_prefix}query_procedure_mgp_type mg-query)
@ -278,6 +275,9 @@ target_link_libraries(${test_prefix}utils_license mg-utils)
add_unit_test(utils_settings.cpp)
target_link_libraries(${test_prefix}utils_settings mg-utils)
add_unit_test(utils_temporal utils_temporal.cpp)
target_link_libraries(${test_prefix}utils_temporal mg-utils)
# Test mg-storage-v2
add_unit_test(commit_log_v2.cpp)

View File

@ -3,9 +3,9 @@
#include <sstream>
#include <string>
#include "query/temporal.hpp"
#include "query/typed_value.hpp"
#include "utils/algorithm.hpp"
#include "utils/temporal.hpp"
/// Functions that convert types to a `std::string` representation of it. The
/// `TAccessor` supplied must have the functions `NameToLabel`, `LabelToName`,
@ -63,13 +63,13 @@ inline std::string ToString(const query::Path &path, const TAccessor &acc) {
}
// TODO(antonio2368): Define printing of dates
inline std::string ToString(const query::Date) { return ""; }
inline std::string ToString(const utils::Date) { return ""; }
inline std::string ToString(const query::LocalTime) { return ""; }
inline std::string ToString(const utils::LocalTime) { return ""; }
inline std::string ToString(const query::LocalDateTime) { return ""; }
inline std::string ToString(const utils::LocalDateTime) { return ""; }
inline std::string ToString(const query::Duration) { return ""; }
inline std::string ToString(const utils::Duration) { return ""; }
template <class TAccessor>
inline std::string ToString(const query::TypedValue &value, const TAccessor &acc) {

View File

@ -1,161 +0,0 @@
#include <optional>
#include <gtest/gtest.h>
#include "query/temporal.hpp"
#include "utils/exceptions.hpp"
namespace {
struct TestDateParameters {
query::DateParameters date_parameters;
bool should_throw;
};
constexpr std::array test_dates{TestDateParameters{{-1996, 11, 22}, true}, TestDateParameters{{1996, -11, 22}, true},
TestDateParameters{{1996, 11, -22}, true}, TestDateParameters{{1, 13, 3}, true},
TestDateParameters{{1, 12, 32}, true}, TestDateParameters{{1, 2, 29}, true},
TestDateParameters{{2020, 2, 29}, false}, TestDateParameters{{1700, 2, 29}, true},
TestDateParameters{{1200, 2, 29}, false}, TestDateParameters{{10000, 12, 3}, true}};
struct TestLocalTimeParameters {
query::LocalTimeParameters local_time_parameters;
bool should_throw;
};
constexpr std::array test_local_times{
TestLocalTimeParameters{{.hours = 24}, true}, TestLocalTimeParameters{{.hours = -1}, true},
TestLocalTimeParameters{{.minutes = -1}, true}, TestLocalTimeParameters{{.minutes = 60}, true},
TestLocalTimeParameters{{.seconds = -1}, true}, TestLocalTimeParameters{{.minutes = 60}, true},
TestLocalTimeParameters{{.milliseconds = -1}, true}, TestLocalTimeParameters{{.milliseconds = 1000}, true},
TestLocalTimeParameters{{.microseconds = -1}, true}, TestLocalTimeParameters{{.microseconds = 1000}, true},
TestLocalTimeParameters{{23, 59, 59, 999, 999}, false}, TestLocalTimeParameters{{0, 0, 0, 0, 0}, false}};
} // namespace
TEST(TemporalTest, DateConstruction) {
std::optional<query::Date> test_date;
for (const auto [date_parameters, should_throw] : test_dates) {
if (should_throw) {
ASSERT_THROW(test_date.emplace(date_parameters), utils::BasicException);
} else {
ASSERT_NO_THROW(test_date.emplace(date_parameters));
}
}
}
TEST(TemporalTest, DateMicrosecondsSinceEpochConversion) {
const auto check_microseconds = [](const auto date_parameters) {
query::Date initial_date{date_parameters};
const auto microseconds = initial_date.MicrosecondsSinceEpoch();
query::Date new_date{microseconds};
ASSERT_EQ(initial_date, new_date);
};
check_microseconds(query::DateParameters{2020, 11, 22});
check_microseconds(query::DateParameters{1900, 2, 22});
check_microseconds(query::DateParameters{0, 1, 1});
ASSERT_THROW(check_microseconds(query::DateParameters{-10, 1, 1}), utils::BasicException);
{
query::Date date{query::DateParameters{1970, 1, 1}};
ASSERT_EQ(date.MicrosecondsSinceEpoch(), 0);
}
{
query::Date date{query::DateParameters{1910, 1, 1}};
ASSERT_LT(date.MicrosecondsSinceEpoch(), 0);
}
{
query::Date date{query::DateParameters{2021, 1, 1}};
ASSERT_GT(date.MicrosecondsSinceEpoch(), 0);
}
}
TEST(TemporalTest, LocalTimeConstruction) {
std::optional<query::LocalTime> test_local_time;
for (const auto [local_time_parameters, should_throw] : test_local_times) {
if (should_throw) {
ASSERT_THROW(test_local_time.emplace(local_time_parameters), utils::BasicException);
} else {
ASSERT_NO_THROW(test_local_time.emplace(local_time_parameters));
}
}
}
TEST(TemporalTest, LocalTimeMicrosecondsSinceEpochConversion) {
const auto check_microseconds = [](const query::LocalTimeParameters &parameters) {
query::LocalTime initial_local_time{parameters};
const auto microseconds = initial_local_time.MicrosecondsSinceEpoch();
query::LocalTime new_local_time{microseconds};
ASSERT_EQ(initial_local_time, new_local_time);
};
check_microseconds(query::LocalTimeParameters{23, 59, 59, 999, 999});
check_microseconds(query::LocalTimeParameters{0, 0, 0, 0, 0});
check_microseconds(query::LocalTimeParameters{14, 8, 55, 321, 452});
}
TEST(TemporalTest, LocalDateTimeMicrosecondsSinceEpochConversion) {
const auto check_microseconds = [](const query::DateParameters date_parameters,
const query::LocalTimeParameters &local_time_parameters) {
query::LocalDateTime initial_local_date_time{date_parameters, local_time_parameters};
const auto microseconds = initial_local_date_time.MicrosecondsSinceEpoch();
query::LocalDateTime new_local_date_time{microseconds};
ASSERT_EQ(initial_local_date_time, new_local_date_time);
};
check_microseconds(query::DateParameters{2020, 11, 22}, query::LocalTimeParameters{0, 0, 0, 0, 0});
check_microseconds(query::DateParameters{1900, 2, 22}, query::LocalTimeParameters{0, 0, 0, 0, 0});
check_microseconds(query::DateParameters{0, 1, 1}, query::LocalTimeParameters{0, 0, 0, 0, 0});
{
query::LocalDateTime local_date_time(query::DateParameters{1970, 1, 1}, query::LocalTimeParameters{0, 0, 0, 0, 0});
ASSERT_EQ(local_date_time.MicrosecondsSinceEpoch(), 0);
}
{
query::LocalDateTime local_date_time(query::DateParameters{1970, 1, 1}, query::LocalTimeParameters{0, 0, 0, 0, 1});
ASSERT_GT(local_date_time.MicrosecondsSinceEpoch(), 0);
}
{
query::LocalTimeParameters local_time_parameters{12, 10, 40, 42, 42};
query::LocalDateTime local_date_time{query::DateParameters{1970, 1, 1}, local_time_parameters};
ASSERT_EQ(local_date_time.MicrosecondsSinceEpoch(),
query::LocalTime{local_time_parameters}.MicrosecondsSinceEpoch());
}
{
query::LocalDateTime local_date_time(query::DateParameters{1910, 1, 1}, query::LocalTimeParameters{0, 0, 0, 0, 0});
ASSERT_LT(local_date_time.MicrosecondsSinceEpoch(), 0);
}
{
query::LocalDateTime local_date_time(query::DateParameters{2021, 1, 1}, query::LocalTimeParameters{0, 0, 0, 0, 0});
ASSERT_GT(local_date_time.MicrosecondsSinceEpoch(), 0);
}
}
TEST(TemporalTest, DurationConversion) {
{
query::Duration duration{{.years = 2.5}};
const auto microseconds = duration.microseconds;
query::LocalDateTime local_date_time{microseconds};
ASSERT_EQ(local_date_time.date.years, 2 + 1970);
ASSERT_EQ(local_date_time.date.months, 6 + 1);
};
{
query::Duration duration{{.months = 26}};
const auto microseconds = duration.microseconds;
query::LocalDateTime local_date_time{microseconds};
ASSERT_EQ(local_date_time.date.years, 2 + 1970);
ASSERT_EQ(local_date_time.date.months, 2 + 1);
};
{
query::Duration duration{{.minutes = 123.25}};
const auto microseconds = duration.microseconds;
query::LocalDateTime local_date_time{microseconds};
ASSERT_EQ(local_date_time.date.years, 1970);
ASSERT_EQ(local_date_time.date.months, 1);
ASSERT_EQ(local_date_time.date.days, 1);
ASSERT_EQ(local_date_time.local_time.hours, 2);
ASSERT_EQ(local_date_time.local_time.minutes, 3);
ASSERT_EQ(local_date_time.local_time.seconds, 15);
};
}

View File

@ -0,0 +1,291 @@
#include <chrono>
#include <optional>
#include <gtest/gtest.h>
#include "utils/exceptions.hpp"
#include "utils/temporal.hpp"
namespace {
std::string ToString(const utils::DateParameters &date_parameters) {
return fmt::format("{:04d}-{:02d}-{:02d}", date_parameters.years, date_parameters.months, date_parameters.days);
}
std::string ToString(const utils::LocalTimeParameters &local_time_parameters) {
return fmt::format("{:02}:{:02d}:{:02d}", local_time_parameters.hours, local_time_parameters.minutes,
local_time_parameters.seconds);
}
struct TestDateParameters {
utils::DateParameters date_parameters;
bool should_throw;
};
constexpr std::array test_dates{TestDateParameters{{-1996, 11, 22}, true}, TestDateParameters{{1996, -11, 22}, true},
TestDateParameters{{1996, 11, -22}, true}, TestDateParameters{{1, 13, 3}, true},
TestDateParameters{{1, 12, 32}, true}, TestDateParameters{{1, 2, 29}, true},
TestDateParameters{{2020, 2, 29}, false}, TestDateParameters{{1700, 2, 29}, true},
TestDateParameters{{1200, 2, 29}, false}, TestDateParameters{{10000, 12, 3}, true}};
struct TestLocalTimeParameters {
utils::LocalTimeParameters local_time_parameters;
bool should_throw;
};
constexpr std::array test_local_times{
TestLocalTimeParameters{{.hours = 24}, true}, TestLocalTimeParameters{{.hours = -1}, true},
TestLocalTimeParameters{{.minutes = -1}, true}, TestLocalTimeParameters{{.minutes = 60}, true},
TestLocalTimeParameters{{.seconds = -1}, true}, TestLocalTimeParameters{{.minutes = 60}, true},
TestLocalTimeParameters{{.milliseconds = -1}, true}, TestLocalTimeParameters{{.milliseconds = 1000}, true},
TestLocalTimeParameters{{.microseconds = -1}, true}, TestLocalTimeParameters{{.microseconds = 1000}, true},
TestLocalTimeParameters{{23, 59, 59, 999, 999}, false}, TestLocalTimeParameters{{0, 0, 0, 0, 0}, false}};
} // namespace
TEST(TemporalTest, DateConstruction) {
std::optional<utils::Date> test_date;
for (const auto [date_parameters, should_throw] : test_dates) {
if (should_throw) {
EXPECT_THROW(test_date.emplace(date_parameters), utils::BasicException) << ToString(date_parameters);
} else {
EXPECT_NO_THROW(test_date.emplace(date_parameters)) << ToString(date_parameters);
}
}
}
TEST(TemporalTest, DateMicrosecondsSinceEpochConversion) {
const auto check_microseconds = [](const auto date_parameters) {
utils::Date initial_date{date_parameters};
const auto microseconds = initial_date.MicrosecondsSinceEpoch();
utils::Date new_date{microseconds};
ASSERT_EQ(initial_date, new_date);
};
check_microseconds(utils::DateParameters{2020, 11, 22});
check_microseconds(utils::DateParameters{1900, 2, 22});
check_microseconds(utils::DateParameters{0, 1, 1});
ASSERT_THROW(check_microseconds(utils::DateParameters{-10, 1, 1}), utils::BasicException);
{
utils::Date date{utils::DateParameters{1970, 1, 1}};
ASSERT_EQ(date.MicrosecondsSinceEpoch(), 0);
}
{
utils::Date date{utils::DateParameters{1910, 1, 1}};
ASSERT_LT(date.MicrosecondsSinceEpoch(), 0);
}
{
utils::Date date{utils::DateParameters{2021, 1, 1}};
ASSERT_GT(date.MicrosecondsSinceEpoch(), 0);
}
}
TEST(TemporalTest, LocalTimeConstruction) {
std::optional<utils::LocalTime> test_local_time;
for (const auto [local_time_parameters, should_throw] : test_local_times) {
if (should_throw) {
ASSERT_THROW(test_local_time.emplace(local_time_parameters), utils::BasicException);
} else {
ASSERT_NO_THROW(test_local_time.emplace(local_time_parameters));
}
}
}
TEST(TemporalTest, LocalTimeMicrosecondsSinceEpochConversion) {
const auto check_microseconds = [](const utils::LocalTimeParameters &parameters) {
utils::LocalTime initial_local_time{parameters};
const auto microseconds = initial_local_time.MicrosecondsSinceEpoch();
utils::LocalTime new_local_time{microseconds};
ASSERT_EQ(initial_local_time, new_local_time);
};
check_microseconds(utils::LocalTimeParameters{23, 59, 59, 999, 999});
check_microseconds(utils::LocalTimeParameters{0, 0, 0, 0, 0});
check_microseconds(utils::LocalTimeParameters{14, 8, 55, 321, 452});
}
TEST(TemporalTest, LocalDateTimeMicrosecondsSinceEpochConversion) {
const auto check_microseconds = [](const utils::DateParameters date_parameters,
const utils::LocalTimeParameters &local_time_parameters) {
utils::LocalDateTime initial_local_date_time{date_parameters, local_time_parameters};
const auto microseconds = initial_local_date_time.MicrosecondsSinceEpoch();
utils::LocalDateTime new_local_date_time{microseconds};
ASSERT_EQ(initial_local_date_time, new_local_date_time);
};
check_microseconds(utils::DateParameters{2020, 11, 22}, utils::LocalTimeParameters{0, 0, 0, 0, 0});
check_microseconds(utils::DateParameters{1900, 2, 22}, utils::LocalTimeParameters{0, 0, 0, 0, 0});
check_microseconds(utils::DateParameters{0, 1, 1}, utils::LocalTimeParameters{0, 0, 0, 0, 0});
{
utils::LocalDateTime local_date_time(utils::DateParameters{1970, 1, 1}, utils::LocalTimeParameters{0, 0, 0, 0, 0});
ASSERT_EQ(local_date_time.MicrosecondsSinceEpoch(), 0);
}
{
utils::LocalDateTime local_date_time(utils::DateParameters{1970, 1, 1}, utils::LocalTimeParameters{0, 0, 0, 0, 1});
ASSERT_GT(local_date_time.MicrosecondsSinceEpoch(), 0);
}
{
utils::LocalTimeParameters local_time_parameters{12, 10, 40, 42, 42};
utils::LocalDateTime local_date_time{utils::DateParameters{1970, 1, 1}, local_time_parameters};
ASSERT_EQ(local_date_time.MicrosecondsSinceEpoch(),
utils::LocalTime{local_time_parameters}.MicrosecondsSinceEpoch());
}
{
utils::LocalDateTime local_date_time(utils::DateParameters{1910, 1, 1}, utils::LocalTimeParameters{0, 0, 0, 0, 0});
ASSERT_LT(local_date_time.MicrosecondsSinceEpoch(), 0);
}
{
utils::LocalDateTime local_date_time(utils::DateParameters{2021, 1, 1}, utils::LocalTimeParameters{0, 0, 0, 0, 0});
ASSERT_GT(local_date_time.MicrosecondsSinceEpoch(), 0);
}
}
TEST(TemporalTest, DurationConversion) {
{
utils::Duration duration{{.years = 2.5}};
const auto microseconds = duration.microseconds;
utils::LocalDateTime local_date_time{microseconds};
ASSERT_EQ(local_date_time.date.years, 2 + 1970);
ASSERT_EQ(local_date_time.date.months, 6 + 1);
};
{
utils::Duration duration{{.months = 26}};
const auto microseconds = duration.microseconds;
utils::LocalDateTime local_date_time{microseconds};
ASSERT_EQ(local_date_time.date.years, 2 + 1970);
ASSERT_EQ(local_date_time.date.months, 2 + 1);
};
{
utils::Duration duration{{.minutes = 123.25}};
const auto microseconds = duration.microseconds;
utils::LocalDateTime local_date_time{microseconds};
ASSERT_EQ(local_date_time.date.years, 1970);
ASSERT_EQ(local_date_time.date.months, 1);
ASSERT_EQ(local_date_time.date.days, 1);
ASSERT_EQ(local_date_time.local_time.hours, 2);
ASSERT_EQ(local_date_time.local_time.minutes, 3);
ASSERT_EQ(local_date_time.local_time.seconds, 15);
};
}
namespace {
using namespace std::literals;
constexpr std::array parsing_test_dates_extended{
std::make_pair("2020-11-22"sv, utils::DateParameters{2020, 11, 22}),
std::make_pair("2020-11"sv, utils::DateParameters{2020, 11}),
};
constexpr std::array parsing_test_dates_basic{std::make_pair("20201122"sv, utils::DateParameters{2020, 11, 22})};
constexpr std::array parsing_test_local_time_extended{
std::make_pair("19:23:21.123456"sv, utils::LocalTimeParameters{19, 23, 21, 123, 456}),
std::make_pair("19:23:21.123"sv, utils::LocalTimeParameters{19, 23, 21, 123}),
std::make_pair("19:23:21"sv, utils::LocalTimeParameters{19, 23, 21}),
std::make_pair("19:23"sv, utils::LocalTimeParameters{19, 23}),
std::make_pair("00:00:00.000000"sv, utils::LocalTimeParameters{0, 0, 0, 0, 0}),
std::make_pair("01:02:03.004005"sv, utils::LocalTimeParameters{1, 2, 3, 4, 5}),
};
constexpr std::array parsing_test_local_time_basic{
std::make_pair("192321.123456"sv, utils::LocalTimeParameters{19, 23, 21, 123, 456}),
std::make_pair("192321.123"sv, utils::LocalTimeParameters{19, 23, 21, 123}),
std::make_pair("192321"sv, utils::LocalTimeParameters{19, 23, 21}),
std::make_pair("1923"sv, utils::LocalTimeParameters{19, 23}),
std::make_pair("19"sv, utils::LocalTimeParameters{19}),
std::make_pair("000000.000000"sv, utils::LocalTimeParameters{0, 0, 0, 0, 0}),
std::make_pair("010203.004005"sv, utils::LocalTimeParameters{1, 2, 3, 4, 5}),
};
} // namespace
TEST(TemporalTest, DateParsing) {
for (const auto &[string, date_parameters] : parsing_test_dates_extended) {
ASSERT_EQ(utils::ParseDateParameters(string).first, date_parameters);
}
for (const auto &[string, date_parameters] : parsing_test_dates_basic) {
ASSERT_EQ(utils::ParseDateParameters(string).first, date_parameters);
}
ASSERT_THROW(utils::ParseDateParameters("202-011-22"), utils::BasicException);
ASSERT_THROW(utils::ParseDateParameters("2020-1-022"), utils::BasicException);
ASSERT_THROW(utils::ParseDateParameters("2020-11-2-"), utils::BasicException);
}
TEST(TemporalTest, LocalTimeParsing) {
for (const auto &[string, local_time_parameters] : parsing_test_local_time_extended) {
ASSERT_EQ(utils::ParseLocalTimeParameters(string).first, local_time_parameters) << ToString(local_time_parameters);
ASSERT_EQ(utils::ParseLocalTimeParameters(fmt::format("T{}", string)).first, local_time_parameters)
<< ToString(local_time_parameters);
}
for (const auto &[string, local_time_parameters] : parsing_test_local_time_basic) {
ASSERT_EQ(utils::ParseLocalTimeParameters(string).first, local_time_parameters) << ToString(local_time_parameters);
ASSERT_EQ(utils::ParseLocalTimeParameters(fmt::format("T{}", string)).first, local_time_parameters)
<< ToString(local_time_parameters);
}
ASSERT_THROW(utils::ParseLocalTimeParameters("19:20:21s"), utils::BasicException);
ASSERT_THROW(utils::ParseLocalTimeParameters("1920:21"), utils::BasicException);
}
TEST(TemporalTest, LocalDateTimeParsing) {
const auto check_local_date_time_combinations = [](const auto &dates, const auto &local_times, const bool is_valid) {
for (const auto &[date_string, date_parameters] : dates) {
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);
if (is_valid) {
EXPECT_EQ(utils::ParseLocalDateTimeParameters(local_date_time_string),
(std::pair{date_parameters, local_time_parameters}));
}
}
}
};
check_local_date_time_combinations(parsing_test_dates_basic, parsing_test_local_time_basic, true);
check_local_date_time_combinations(parsing_test_dates_extended, parsing_test_local_time_extended, true);
check_local_date_time_combinations(parsing_test_dates_basic, parsing_test_local_time_extended, false);
check_local_date_time_combinations(parsing_test_dates_extended, parsing_test_local_time_basic, false);
}
void CheckDurationParameters(const auto &values, const auto &expected) {
ASSERT_NEAR(values.years, expected.years, 0.01);
ASSERT_NEAR(values.months, expected.months, 0.01);
ASSERT_NEAR(values.days, expected.days, 0.01);
ASSERT_NEAR(values.hours, expected.hours, 0.01);
ASSERT_NEAR(values.minutes, expected.minutes, 0.01);
ASSERT_NEAR(values.seconds, expected.seconds, 0.01);
}
TEST(TemporalTest, DurationParsing) {
CheckDurationParameters(utils::ParseDurationParameters("P12Y"), utils::DurationParameters{.years = 12.0});
CheckDurationParameters(utils::ParseDurationParameters("P12Y32DT2M"),
utils::DurationParameters{.years = 12.0, .days = 32.0, .minutes = 2.0});
CheckDurationParameters(utils::ParseDurationParameters("PT2M"), utils::DurationParameters{.minutes = 2.0});
CheckDurationParameters(utils::ParseDurationParameters("PT2M3S"),
utils::DurationParameters{.minutes = 2.0, .seconds = 3.0});
CheckDurationParameters(utils::ParseDurationParameters("PT2.5H"), utils::DurationParameters{.hours = 2.5});
CheckDurationParameters(utils::ParseDurationParameters("P2DT2.5H"),
utils::DurationParameters{.days = 2.0, .hours = 2.5});
ASSERT_THROW(utils::ParseDurationParameters("P2M3S"), utils::BasicException);
ASSERT_THROW(utils::ParseDurationParameters("PTM3S"), utils::BasicException);
ASSERT_THROW(utils::ParseDurationParameters("P2M3Y"), utils::BasicException);
ASSERT_THROW(utils::ParseDurationParameters("PT2Y3M"), utils::BasicException);
ASSERT_THROW(utils::ParseDurationParameters("12Y32DT2M"), utils::BasicException);
ASSERT_THROW(utils::ParseDurationParameters("PY"), utils::BasicException);
ASSERT_THROW(utils::ParseDurationParameters(""), utils::BasicException);
ASSERT_THROW(utils::ParseDurationParameters("PT2M3SX"), utils::BasicException);
ASSERT_THROW(utils::ParseDurationParameters("PT2M3S32"), utils::BasicException);
ASSERT_THROW(utils::ParseDurationParameters("PT2.5M3S"), utils::BasicException);
ASSERT_THROW(utils::ParseDurationParameters("PT2.5M3.5S"), utils::BasicException);
CheckDurationParameters(utils::ParseDurationParameters("P20201122T192032"),
utils::DurationParameters{2020, 11, 22, 19, 20, 32});
CheckDurationParameters(utils::ParseDurationParameters("P20201122T192032.333"),
utils::DurationParameters{2020, 11, 22, 19, 20, 32, 333});
CheckDurationParameters(utils::ParseDurationParameters("P2020-11-22T19:20:32"),
utils::DurationParameters{2020, 11, 22, 19, 20, 32});
}