Add support for temporal data types in the communication Bolt layer (#198)
This commit is contained in:
parent
4e604de9d7
commit
6913feacc7
@ -30,6 +30,12 @@ enum class Signature : uint8_t {
|
||||
Relationship = 0x52,
|
||||
Path = 0x50,
|
||||
UnboundRelationship = 0x72,
|
||||
|
||||
/// Temporal data types
|
||||
Date = 0x44,
|
||||
Duration = 0x45,
|
||||
LocalDateTime = 0x64,
|
||||
LocalTime = 0x74,
|
||||
};
|
||||
|
||||
enum class Marker : uint8_t {
|
||||
|
@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
#include "communication/bolt/v1/codes.hpp"
|
||||
@ -7,6 +9,7 @@
|
||||
#include "utils/cast.hpp"
|
||||
#include "utils/endian.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/temporal.hpp"
|
||||
|
||||
namespace communication::bolt {
|
||||
|
||||
@ -69,12 +72,37 @@ class Decoder {
|
||||
case Marker::Map16:
|
||||
case Marker::Map32:
|
||||
return ReadMap(marker, data);
|
||||
|
||||
case Marker::TinyStruct1: {
|
||||
uint8_t signature = 0;
|
||||
if (!buffer_.Read(&signature, 1)) {
|
||||
return false;
|
||||
}
|
||||
switch (static_cast<Signature>(signature)) {
|
||||
case Signature::Date:
|
||||
return ReadDate(data);
|
||||
case Signature::LocalTime:
|
||||
return ReadLocalTime(data);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
case Marker::TinyStruct2: {
|
||||
uint8_t signature = 0;
|
||||
if (!buffer_.Read(&signature, 1)) {
|
||||
return false;
|
||||
}
|
||||
switch (static_cast<Signature>(signature)) {
|
||||
case Signature::LocalDateTime:
|
||||
return ReadLocalDateTime(data);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
case Marker::TinyStruct3: {
|
||||
// For tiny struct 3 we will also read the Signature to switch between
|
||||
// vertex, unbounded_edge and path. Note that in those functions we
|
||||
// won't perform an additional signature read.
|
||||
uint8_t signature;
|
||||
uint8_t signature = 0;
|
||||
if (!buffer_.Read(&signature, 1)) {
|
||||
return false;
|
||||
}
|
||||
@ -89,10 +117,30 @@ class Decoder {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
case Marker::TinyStruct5:
|
||||
return ReadEdge(marker, data);
|
||||
|
||||
case Marker::TinyStruct4: {
|
||||
uint8_t signature = 0;
|
||||
if (!buffer_.Read(&signature, 1)) {
|
||||
return false;
|
||||
}
|
||||
switch (static_cast<Signature>(signature)) {
|
||||
case Signature::Duration:
|
||||
return ReadDuration(data);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
case Marker::TinyStruct5: {
|
||||
uint8_t signature = 0;
|
||||
if (!buffer_.Read(&signature, 1)) {
|
||||
return false;
|
||||
}
|
||||
switch (static_cast<Signature>(signature)) {
|
||||
case Signature::Relationship:
|
||||
return ReadEdge(data);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
default:
|
||||
if ((value & 0xF0) == utils::UnderlyingCast(Marker::TinyString)) {
|
||||
return ReadString(marker, data);
|
||||
@ -351,24 +399,11 @@ class Decoder {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadEdge(const Marker &marker, Value *data) {
|
||||
uint8_t value;
|
||||
bool ReadEdge(Value *data) {
|
||||
Value dv;
|
||||
*data = Value(Edge());
|
||||
auto &edge = data->ValueEdge();
|
||||
|
||||
if (!buffer_.Read(&value, 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check header
|
||||
if (marker != Marker::TinyStruct5) {
|
||||
return false;
|
||||
}
|
||||
if (value != utils::UnderlyingCast(Signature::Relationship)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// read ID
|
||||
if (!ReadValue(&dv, Value::Type::Int)) {
|
||||
return false;
|
||||
@ -468,5 +503,81 @@ class Decoder {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadDate(Value *data) {
|
||||
Value dv;
|
||||
if (!ReadValue(&dv, Value::Type::Int)) {
|
||||
return false;
|
||||
}
|
||||
const auto chrono_days = std::chrono::days(dv.ValueInt());
|
||||
const auto sys_days = std::chrono::sys_days(chrono_days);
|
||||
const auto date = std::chrono::year_month_day(sys_days);
|
||||
*data = Value(utils::Date(
|
||||
{static_cast<int>(date.year()), static_cast<unsigned>(date.month()), static_cast<unsigned>(date.day())}));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadLocalTime(Value *data) {
|
||||
Value dv;
|
||||
if (!ReadValue(&dv, Value::Type::Int)) {
|
||||
return false;
|
||||
}
|
||||
namespace chrono = std::chrono;
|
||||
const auto nanos = chrono::nanoseconds(dv.ValueInt());
|
||||
const auto microseconds = chrono::duration_cast<chrono::microseconds>(nanos);
|
||||
*data = Value(utils::LocalTime(microseconds.count()));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadLocalDateTime(Value *data) {
|
||||
Value secs;
|
||||
if (!ReadValue(&secs, Value::Type::Int)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Value nanos;
|
||||
if (!ReadValue(&nanos, Value::Type::Int)) {
|
||||
return false;
|
||||
}
|
||||
namespace chrono = std::chrono;
|
||||
const auto chrono_seconds = chrono::seconds(secs.ValueInt());
|
||||
const auto sys_seconds = chrono::sys_seconds(chrono_seconds);
|
||||
const auto sys_days = chrono::time_point_cast<chrono::days>(sys_seconds);
|
||||
const auto date = chrono::year_month_day(sys_days);
|
||||
|
||||
const auto ldt = utils::Date(
|
||||
{static_cast<int>(date.year()), static_cast<unsigned>(date.month()), static_cast<unsigned>(date.day())});
|
||||
|
||||
auto secs_leftover = chrono::seconds(sys_seconds - sys_days);
|
||||
const auto h = utils::GetAndSubtractDuration<chrono::hours>(secs_leftover);
|
||||
const auto m = utils::GetAndSubtractDuration<chrono::minutes>(secs_leftover);
|
||||
const auto s = secs_leftover.count();
|
||||
auto nanos_leftover = chrono::nanoseconds(nanos.ValueInt());
|
||||
const auto ml = utils::GetAndSubtractDuration<chrono::milliseconds>(nanos_leftover);
|
||||
const auto mi = chrono::duration_cast<chrono::microseconds>(nanos_leftover).count();
|
||||
const auto params = utils::LocalTimeParameters{h, m, s, ml, mi};
|
||||
const auto tm = utils::LocalTime(params);
|
||||
*data = utils::LocalDateTime(ldt, tm);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadDuration(Value *data) {
|
||||
Value dv;
|
||||
std::array<int64_t, 4> values{0};
|
||||
for (auto &val : values) {
|
||||
if (!ReadValue(&dv, Value::Type::Int)) {
|
||||
return false;
|
||||
}
|
||||
val = dv.ValueInt();
|
||||
}
|
||||
namespace chrono = std::chrono;
|
||||
const auto months = chrono::months(values[0]);
|
||||
const auto days = chrono::days(values[1]);
|
||||
const auto secs = chrono::seconds(values[2]);
|
||||
const auto nanos = chrono::nanoseconds(values[3]);
|
||||
const auto micros = months + days + secs + chrono::duration_cast<chrono::microseconds>(nanos);
|
||||
*data = Value(utils::Duration(micros.count()));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
} // namespace communication::bolt
|
||||
|
@ -15,7 +15,8 @@ namespace communication::bolt {
|
||||
|
||||
/**
|
||||
* Bolt BaseEncoder. Has public interfaces for writing Bolt encoded data.
|
||||
* Supported types are: Null, Bool, Int, Double, String, List, Map, Vertex, Edge
|
||||
* Supported types are: Null, Bool, Int, Double, String, List, Map, Vertex,
|
||||
* Edge, Date, LocalDate, LocalDateTime, Duration.
|
||||
*
|
||||
* The purpose of this class is to stream bolt data into the given Buffer.
|
||||
*
|
||||
@ -89,6 +90,7 @@ class BaseEncoder {
|
||||
WriteTypeSize(value.size(), MarkerString);
|
||||
WriteRAW(value.c_str(), value.size());
|
||||
}
|
||||
|
||||
void WriteList(const std::vector<Value> &value) {
|
||||
WriteTypeSize(value.size(), MarkerList);
|
||||
for (auto &x : value) WriteValue(x);
|
||||
@ -168,6 +170,34 @@ class BaseEncoder {
|
||||
for (auto &i : path.indices) WriteInt(i);
|
||||
}
|
||||
|
||||
void WriteDate(const utils::Date &date) {
|
||||
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct1));
|
||||
WriteRAW(utils::UnderlyingCast(Signature::Date));
|
||||
WriteInt(date.DaysSinceEpoch());
|
||||
}
|
||||
|
||||
void WriteLocalTime(const utils::LocalTime &local_time) {
|
||||
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct1));
|
||||
WriteRAW(utils::UnderlyingCast(Signature::LocalTime));
|
||||
WriteInt(local_time.NanosecondsSinceEpoch());
|
||||
}
|
||||
|
||||
void WriteLocalDateTime(const utils::LocalDateTime &local_date_time) {
|
||||
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct2));
|
||||
WriteRAW(utils::UnderlyingCast(Signature::LocalDateTime));
|
||||
WriteInt(local_date_time.SecondsSinceEpoch());
|
||||
WriteInt(local_date_time.SubSecondsAsNanoseconds());
|
||||
}
|
||||
|
||||
void WriteDuration(const utils::Duration &duration) {
|
||||
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct4));
|
||||
WriteRAW(utils::UnderlyingCast(Signature::Duration));
|
||||
WriteInt(duration.Months());
|
||||
WriteInt(duration.SubMonthsAsDays());
|
||||
WriteInt(duration.SubDaysAsSeconds());
|
||||
WriteInt(duration.SubSecondsAsNanoseconds());
|
||||
}
|
||||
|
||||
void WriteValue(const Value &value) {
|
||||
switch (value.type()) {
|
||||
case Value::Type::Null:
|
||||
@ -203,6 +233,18 @@ class BaseEncoder {
|
||||
case Value::Type::Path:
|
||||
WritePath(value.ValuePath());
|
||||
break;
|
||||
case Value::Type::Date:
|
||||
WriteDate(value.ValueDate());
|
||||
break;
|
||||
case Value::Type::LocalTime:
|
||||
WriteLocalTime(value.ValueLocalTime());
|
||||
break;
|
||||
case Value::Type::LocalDateTime:
|
||||
WriteLocalDateTime(value.ValueLocalDateTime());
|
||||
break;
|
||||
case Value::Type::Duration:
|
||||
WriteDuration(value.ValueDuration());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,6 +39,10 @@ DEF_GETTER_BY_REF(Vertex, Vertex, vertex_v)
|
||||
DEF_GETTER_BY_REF(Edge, Edge, edge_v)
|
||||
DEF_GETTER_BY_REF(UnboundedEdge, UnboundedEdge, unbounded_edge_v)
|
||||
DEF_GETTER_BY_REF(Path, Path, path_v)
|
||||
DEF_GETTER_BY_REF(Date, utils::Date, date_v)
|
||||
DEF_GETTER_BY_REF(LocalTime, utils::LocalTime, local_time_v)
|
||||
DEF_GETTER_BY_REF(LocalDateTime, utils::LocalDateTime, local_date_time_v)
|
||||
DEF_GETTER_BY_REF(Duration, utils::Duration, duration_v)
|
||||
|
||||
#undef DEF_GETTER_BY_REF
|
||||
|
||||
@ -76,6 +80,18 @@ Value::Value(const Value &other) : type_(other.type_) {
|
||||
case Type::Path:
|
||||
new (&path_v) Path(other.path_v);
|
||||
return;
|
||||
case Type::Date:
|
||||
new (&date_v) utils::Date(other.date_v);
|
||||
return;
|
||||
case Type::LocalTime:
|
||||
new (&local_time_v) utils::LocalTime(other.local_time_v);
|
||||
return;
|
||||
case Type::LocalDateTime:
|
||||
new (&local_date_time_v) utils::LocalDateTime(other.local_date_time_v);
|
||||
return;
|
||||
case Type::Duration:
|
||||
new (&duration_v) utils::Duration(other.duration_v);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,6 +134,18 @@ Value &Value::operator=(const Value &other) {
|
||||
case Type::Path:
|
||||
new (&path_v) Path(other.path_v);
|
||||
return *this;
|
||||
case Type::Date:
|
||||
new (&date_v) utils::Date(other.date_v);
|
||||
return *this;
|
||||
case Type::LocalTime:
|
||||
new (&local_time_v) utils::LocalTime(other.local_time_v);
|
||||
return *this;
|
||||
case Type::LocalDateTime:
|
||||
new (&local_date_time_v) utils::LocalDateTime(other.local_date_time_v);
|
||||
return *this;
|
||||
case Type::Duration:
|
||||
new (&duration_v) utils::Duration(other.duration_v);
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
@ -157,6 +185,18 @@ Value::Value(Value &&other) noexcept : type_(other.type_) {
|
||||
case Type::Path:
|
||||
new (&path_v) Path(std::move(other.path_v));
|
||||
break;
|
||||
case Type::Date:
|
||||
new (&date_v) utils::Date(other.date_v);
|
||||
break;
|
||||
case Type::LocalTime:
|
||||
new (&local_time_v) utils::LocalTime(other.local_time_v);
|
||||
break;
|
||||
case Type::LocalDateTime:
|
||||
new (&local_date_time_v) utils::LocalDateTime(other.local_date_time_v);
|
||||
break;
|
||||
case Type::Duration:
|
||||
new (&duration_v) utils::Duration(other.duration_v);
|
||||
break;
|
||||
}
|
||||
|
||||
// reset the type of other
|
||||
@ -203,6 +243,18 @@ Value &Value::operator=(Value &&other) noexcept {
|
||||
case Type::Path:
|
||||
new (&path_v) Path(std::move(other.path_v));
|
||||
break;
|
||||
case Type::Date:
|
||||
new (&date_v) utils::Date(other.date_v);
|
||||
break;
|
||||
case Type::LocalTime:
|
||||
new (&local_time_v) utils::LocalTime(other.local_time_v);
|
||||
break;
|
||||
case Type::LocalDateTime:
|
||||
new (&local_date_time_v) utils::LocalDateTime(other.local_date_time_v);
|
||||
break;
|
||||
case Type::Duration:
|
||||
new (&duration_v) utils::Duration(other.duration_v);
|
||||
break;
|
||||
}
|
||||
|
||||
// reset the type of other
|
||||
@ -249,6 +301,18 @@ Value::~Value() {
|
||||
case Type::Path:
|
||||
path_v.~Path();
|
||||
return;
|
||||
case Type::Date:
|
||||
date_v.~Date();
|
||||
return;
|
||||
case Type::LocalTime:
|
||||
local_time_v.~LocalTime();
|
||||
return;
|
||||
case Type::LocalDateTime:
|
||||
local_date_time_v.~LocalDateTime();
|
||||
return;
|
||||
case Type::Duration:
|
||||
duration_v.~Duration();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -341,6 +405,14 @@ std::ostream &operator<<(std::ostream &os, const Value &value) {
|
||||
return os << value.ValueUnboundedEdge();
|
||||
case Value::Type::Path:
|
||||
return os << value.ValuePath();
|
||||
case Value::Type::Date:
|
||||
return os << value.ValueDate();
|
||||
case Value::Type::LocalTime:
|
||||
return os << value.ValueLocalTime();
|
||||
case Value::Type::LocalDateTime:
|
||||
return os << value.ValueLocalDateTime();
|
||||
case Value::Type::Duration:
|
||||
return os << value.ValueDuration();
|
||||
}
|
||||
}
|
||||
|
||||
@ -368,6 +440,14 @@ std::ostream &operator<<(std::ostream &os, const Value::Type type) {
|
||||
return os << "unbounded_edge";
|
||||
case Value::Type::Path:
|
||||
return os << "path";
|
||||
case Value::Type::Date:
|
||||
return os << "date";
|
||||
case Value::Type::LocalTime:
|
||||
return os << "local_time";
|
||||
case Value::Type::LocalDateTime:
|
||||
return os << "local_date_time";
|
||||
case Value::Type::Duration:
|
||||
return os << "duration";
|
||||
}
|
||||
}
|
||||
} // namespace communication::bolt
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include "utils/cast.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/temporal.hpp"
|
||||
|
||||
namespace communication::bolt {
|
||||
|
||||
@ -120,7 +121,23 @@ class Value {
|
||||
Value() : type_(Type::Null) {}
|
||||
|
||||
/** Types that can be stored in a Value. */
|
||||
enum class Type : unsigned { Null, Bool, Int, Double, String, List, Map, Vertex, Edge, UnboundedEdge, Path };
|
||||
enum class Type : unsigned {
|
||||
Null,
|
||||
Bool,
|
||||
Int,
|
||||
Double,
|
||||
String,
|
||||
List,
|
||||
Map,
|
||||
Vertex,
|
||||
Edge,
|
||||
UnboundedEdge,
|
||||
Path,
|
||||
Date,
|
||||
LocalTime,
|
||||
LocalDateTime,
|
||||
Duration
|
||||
};
|
||||
|
||||
// constructors for primitive types
|
||||
Value(bool value) : type_(Type::Bool) { bool_v = value; }
|
||||
@ -139,7 +156,12 @@ class Value {
|
||||
Value(const Edge &value) : type_(Type::Edge) { new (&edge_v) Edge(value); }
|
||||
Value(const UnboundedEdge &value) : type_(Type::UnboundedEdge) { new (&unbounded_edge_v) UnboundedEdge(value); }
|
||||
Value(const Path &value) : type_(Type::Path) { new (&path_v) Path(value); }
|
||||
|
||||
Value(const utils::Date &date) : type_(Type::Date) { new (&date_v) utils::Date(date); }
|
||||
Value(const utils::LocalTime &time) : type_(Type::LocalTime) { new (&local_time_v) utils::LocalTime(time); }
|
||||
Value(const utils::LocalDateTime &date_time) : type_(Type::LocalDateTime) {
|
||||
new (&local_date_time_v) utils::LocalDateTime(date_time);
|
||||
}
|
||||
Value(const utils::Duration &dur) : type_(Type::Duration) { new (&duration_v) utils::Duration(dur); }
|
||||
// move constructors for non-primitive values
|
||||
Value(std::string &&value) noexcept : type_(Type::String) { new (&string_v) std::string(std::move(value)); }
|
||||
Value(std::vector<Value> &&value) noexcept : type_(Type::List) { new (&list_v) std::vector<Value>(std::move(value)); }
|
||||
@ -183,7 +205,10 @@ class Value {
|
||||
DECL_GETTER_BY_REFERENCE(Edge, Edge)
|
||||
DECL_GETTER_BY_REFERENCE(UnboundedEdge, UnboundedEdge)
|
||||
DECL_GETTER_BY_REFERENCE(Path, Path)
|
||||
|
||||
DECL_GETTER_BY_REFERENCE(Date, utils::Date)
|
||||
DECL_GETTER_BY_REFERENCE(LocalTime, utils::LocalTime)
|
||||
DECL_GETTER_BY_REFERENCE(LocalDateTime, utils::LocalDateTime)
|
||||
DECL_GETTER_BY_REFERENCE(Duration, utils::Duration)
|
||||
#undef DECL_GETTER_BY_REFERNCE
|
||||
|
||||
#define TYPE_CHECKER(type) \
|
||||
@ -199,7 +224,10 @@ class Value {
|
||||
TYPE_CHECKER(Edge)
|
||||
TYPE_CHECKER(UnboundedEdge)
|
||||
TYPE_CHECKER(Path)
|
||||
|
||||
TYPE_CHECKER(Date)
|
||||
TYPE_CHECKER(LocalTime)
|
||||
TYPE_CHECKER(LocalDateTime)
|
||||
TYPE_CHECKER(Duration)
|
||||
#undef TYPE_CHECKER
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &os, const Value &value);
|
||||
@ -219,9 +247,12 @@ class Value {
|
||||
Edge edge_v;
|
||||
UnboundedEdge unbounded_edge_v;
|
||||
Path path_v;
|
||||
utils::Date date_v;
|
||||
utils::LocalTime local_time_v;
|
||||
utils::LocalDateTime local_date_time_v;
|
||||
utils::Duration duration_v;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* An exception raised by the Value system.
|
||||
*/
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "storage/v2/edge_accessor.hpp"
|
||||
#include "storage/v2/storage.hpp"
|
||||
#include "storage/v2/vertex_accessor.hpp"
|
||||
#include "utils/temporal.hpp"
|
||||
|
||||
using communication::bolt::Value;
|
||||
|
||||
@ -40,6 +41,14 @@ query::TypedValue ToTypedValue(const Value &value) {
|
||||
case Value::Type::UnboundedEdge:
|
||||
case Value::Type::Path:
|
||||
throw communication::bolt::ValueException("Unsupported conversion from Value to TypedValue");
|
||||
case Value::Type::Date:
|
||||
return query::TypedValue(value.ValueDate());
|
||||
case Value::Type::LocalTime:
|
||||
return query::TypedValue(value.ValueLocalTime());
|
||||
case Value::Type::LocalDateTime:
|
||||
return query::TypedValue(value.ValueLocalDateTime());
|
||||
case Value::Type::Duration:
|
||||
return query::TypedValue(value.ValueDuration());
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,12 +109,13 @@ storage::Result<Value> ToBoltValue(const query::TypedValue &value, const storage
|
||||
return Value(std::move(*maybe_path));
|
||||
}
|
||||
case query::TypedValue::Type::Date:
|
||||
return Value(value.ValueDate());
|
||||
case query::TypedValue::Type::LocalTime:
|
||||
return Value(value.ValueLocalTime());
|
||||
case query::TypedValue::Type::LocalDateTime:
|
||||
return Value(value.ValueLocalDateTime());
|
||||
case query::TypedValue::Type::Duration:
|
||||
// TODO(antonio2368): Change this when Bolt value for temporal types
|
||||
// are implemented
|
||||
LOG_FATAL("Temporal types not yet supported");
|
||||
return Value(value.ValueDuration());
|
||||
}
|
||||
}
|
||||
|
||||
@ -190,6 +200,18 @@ storage::PropertyValue ToPropertyValue(const Value &value) {
|
||||
case Value::Type::UnboundedEdge:
|
||||
case Value::Type::Path:
|
||||
throw communication::bolt::ValueException("Unsupported conversion from Value to PropertyValue");
|
||||
case Value::Type::Date:
|
||||
return storage::PropertyValue(
|
||||
storage::TemporalData(storage::TemporalType::Date, value.ValueDate().MicrosecondsSinceEpoch()));
|
||||
case Value::Type::LocalTime:
|
||||
return storage::PropertyValue(
|
||||
storage::TemporalData(storage::TemporalType::LocalTime, value.ValueLocalTime().MicrosecondsSinceEpoch()));
|
||||
case Value::Type::LocalDateTime:
|
||||
return storage::PropertyValue(storage::TemporalData(storage::TemporalType::LocalDateTime,
|
||||
value.ValueLocalDateTime().MicrosecondsSinceEpoch()));
|
||||
case Value::Type::Duration:
|
||||
return storage::PropertyValue(
|
||||
storage::TemporalData(storage::TemporalType::Duration, value.ValueDuration().microseconds));
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,7 +246,17 @@ Value ToBoltValue(const storage::PropertyValue &value) {
|
||||
return Value(std::move(dv_map));
|
||||
}
|
||||
case storage::PropertyValue::Type::TemporalData:
|
||||
LOG_FATAL("Unsupported type");
|
||||
const auto &type = value.ValueTemporalData();
|
||||
switch (type.type) {
|
||||
case storage::TemporalType::Date:
|
||||
return Value(utils::Date(type.microseconds));
|
||||
case storage::TemporalType::LocalTime:
|
||||
return Value(utils::LocalTime(type.microseconds));
|
||||
case storage::TemporalType::LocalDateTime:
|
||||
return Value(utils::LocalDateTime(type.microseconds));
|
||||
case storage::TemporalType::Duration:
|
||||
return Value(utils::Duration(type.microseconds));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,20 +9,6 @@
|
||||
|
||||
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; }
|
||||
|
||||
@ -48,12 +34,13 @@ std::optional<T> ParseNumber(const std::string_view string, const size_t size) {
|
||||
} // 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);
|
||||
namespace chrono = std::chrono;
|
||||
const auto chrono_micros = chrono::microseconds(microseconds);
|
||||
const auto s_days = chrono::sys_days(chrono::duration_cast<chrono::days>(chrono_micros));
|
||||
const auto date = chrono::year_month_day(s_days);
|
||||
years = static_cast<int>(date.year());
|
||||
months = static_cast<unsigned>(date.month());
|
||||
days = static_cast<unsigned>(date.day());
|
||||
}
|
||||
|
||||
Date::Date(const DateParameters &date_parameters) {
|
||||
@ -159,13 +146,12 @@ std::pair<DateParameters, bool> ParseDateParameters(std::string_view date_string
|
||||
}
|
||||
|
||||
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();
|
||||
namespace chrono = std::chrono;
|
||||
return chrono::duration_cast<chrono::microseconds>(utils::DaysSinceEpoch(years, months, days)).count();
|
||||
}
|
||||
|
||||
int64_t Date::DaysSinceEpoch() const { return utils::DaysSinceEpoch(years, months, days).count(); }
|
||||
|
||||
size_t DateHash::operator()(const Date &date) const {
|
||||
utils::HashCombine<uint64_t, uint64_t> hasher;
|
||||
size_t result = hasher(0, date.years);
|
||||
@ -307,10 +293,14 @@ std::pair<LocalTimeParameters, bool> ParseLocalTimeParameters(std::string_view l
|
||||
|
||||
LocalTime::LocalTime(const int64_t microseconds) {
|
||||
auto chrono_microseconds = std::chrono::microseconds(microseconds);
|
||||
MG_ASSERT(chrono_microseconds.count() >= 0, "Negative LocalTime specified in microseconds");
|
||||
if (chrono_microseconds.count() < 0) {
|
||||
throw utils::BasicException("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");
|
||||
if (parsed_hours > 23) {
|
||||
throw utils::BasicException("Invalid LocalTime specified in microseconds");
|
||||
}
|
||||
|
||||
hours = parsed_hours;
|
||||
minutes = GetAndSubtractDuration<std::chrono::minutes>(chrono_microseconds);
|
||||
@ -334,11 +324,11 @@ LocalTime::LocalTime(const LocalTimeParameters &local_time_parameters) {
|
||||
}
|
||||
|
||||
if (!IsInBounds(0, 999, local_time_parameters.milliseconds)) {
|
||||
throw utils::BasicException("Creating a LocalTime with invalid seconds parameter.");
|
||||
throw utils::BasicException("Creating a LocalTime with invalid milliseconds parameter.");
|
||||
}
|
||||
|
||||
if (!IsInBounds(0, 999, local_time_parameters.microseconds)) {
|
||||
throw utils::BasicException("Creating a LocalTime with invalid seconds parameter.");
|
||||
throw utils::BasicException("Creating a LocalTime with invalid microseconds parameter.");
|
||||
}
|
||||
|
||||
hours = local_time_parameters.hours;
|
||||
@ -348,11 +338,17 @@ LocalTime::LocalTime(const LocalTimeParameters &local_time_parameters) {
|
||||
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();
|
||||
std::chrono::microseconds LocalTime::SumLocalTimeParts() const {
|
||||
namespace chrono = std::chrono;
|
||||
return chrono::hours{hours} + chrono::minutes{minutes} + chrono::seconds{seconds} +
|
||||
chrono::milliseconds{milliseconds} + chrono::microseconds{microseconds};
|
||||
}
|
||||
|
||||
int64_t LocalTime::MicrosecondsSinceEpoch() const { return SumLocalTimeParts().count(); }
|
||||
|
||||
int64_t LocalTime::NanosecondsSinceEpoch() const {
|
||||
namespace chrono = std::chrono;
|
||||
return chrono::duration_cast<chrono::nanoseconds>(SumLocalTimeParts()).count();
|
||||
}
|
||||
|
||||
size_t LocalTimeHash::operator()(const LocalTime &local_time) const {
|
||||
@ -443,6 +439,23 @@ int64_t LocalDateTime::MicrosecondsSinceEpoch() const {
|
||||
return date.MicrosecondsSinceEpoch() + local_time.MicrosecondsSinceEpoch();
|
||||
}
|
||||
|
||||
int64_t LocalDateTime::SecondsSinceEpoch() const {
|
||||
namespace chrono = std::chrono;
|
||||
const auto to_sec = chrono::duration_cast<chrono::seconds>(DaysSinceEpoch(date.years, date.months, date.days));
|
||||
const auto local_time_seconds =
|
||||
chrono::hours(local_time.hours) + chrono::minutes(local_time.minutes) + chrono::seconds(local_time.seconds);
|
||||
return (to_sec + local_time_seconds).count();
|
||||
}
|
||||
|
||||
int64_t LocalDateTime::SubSecondsAsNanoseconds() const {
|
||||
namespace chrono = std::chrono;
|
||||
const auto milli_as_nanos = chrono::duration_cast<chrono::nanoseconds>(chrono::milliseconds(local_time.milliseconds));
|
||||
const auto micros_as_nanos =
|
||||
chrono::duration_cast<chrono::nanoseconds>(chrono::microseconds(local_time.microseconds));
|
||||
|
||||
return (milli_as_nanos + micros_as_nanos).count();
|
||||
}
|
||||
|
||||
LocalDateTime::LocalDateTime(const DateParameters date_parameters, const LocalTimeParameters &local_time_parameters)
|
||||
: date(date_parameters), local_time(local_time_parameters) {}
|
||||
|
||||
@ -643,15 +656,14 @@ DurationParameters ParseDurationParameters(std::string_view string) {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
} // namespace
|
||||
Duration::Duration(int64_t microseconds) { this->microseconds = microseconds; }
|
||||
|
||||
Duration::Duration(const DurationParameters ¶meters) {
|
||||
microseconds = (CastChronoDouble<std::chrono::years, std::chrono::microseconds>(parameters.years) +
|
||||
@ -665,6 +677,35 @@ Duration::Duration(const DurationParameters ¶meters) {
|
||||
.count();
|
||||
}
|
||||
|
||||
int64_t Duration::Months() const {
|
||||
std::chrono::microseconds ms(microseconds);
|
||||
return std::chrono::duration_cast<std::chrono::months>(ms).count();
|
||||
}
|
||||
|
||||
int64_t Duration::SubMonthsAsDays() const {
|
||||
namespace chrono = std::chrono;
|
||||
const auto months = chrono::months(Months());
|
||||
const auto micros = chrono::microseconds(microseconds);
|
||||
return chrono::duration_cast<chrono::days>(micros - months).count();
|
||||
}
|
||||
|
||||
int64_t Duration::SubDaysAsSeconds() const {
|
||||
namespace chrono = std::chrono;
|
||||
const auto months = chrono::months(Months());
|
||||
const auto days = chrono::days(SubMonthsAsDays());
|
||||
const auto micros = chrono::microseconds(microseconds);
|
||||
return chrono::duration_cast<chrono::seconds>(micros - months - days).count();
|
||||
}
|
||||
|
||||
int64_t Duration::SubSecondsAsNanoseconds() const {
|
||||
namespace chrono = std::chrono;
|
||||
const auto months = chrono::months(Months());
|
||||
const auto days = chrono::days(SubMonthsAsDays());
|
||||
const auto secs = chrono::seconds(SubDaysAsSeconds());
|
||||
const auto micros = chrono::microseconds(microseconds);
|
||||
return chrono::duration_cast<chrono::nanoseconds>(micros - months - days - secs).count();
|
||||
}
|
||||
|
||||
Duration Duration::operator-() const {
|
||||
Duration result{-microseconds};
|
||||
return result;
|
||||
|
@ -1,12 +1,29 @@
|
||||
#pragma once
|
||||
#include <chrono>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
|
||||
#include "fmt/format.h"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
namespace utils {
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
struct DateParameters {
|
||||
int64_t years{0};
|
||||
int64_t months{1};
|
||||
@ -24,7 +41,13 @@ struct Date {
|
||||
explicit Date(int64_t microseconds);
|
||||
explicit Date(const DateParameters &date_parameters);
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &os, const Date &date) {
|
||||
return os << fmt::format("{:0>2}-{:0>2}-{:0>2}", date.years, static_cast<int>(date.months),
|
||||
static_cast<int>(date.days));
|
||||
}
|
||||
|
||||
int64_t MicrosecondsSinceEpoch() const;
|
||||
int64_t DaysSinceEpoch() const;
|
||||
|
||||
auto operator<=>(const Date &) const = default;
|
||||
|
||||
@ -55,10 +78,23 @@ struct LocalTime {
|
||||
explicit LocalTime(int64_t microseconds);
|
||||
explicit LocalTime(const LocalTimeParameters &local_time_parameters);
|
||||
|
||||
std::chrono::microseconds SumLocalTimeParts() const;
|
||||
|
||||
// Epoch means the start of the day, i,e, midnight
|
||||
int64_t MicrosecondsSinceEpoch() const;
|
||||
int64_t NanosecondsSinceEpoch() const;
|
||||
|
||||
auto operator<=>(const LocalTime &) const = default;
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &os, const LocalTime <) {
|
||||
namespace chrono = std::chrono;
|
||||
using milli = chrono::milliseconds;
|
||||
using micro = chrono::microseconds;
|
||||
const auto subseconds = milli(lt.milliseconds) + micro(lt.microseconds);
|
||||
return os << fmt::format("{:0>2}:{:0>2}:{:0>2}.{:0>6}", static_cast<int>(lt.hours), static_cast<int>(lt.minutes),
|
||||
static_cast<int>(lt.seconds), subseconds.count());
|
||||
}
|
||||
|
||||
uint8_t hours;
|
||||
uint8_t minutes;
|
||||
uint8_t seconds;
|
||||
@ -74,12 +110,21 @@ std::pair<DateParameters, LocalTimeParameters> ParseLocalDateTimeParameters(std:
|
||||
|
||||
struct LocalDateTime {
|
||||
explicit LocalDateTime(int64_t microseconds);
|
||||
explicit LocalDateTime(DateParameters date, const LocalTimeParameters &local_time);
|
||||
explicit LocalDateTime(DateParameters date_parameters, const LocalTimeParameters &local_time_parameters);
|
||||
|
||||
LocalDateTime(const Date &dt, const LocalTime <) : date(dt), local_time(lt) {}
|
||||
|
||||
int64_t MicrosecondsSinceEpoch() const;
|
||||
int64_t SecondsSinceEpoch() const; // seconds since epoch
|
||||
int64_t SubSecondsAsNanoseconds() const;
|
||||
|
||||
auto operator<=>(const LocalDateTime &) const = default;
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &os, const LocalDateTime &ldt) {
|
||||
os << ldt.date << 'T' << ldt.local_time;
|
||||
return os;
|
||||
}
|
||||
|
||||
Date date;
|
||||
LocalTime local_time;
|
||||
};
|
||||
@ -107,6 +152,24 @@ struct Duration {
|
||||
|
||||
auto operator<=>(const Duration &) const = default;
|
||||
|
||||
int64_t Months() const;
|
||||
int64_t SubMonthsAsDays() const;
|
||||
int64_t SubDaysAsSeconds() const;
|
||||
int64_t SubSecondsAsNanoseconds() const;
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &os, const Duration &dur) {
|
||||
// ISO 8601 extended format: P[YYYY]-[MM]-[DD]T[hh]:[mm]:[ss].
|
||||
namespace chrono = std::chrono;
|
||||
auto micros = chrono::microseconds(dur.microseconds);
|
||||
const auto y = GetAndSubtractDuration<chrono::years>(micros);
|
||||
const auto mo = GetAndSubtractDuration<chrono::months>(micros);
|
||||
const auto dd = GetAndSubtractDuration<chrono::days>(micros);
|
||||
const auto h = GetAndSubtractDuration<chrono::hours>(micros);
|
||||
const auto m = GetAndSubtractDuration<chrono::minutes>(micros);
|
||||
const auto s = GetAndSubtractDuration<chrono::seconds>(micros);
|
||||
return os << fmt::format("P{:0>4}-{:0>2}-{:0>2}T{:0>2}:{:0>2}:{:0>2}.{:0>6}", y, mo, dd, h, m, s, micros.count());
|
||||
}
|
||||
|
||||
Duration operator-() const;
|
||||
|
||||
int64_t microseconds;
|
||||
@ -116,4 +179,10 @@ struct DurationHash {
|
||||
size_t operator()(const Duration &duration) const;
|
||||
};
|
||||
|
||||
constexpr std::chrono::days DaysSinceEpoch(uint16_t years, uint8_t months, uint8_t days) {
|
||||
namespace chrono = std::chrono;
|
||||
const auto ymd = chrono::year_month_day(chrono::year(years), chrono::month(months), chrono::day(days));
|
||||
return chrono::sys_days{ymd}.time_since_epoch();
|
||||
}
|
||||
|
||||
} // namespace utils
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include <bit>
|
||||
|
||||
#include "bolt_common.hpp"
|
||||
#include "bolt_testdata.hpp"
|
||||
|
||||
#include "communication/bolt/v1/decoder/decoder.hpp"
|
||||
|
||||
using communication::bolt::Value;
|
||||
@ -434,3 +435,258 @@ TEST_F(BoltDecoder, Edge) {
|
||||
ASSERT_EQ(edge.type, std::string("a"));
|
||||
ASSERT_EQ(edge.properties[std::string("a")].ValueInt(), 1);
|
||||
}
|
||||
|
||||
// Temporal types testing starts here
|
||||
|
||||
template <typename T>
|
||||
constexpr uint8_t Cast(T marker) {
|
||||
return static_cast<uint8_t>(marker);
|
||||
}
|
||||
|
||||
void AssertThatDatesAreEqual(const utils::Date &d1, const utils::Date &d2) {
|
||||
ASSERT_EQ(d1.days, d2.days);
|
||||
ASSERT_EQ(d1.months, d2.months);
|
||||
ASSERT_EQ(d1.years, d2.years);
|
||||
}
|
||||
|
||||
TEST_F(BoltDecoder, DateOld) {
|
||||
TestDecoderBuffer buffer;
|
||||
DecoderT decoder(buffer);
|
||||
|
||||
Value dv;
|
||||
|
||||
using Marker = communication::bolt::Marker;
|
||||
using Sig = communication::bolt::Signature;
|
||||
const auto date = utils::Date({1970, 1, 1});
|
||||
const auto days = date.DaysSinceEpoch();
|
||||
ASSERT_EQ(days, 0);
|
||||
// clang-format off
|
||||
std::array<uint8_t, 3> data = {
|
||||
Cast(Marker::TinyStruct1),
|
||||
Cast(Sig::Date),
|
||||
0x0 };
|
||||
// clang-format on
|
||||
buffer.Clear();
|
||||
buffer.Write(data.data(), data.size());
|
||||
ASSERT_EQ(decoder.ReadValue(&dv, Value::Type::Date), true);
|
||||
AssertThatDatesAreEqual(dv.ValueDate(), date);
|
||||
}
|
||||
|
||||
TEST_F(BoltDecoder, DateRecent) {
|
||||
TestDecoderBuffer buffer;
|
||||
DecoderT decoder(buffer);
|
||||
Value dv;
|
||||
using Marker = communication::bolt::Marker;
|
||||
using Sig = communication::bolt::Signature;
|
||||
const auto date = utils::Date({2021, 7, 20});
|
||||
const auto days = date.DaysSinceEpoch();
|
||||
ASSERT_EQ(days, 18828);
|
||||
const auto *d_bytes = std::bit_cast<const uint8_t *>(&days);
|
||||
// clang-format off
|
||||
std::array<uint8_t, 7> data = {
|
||||
Cast(Marker::TinyStruct1),
|
||||
Cast(Sig::Date),
|
||||
Cast(Marker::Int32),
|
||||
d_bytes[3],
|
||||
d_bytes[2],
|
||||
d_bytes[1],
|
||||
d_bytes[0] };
|
||||
// clang-format on
|
||||
buffer.Clear();
|
||||
buffer.Write(data.data(), data.size());
|
||||
ASSERT_EQ(decoder.ReadValue(&dv, Value::Type::Date), true);
|
||||
AssertThatDatesAreEqual(dv.ValueDate(), date);
|
||||
}
|
||||
|
||||
TEST_F(BoltDecoder, DurationOneSec) {
|
||||
TestDecoderBuffer buffer;
|
||||
DecoderT decoder(buffer);
|
||||
Value dv;
|
||||
const auto value = Value(utils::Duration(1));
|
||||
const auto &dur = value.ValueDuration();
|
||||
const auto nanos = dur.SubSecondsAsNanoseconds();
|
||||
ASSERT_EQ(dur.Months(), 0);
|
||||
ASSERT_EQ(dur.SubMonthsAsDays(), 0);
|
||||
ASSERT_EQ(dur.SubDaysAsSeconds(), 0);
|
||||
ASSERT_EQ(nanos, 1000);
|
||||
const auto *n_bytes = std::bit_cast<const uint8_t *>(&nanos);
|
||||
using Marker = communication::bolt::Marker;
|
||||
using Sig = communication::bolt::Signature;
|
||||
// clang-format off
|
||||
std::array<uint8_t, 8> data = {
|
||||
Cast(Marker::TinyStruct4),
|
||||
Cast(Sig::Duration),
|
||||
0x0,
|
||||
0x0,
|
||||
0x0,
|
||||
Cast(Marker::Int16),
|
||||
n_bytes[1], n_bytes[0] };
|
||||
// clang-format on
|
||||
buffer.Clear();
|
||||
buffer.Write(data.data(), data.size());
|
||||
ASSERT_EQ(decoder.ReadValue(&dv, Value::Type::Duration), true);
|
||||
ASSERT_EQ(dv.ValueDuration().microseconds, dur.microseconds);
|
||||
}
|
||||
|
||||
TEST_F(BoltDecoder, DurationMinusOneSec) {
|
||||
TestDecoderBuffer buffer;
|
||||
DecoderT decoder(buffer);
|
||||
Value dv;
|
||||
const auto value = Value(utils::Duration(-1));
|
||||
const auto &dur = value.ValueDuration();
|
||||
const auto nanos = dur.SubSecondsAsNanoseconds();
|
||||
ASSERT_EQ(dur.Months(), 0);
|
||||
ASSERT_EQ(dur.SubMonthsAsDays(), 0);
|
||||
ASSERT_EQ(dur.SubDaysAsSeconds(), 0);
|
||||
ASSERT_EQ(nanos, -1000);
|
||||
const auto *n_bytes = std::bit_cast<const uint8_t *>(&nanos);
|
||||
using Marker = communication::bolt::Marker;
|
||||
using Sig = communication::bolt::Signature;
|
||||
// clang-format off
|
||||
std::array<uint8_t, 8> data = {
|
||||
Cast(Marker::TinyStruct4),
|
||||
Cast(Sig::Duration),
|
||||
0x0,
|
||||
0x0,
|
||||
0x0,
|
||||
Cast(Marker::Int16),
|
||||
n_bytes[1], n_bytes[0] };
|
||||
// clang-format on
|
||||
buffer.Clear();
|
||||
buffer.Write(data.data(), data.size());
|
||||
ASSERT_EQ(decoder.ReadValue(&dv, Value::Type::Duration), true);
|
||||
ASSERT_EQ(dv.ValueDuration().microseconds, dur.microseconds);
|
||||
}
|
||||
|
||||
TEST_F(BoltDecoder, ArbitraryDuration) {
|
||||
TestDecoderBuffer buffer;
|
||||
DecoderT decoder(buffer);
|
||||
Value dv;
|
||||
const auto value = Value(utils::Duration({1, 1, 1, 1, 1, 1, 1, 0}));
|
||||
const auto &dur = value.ValueDuration();
|
||||
ASSERT_EQ(dur.Months(), 13);
|
||||
ASSERT_EQ(dur.SubMonthsAsDays(), 1);
|
||||
const auto secs = dur.SubDaysAsSeconds();
|
||||
ASSERT_EQ(secs, 3661);
|
||||
const auto *sec_bytes = std::bit_cast<const uint8_t *>(&secs);
|
||||
const auto nanos = dur.SubSecondsAsNanoseconds();
|
||||
ASSERT_EQ(nanos, 1000000);
|
||||
const auto *nano_bytes = std::bit_cast<const uint8_t *>(&nanos);
|
||||
using Marker = communication::bolt::Marker;
|
||||
using Sig = communication::bolt::Signature;
|
||||
// clang-format off
|
||||
std::array<uint8_t, 12> data = {
|
||||
Cast(Marker::TinyStruct4),
|
||||
Cast(Sig::Duration),
|
||||
0xD,
|
||||
0x1,
|
||||
Cast(Marker::Int16),
|
||||
sec_bytes[1],
|
||||
sec_bytes[0],
|
||||
Cast(Marker::Int32),
|
||||
nano_bytes[3],
|
||||
nano_bytes[2],
|
||||
nano_bytes[1],
|
||||
nano_bytes[0] };
|
||||
|
||||
// clang-format on
|
||||
buffer.Clear();
|
||||
buffer.Write(data.data(), data.size());
|
||||
ASSERT_EQ(decoder.ReadValue(&dv, Value::Type::Duration), true);
|
||||
ASSERT_EQ(dv.ValueDuration().microseconds, dur.microseconds);
|
||||
}
|
||||
|
||||
void AssertThatLocalTimeIsEqual(utils::LocalTime t1, utils::LocalTime t2) {
|
||||
ASSERT_EQ(t1.hours, t2.hours);
|
||||
ASSERT_EQ(t1.minutes, t2.minutes);
|
||||
ASSERT_EQ(t1.seconds, t2.seconds);
|
||||
ASSERT_EQ(t1.microseconds, t2.microseconds);
|
||||
ASSERT_EQ(t1.milliseconds, t2.milliseconds);
|
||||
}
|
||||
|
||||
TEST_F(BoltDecoder, LocalTimeOneMicro) {
|
||||
TestDecoderBuffer buffer;
|
||||
DecoderT decoder(buffer);
|
||||
Value dv;
|
||||
const auto value = Value(utils::LocalTime(1));
|
||||
const auto &local_time = value.ValueLocalTime();
|
||||
const auto nanos = local_time.NanosecondsSinceEpoch();
|
||||
ASSERT_EQ(nanos, 1000);
|
||||
const auto *n_bytes = std::bit_cast<const uint8_t *>(&nanos);
|
||||
using Marker = communication::bolt::Marker;
|
||||
using Sig = communication::bolt::Signature;
|
||||
// clang-format off
|
||||
std::array<uint8_t, 5> data = {
|
||||
Cast(Marker::TinyStruct1),
|
||||
Cast(Sig::LocalTime),
|
||||
Cast(Marker::Int16),
|
||||
n_bytes[1],
|
||||
n_bytes[0] };
|
||||
// clang-format on
|
||||
buffer.Clear();
|
||||
buffer.Write(data.data(), data.size());
|
||||
ASSERT_EQ(decoder.ReadValue(&dv, Value::Type::LocalTime), true);
|
||||
AssertThatLocalTimeIsEqual(dv.ValueLocalTime(), local_time);
|
||||
}
|
||||
|
||||
TEST_F(BoltDecoder, LocalTimeOneThousandMicro) {
|
||||
TestDecoderBuffer buffer;
|
||||
DecoderT decoder(buffer);
|
||||
Value dv;
|
||||
const auto value = Value(utils::LocalTime(1000));
|
||||
const auto &local_time = value.ValueLocalTime();
|
||||
const auto nanos = local_time.NanosecondsSinceEpoch();
|
||||
ASSERT_EQ(nanos, 1000000);
|
||||
const auto *n_bytes = std::bit_cast<const uint8_t *>(&nanos);
|
||||
using Marker = communication::bolt::Marker;
|
||||
using Sig = communication::bolt::Signature;
|
||||
// clang-format off
|
||||
std::array<uint8_t, 7> data = {
|
||||
Cast(Marker::TinyStruct1),
|
||||
Cast(Sig::LocalTime),
|
||||
Cast(Marker::Int32),
|
||||
n_bytes[3], n_bytes[2],
|
||||
n_bytes[1], n_bytes[0] };
|
||||
// clang-format on
|
||||
buffer.Clear();
|
||||
buffer.Write(data.data(), data.size());
|
||||
ASSERT_EQ(decoder.ReadValue(&dv, Value::Type::LocalTime), true);
|
||||
AssertThatLocalTimeIsEqual(dv.ValueLocalTime(), local_time);
|
||||
}
|
||||
|
||||
TEST_F(BoltDecoder, LocalDateTime) {
|
||||
TestDecoderBuffer buffer;
|
||||
DecoderT decoder(buffer);
|
||||
|
||||
Value dv;
|
||||
|
||||
const auto local_time = utils::LocalTime(utils::LocalTimeParameters({0, 0, 30, 1, 0}));
|
||||
const auto date = utils::Date(1);
|
||||
const auto value = Value(utils::LocalDateTime(date, local_time));
|
||||
const auto local_date_time = value.ValueLocalDateTime();
|
||||
const auto secs = local_date_time.SecondsSinceEpoch();
|
||||
ASSERT_EQ(secs, 30);
|
||||
const auto *sec_bytes = std::bit_cast<const uint8_t *>(&secs);
|
||||
const auto nanos = local_date_time.SubSecondsAsNanoseconds();
|
||||
ASSERT_EQ(nanos, 1000000);
|
||||
const auto *nano_bytes = std::bit_cast<const uint8_t *>(&nanos);
|
||||
using Marker = communication::bolt::Marker;
|
||||
using Sig = communication::bolt::Signature;
|
||||
// clang-format off
|
||||
std::array<uint8_t, 8> data = {
|
||||
Cast(Marker::TinyStruct2),
|
||||
Cast(Sig::LocalDateTime),
|
||||
// Seconds
|
||||
sec_bytes[0],
|
||||
// Nanoseconds
|
||||
Cast(Marker::Int32),
|
||||
nano_bytes[3], nano_bytes[2],
|
||||
nano_bytes[1], nano_bytes[0] };
|
||||
|
||||
// clang-format on
|
||||
buffer.Clear();
|
||||
buffer.Write(data.data(), data.size());
|
||||
ASSERT_EQ(decoder.ReadValue(&dv, Value::Type::LocalDateTime), true);
|
||||
AssertThatDatesAreEqual(dv.ValueLocalDateTime().date, local_date_time.date);
|
||||
AssertThatLocalTimeIsEqual(dv.ValueLocalDateTime().local_time, local_date_time.local_time);
|
||||
}
|
||||
|
@ -1,10 +1,13 @@
|
||||
#include <array>
|
||||
#include <bit>
|
||||
|
||||
#include "bolt_common.hpp"
|
||||
#include "bolt_testdata.hpp"
|
||||
|
||||
#include "communication/bolt/v1/codes.hpp"
|
||||
#include "communication/bolt/v1/encoder/encoder.hpp"
|
||||
#include "glue/communication.hpp"
|
||||
#include "storage/v2/storage.hpp"
|
||||
|
||||
#include "utils/temporal.hpp"
|
||||
using communication::bolt::Value;
|
||||
|
||||
/**
|
||||
@ -240,10 +243,269 @@ TEST_F(BoltEncoder, BoltV1ExampleMessages) {
|
||||
fvals.insert(std::make_pair(fk2, ftv2));
|
||||
bolt_encoder.MessageFailure(fvals);
|
||||
CheckOutput(output,
|
||||
(const uint8_t *) "\xB1\x7F\xA2\x84\x63\x6F\x64\x65\xD0\x25\x4E\x65\x6F\x2E\x43\x6C\x69\x65\x6E\x74\x45\x72\x72\x6F\x72\x2E\x53\x74\x61\x74\x65\x6D\x65\x6E\x74\x2E\x53\x79\x6E\x74\x61\x78\x45\x72\x72\x6F\x72\x87\x6D\x65\x73\x73\x61\x67\x65\x8F\x49\x6E\x76\x61\x6C\x69\x64\x20\x73\x79\x6E\x74\x61\x78\x2E",
|
||||
(const uint8_t *)
|
||||
"\xB1\x7F\xA2\x84\x63\x6F\x64\x65\xD0\x25\x4E\x65\x6F\x2E\x43\x6C\x69\x65\x6E\x74\x45\x72\x72\x6F\x72\x2E\x53\x74\x61\x74\x65\x6D\x65\x6E\x74\x2E\x53\x79\x6E\x74\x61\x78\x45\x72\x72\x6F\x72\x87\x6D\x65\x73\x73\x61\x67\x65\x8F\x49\x6E\x76\x61\x6C\x69\x64\x20\x73\x79\x6E\x74\x61\x78\x2E",
|
||||
71);
|
||||
|
||||
// ignored message
|
||||
bolt_encoder.MessageIgnored();
|
||||
CheckOutput(output, (const uint8_t *)"\xB0\x7E", 2);
|
||||
}
|
||||
|
||||
// Temporal types testing starts here
|
||||
template <typename T>
|
||||
constexpr uint8_t Cast(T marker) {
|
||||
return static_cast<uint8_t>(marker);
|
||||
}
|
||||
|
||||
TEST_F(BoltEncoder, DateOld) {
|
||||
output.clear();
|
||||
std::vector<Value> vals;
|
||||
const auto value = Value(utils::Date({1970, 1, 1}));
|
||||
vals.push_back(value);
|
||||
ASSERT_EQ(bolt_encoder.MessageRecord(vals), true);
|
||||
const auto &date = value.ValueDate();
|
||||
const auto days = date.DaysSinceEpoch();
|
||||
ASSERT_EQ(days, 0);
|
||||
const auto *d_bytes = std::bit_cast<const uint8_t *>(&days);
|
||||
// 0x91 denotes the size of vals (it's 0x91 because it's anded -- see
|
||||
// WriteTypeSize() in base_encoder.hpp).
|
||||
// We reverse the order of d_bytes because after the encoding
|
||||
// it has BigEndian orderring.
|
||||
using Marker = communication::bolt::Marker;
|
||||
using Sig = communication::bolt::Signature;
|
||||
// clang-format off
|
||||
const auto expected = std::array<uint8_t, 6> {
|
||||
Cast(Marker::TinyStruct1),
|
||||
Cast(Sig::Record),
|
||||
0x91,
|
||||
Cast(Marker::TinyStruct1),
|
||||
Cast(Sig::Date),
|
||||
d_bytes[0] };
|
||||
// clang-format on
|
||||
CheckOutput(output, expected.data(), expected.size());
|
||||
}
|
||||
|
||||
TEST_F(BoltEncoder, DateRecent) {
|
||||
output.clear();
|
||||
std::vector<Value> vals;
|
||||
const auto value = Value(utils::Date({2021, 7, 20}));
|
||||
vals.push_back(value);
|
||||
ASSERT_EQ(bolt_encoder.MessageRecord(vals), true);
|
||||
const auto &date = value.ValueDate();
|
||||
const auto days = date.DaysSinceEpoch();
|
||||
ASSERT_EQ(days, 18828);
|
||||
const auto *d_bytes = std::bit_cast<const uint8_t *>(&days);
|
||||
// 0x91 denotes the size of vals (it's 0x91 because it's anded -- see
|
||||
// WriteTypeSize() in base_encoder.hpp).
|
||||
// We reverse the order of d_bytes because after the encoding
|
||||
// it has BigEndian orderring.
|
||||
using Marker = communication::bolt::Marker;
|
||||
using Sig = communication::bolt::Signature;
|
||||
// clang-format off
|
||||
const auto expected = std::array<uint8_t, 8> {
|
||||
Cast(Marker::TinyStruct1),
|
||||
Cast(Sig::Record),
|
||||
0x91,
|
||||
Cast(Marker::TinyStruct1),
|
||||
Cast(Sig::Date),
|
||||
Cast(Marker::Int16),
|
||||
d_bytes[1],
|
||||
d_bytes[0] };
|
||||
// clang-format on
|
||||
CheckOutput(output, expected.data(), expected.size());
|
||||
}
|
||||
|
||||
TEST_F(BoltEncoder, DurationOneSec) {
|
||||
output.clear();
|
||||
std::vector<Value> vals;
|
||||
const auto value = Value(utils::Duration(1));
|
||||
vals.push_back(value);
|
||||
ASSERT_EQ(bolt_encoder.MessageRecord(vals), true);
|
||||
const auto &dur = value.ValueDuration();
|
||||
ASSERT_EQ(dur.Months(), 0);
|
||||
ASSERT_EQ(dur.SubMonthsAsDays(), 0);
|
||||
ASSERT_EQ(dur.SubDaysAsSeconds(), 0);
|
||||
const auto nanos = dur.SubSecondsAsNanoseconds();
|
||||
ASSERT_EQ(nanos, 1000);
|
||||
const auto *d_bytes = std::bit_cast<const uint8_t *>(&nanos);
|
||||
// 0x91 denotes the size of vals (it's 0x91 because it's anded -- see
|
||||
// WriteTypeSize in base_encoder.hpp).
|
||||
using Marker = communication::bolt::Marker;
|
||||
using Sig = communication::bolt::Signature;
|
||||
// clang-format off
|
||||
const auto expected = std::array<uint8_t, 11> {
|
||||
Cast(Marker::TinyStruct1),
|
||||
Cast(Sig::Record),
|
||||
0x91,
|
||||
Cast(Marker::TinyStruct4),
|
||||
Cast(Sig::Duration),
|
||||
0x0,
|
||||
0x0,
|
||||
0x0,
|
||||
Cast(Marker::Int16),
|
||||
d_bytes[1],
|
||||
d_bytes[0] };
|
||||
// clang-format on
|
||||
CheckOutput(output, expected.data(), expected.size());
|
||||
}
|
||||
|
||||
TEST_F(BoltEncoder, DurationMinusOneSec) {
|
||||
output.clear();
|
||||
std::vector<Value> vals;
|
||||
const auto value = Value(utils::Duration(-1));
|
||||
vals.push_back(value);
|
||||
ASSERT_EQ(bolt_encoder.MessageRecord(vals), true);
|
||||
const auto &dur = value.ValueDuration();
|
||||
ASSERT_EQ(dur.Months(), 0);
|
||||
ASSERT_EQ(dur.SubMonthsAsDays(), 0);
|
||||
ASSERT_EQ(dur.SubDaysAsSeconds(), 0);
|
||||
const auto nanos = dur.SubSecondsAsNanoseconds();
|
||||
const auto *d_bytes = std::bit_cast<const uint8_t *>(&nanos);
|
||||
ASSERT_EQ(nanos, -1000);
|
||||
// 0x91 denotes the size of vals (it's 0x91 because it's anded -- see
|
||||
// WriteTypeSize in base_encoder.hpp).
|
||||
using Marker = communication::bolt::Marker;
|
||||
using Sig = communication::bolt::Signature;
|
||||
// clang-format off
|
||||
const auto expected = std::array<uint8_t, 11> {
|
||||
Cast(Marker::TinyStruct1),
|
||||
Cast(Sig::Record),
|
||||
0x91,
|
||||
Cast(Marker::TinyStruct4),
|
||||
Cast(Sig::Duration),
|
||||
0x0,
|
||||
0x0,
|
||||
0x0,
|
||||
Cast(Marker::Int16),
|
||||
d_bytes[1],
|
||||
d_bytes[0] };
|
||||
// clang-format on
|
||||
CheckOutput(output, expected.data(), expected.size());
|
||||
}
|
||||
|
||||
TEST_F(BoltEncoder, ArbitraryDuration) {
|
||||
output.clear();
|
||||
std::vector<Value> vals;
|
||||
const auto value = Value(utils::Duration({1, 1, 1, 1, 1, 1, 1, 0}));
|
||||
vals.push_back(value);
|
||||
ASSERT_EQ(bolt_encoder.MessageRecord(vals), true);
|
||||
const auto &dur = value.ValueDuration();
|
||||
ASSERT_EQ(dur.Months(), 13);
|
||||
ASSERT_EQ(dur.SubMonthsAsDays(), 1);
|
||||
const auto secs = dur.SubDaysAsSeconds();
|
||||
ASSERT_EQ(secs, 3661);
|
||||
const auto *sec_bytes = std::bit_cast<const uint8_t *>(&secs);
|
||||
const auto nanos = dur.SubSecondsAsNanoseconds();
|
||||
ASSERT_EQ(nanos, 1000000);
|
||||
const auto *nano_bytes = std::bit_cast<const uint8_t *>(&nanos);
|
||||
// 0x91 denotes the size of vals (it's 0x91 because it's anded -- see
|
||||
// WriteTypeSize in base_encoder.hpp).
|
||||
using Marker = communication::bolt::Marker;
|
||||
using Sig = communication::bolt::Signature;
|
||||
// clang-format off
|
||||
const auto expected = std::array<uint8_t, 15> {
|
||||
Cast(Marker::TinyStruct1),
|
||||
Cast(Sig::Record),
|
||||
0x91,
|
||||
Cast(Marker::TinyStruct4),
|
||||
Cast(Sig::Duration),
|
||||
0xD,
|
||||
0x1,
|
||||
Cast(Marker::Int16),
|
||||
sec_bytes[1],
|
||||
sec_bytes[0],
|
||||
Cast(Marker::Int32),
|
||||
nano_bytes[3],
|
||||
nano_bytes[2],
|
||||
nano_bytes[1],
|
||||
nano_bytes[0] };
|
||||
// clang-format on
|
||||
CheckOutput(output, expected.data(), expected.size());
|
||||
}
|
||||
|
||||
TEST_F(BoltEncoder, LocalTimeOneMicro) {
|
||||
output.clear();
|
||||
std::vector<Value> vals;
|
||||
const auto value = Value(utils::LocalTime(1));
|
||||
vals.push_back(value);
|
||||
ASSERT_EQ(bolt_encoder.MessageRecord(vals), true);
|
||||
const auto &local_time = value.ValueLocalTime();
|
||||
const auto nanos = local_time.NanosecondsSinceEpoch();
|
||||
ASSERT_EQ(nanos, 1000);
|
||||
const auto *n_bytes = std::bit_cast<const uint8_t *>(&nanos);
|
||||
using Marker = communication::bolt::Marker;
|
||||
using Sig = communication::bolt::Signature;
|
||||
// clang-format off
|
||||
const auto expected = std::array<uint8_t, 8> {
|
||||
Cast(Marker::TinyStruct1),
|
||||
Cast(Sig::Record),
|
||||
0x91,
|
||||
Cast(Marker::TinyStruct1),
|
||||
Cast(Sig::LocalTime),
|
||||
Cast(Marker::Int16),
|
||||
n_bytes[1], n_bytes[0] };
|
||||
// clang-format on
|
||||
CheckOutput(output, expected.data(), expected.size());
|
||||
}
|
||||
|
||||
TEST_F(BoltEncoder, LocalTimeOneThousandMicro) {
|
||||
output.clear();
|
||||
std::vector<Value> vals;
|
||||
const auto value = Value(utils::LocalTime(1000));
|
||||
vals.push_back(value);
|
||||
ASSERT_EQ(bolt_encoder.MessageRecord(vals), true);
|
||||
const auto &local_time = value.ValueLocalTime();
|
||||
const auto nanos = local_time.NanosecondsSinceEpoch();
|
||||
ASSERT_EQ(nanos, 1000000);
|
||||
const auto *n_bytes = std::bit_cast<const uint8_t *>(&nanos);
|
||||
using Marker = communication::bolt::Marker;
|
||||
using Sig = communication::bolt::Signature;
|
||||
// clang-format off
|
||||
const auto expected = std::array<uint8_t, 10> {
|
||||
Cast(Marker::TinyStruct1),
|
||||
Cast(Sig::Record),
|
||||
0x91,
|
||||
Cast(Marker::TinyStruct1),
|
||||
Cast(Sig::LocalTime),
|
||||
Cast(Marker::Int32),
|
||||
n_bytes[3], n_bytes[2],
|
||||
n_bytes[1], n_bytes[0] };
|
||||
// clang-format on
|
||||
CheckOutput(output, expected.data(), expected.size());
|
||||
}
|
||||
|
||||
TEST_F(BoltEncoder, LocalDateTime) {
|
||||
output.clear();
|
||||
std::vector<Value> vals;
|
||||
const auto value = Value(utils::LocalDateTime(utils::Date(1), utils::LocalTime({0, 0, 30, 1, 0})));
|
||||
const auto &local_date_time = value.ValueLocalDateTime();
|
||||
vals.push_back(value);
|
||||
ASSERT_EQ(bolt_encoder.MessageRecord(vals), true);
|
||||
const auto secs = local_date_time.SecondsSinceEpoch();
|
||||
ASSERT_EQ(secs, 30);
|
||||
const auto *sec_bytes = std::bit_cast<const uint8_t *>(&secs);
|
||||
const auto nanos = local_date_time.SubSecondsAsNanoseconds();
|
||||
ASSERT_EQ(nanos, 1000000);
|
||||
const auto *nano_bytes = std::bit_cast<const uint8_t *>(&nanos);
|
||||
// 0x91 denotes the size of vals (it's 0x91 because it's anded -- see
|
||||
// WriteTypeSize in base_encoder.hpp).
|
||||
// The rest of the expected results follow logically from LocalTime and Date test cases
|
||||
using Marker = communication::bolt::Marker;
|
||||
using Sig = communication::bolt::Signature;
|
||||
// clang-format off
|
||||
const auto expected = std::array<uint8_t, 11> {
|
||||
Cast(Marker::TinyStruct1),
|
||||
Cast(Sig::Record),
|
||||
0x91,
|
||||
Cast(Marker::TinyStruct2),
|
||||
Cast(Sig::LocalDateTime),
|
||||
// SuperSeconds
|
||||
sec_bytes[0],
|
||||
// SubSeconds
|
||||
Cast(Marker::Int32),
|
||||
nano_bytes[3], nano_bytes[2],
|
||||
nano_bytes[1], nano_bytes[0] };
|
||||
// clang-format on
|
||||
CheckOutput(output, expected.data(), expected.size());
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
@ -65,6 +67,7 @@ TEST(TemporalTest, DateMicrosecondsSinceEpochConversion) {
|
||||
check_microseconds(utils::DateParameters{2020, 11, 22});
|
||||
check_microseconds(utils::DateParameters{1900, 2, 22});
|
||||
check_microseconds(utils::DateParameters{0, 1, 1});
|
||||
check_microseconds(utils::DateParameters{1994, 12, 7});
|
||||
|
||||
ASSERT_THROW(check_microseconds(utils::DateParameters{-10, 1, 1}), utils::BasicException);
|
||||
|
||||
@ -289,3 +292,54 @@ TEST(TemporalTest, DurationParsing) {
|
||||
CheckDurationParameters(utils::ParseDurationParameters("P2020-11-22T19:20:32"),
|
||||
utils::DurationParameters{2020, 11, 22, 19, 20, 32});
|
||||
}
|
||||
|
||||
TEST(TemporalTest, PrintDate) {
|
||||
const auto unix_epoch = utils::Date(utils::DateParameters{1970, 1, 1});
|
||||
std::ostringstream stream;
|
||||
stream << unix_epoch;
|
||||
ASSERT_TRUE(stream);
|
||||
ASSERT_EQ(stream.view(), "1970-01-01");
|
||||
}
|
||||
|
||||
TEST(TemporalTest, PrintLocalTime) {
|
||||
const auto lt = utils::LocalTime({13, 2, 40, 100, 50});
|
||||
std::ostringstream stream;
|
||||
stream << lt;
|
||||
ASSERT_TRUE(stream);
|
||||
ASSERT_EQ(stream.view(), "13:02:40.100050");
|
||||
}
|
||||
|
||||
TEST(TemporalTest, PrintDuration) {
|
||||
const auto dur = utils::Duration({1, 0, 0, 0, 0, 0, 0, 0});
|
||||
std::ostringstream stream;
|
||||
stream << dur;
|
||||
ASSERT_TRUE(stream);
|
||||
ASSERT_EQ(stream.view(), "P0001-00-00T00:00:00.000000");
|
||||
stream.str("");
|
||||
stream.clear();
|
||||
const auto complex_dur = utils::Duration({1, 5, 10, 3, 30, 33, 100, 50});
|
||||
stream << complex_dur;
|
||||
ASSERT_TRUE(stream);
|
||||
ASSERT_EQ(stream.view(), "P0001-05-10T03:30:33.100050");
|
||||
/// stream.str("");
|
||||
/// stream.clear();
|
||||
/// TODO (kostasrim)
|
||||
/// We do not support pasring negative Durations yet. We are the only ones we have access
|
||||
/// to the constructor below.
|
||||
/*
|
||||
const auto negative_dur = utils::Duration({-1, 5, -10, -3, -30, -33});
|
||||
stream << negative_dur;
|
||||
ASSERT_TRUE(stream);
|
||||
ASSERT_EQ(stream.view(), "P0001-05-10T03:30:33.000000");
|
||||
*/
|
||||
}
|
||||
|
||||
TEST(TemporalTest, PrintLocalDateTime) {
|
||||
const auto unix_epoch = utils::Date(utils::DateParameters{1970, 1, 1});
|
||||
const auto lt = utils::LocalTime({13, 2, 40, 100, 50});
|
||||
utils::LocalDateTime ldt(unix_epoch, lt);
|
||||
std::ostringstream stream;
|
||||
stream << ldt;
|
||||
ASSERT_TRUE(stream);
|
||||
ASSERT_EQ(stream.view(), "1970-01-01T13:02:40.100050");
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user