Add support for parsing temporal types (#187)
This commit is contained in:
parent
e628b5ba6e
commit
4e604de9d7
@ -1,5 +1,6 @@
|
||||
---
|
||||
Checks: '*,
|
||||
-abseil-string-find-str-contains,
|
||||
-altera-struct-pack-align,
|
||||
-android-*,
|
||||
-cert-err58-cpp,
|
||||
|
@ -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)
|
||||
|
@ -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 ¶meters) {
|
||||
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
|
@ -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");
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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
675
src/utils/temporal.cpp
Normal 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 ¶meters) {
|
||||
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
|
@ -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 ¶meters);
|
||||
@ -102,4 +116,4 @@ struct DurationHash {
|
||||
size_t operator()(const Duration &duration) const;
|
||||
};
|
||||
|
||||
} // namespace query
|
||||
} // namespace utils
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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 ¶meters) {
|
||||
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);
|
||||
};
|
||||
}
|
291
tests/unit/utils_temporal.cpp
Normal file
291
tests/unit/utils_temporal.cpp
Normal 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 ¶meters) {
|
||||
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});
|
||||
}
|
Loading…
Reference in New Issue
Block a user