// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source // License, and you may not use this file except in compliance with the Business Source License. // // As of the Change Date specified in that file, in accordance with // the Business Source License, use of this software will be governed // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. #pragma once #include #include #include #include #include "fmt/format.h" #include "utils/exceptions.hpp" #include "utils/logging.hpp" namespace memgraph::utils { class Timezone; template concept Chrono = requires(T) { typename T::rep; typename T::period; }; template constexpr auto GetAndSubtractDuration(TSecond &base_duration) { const auto duration = std::chrono::duration_cast(base_duration); base_duration -= duration; return duration.count(); } template bool Overflows(const TType &lhs, const TType &rhs) { if (lhs > 0 && rhs > 0 && lhs > (std::numeric_limits::max() - rhs)) [[unlikely]] { return true; } return false; } template bool Underflows(const TType &lhs, const TType &rhs) { if (lhs < 0 && rhs < 0 && lhs < (std::numeric_limits::min() - rhs)) [[unlikely]] { return true; } return false; } namespace temporal { struct InvalidArgumentException : public utils::BasicException { using utils::BasicException::BasicException; SPECIALIZE_GET_EXCEPTION_NAME(InvalidArgumentException) }; } // namespace temporal struct DurationParameters { double day{0}; double hour{0}; double minute{0}; double second{0}; double millisecond{0}; double microsecond{0}; }; DurationParameters ParseDurationParameters(std::string_view string); struct Date; struct LocalTime; struct LocalDateTime; struct Duration { explicit Duration(int64_t microseconds); explicit Duration(const DurationParameters ¶meters); auto operator<=>(const Duration &) const = default; int64_t Days() const; int64_t SubDaysAsSeconds() const; int64_t SubDaysAsHours() const; int64_t SubDaysAsMinutes() const; int64_t SubDaysAsMilliseconds() const; int64_t SubDaysAsMicroseconds() const; int64_t SubDaysAsNanoseconds() const; int64_t SubSecondsAsNanoseconds() const; std::string ToString() const; friend std::ostream &operator<<(std::ostream &os, const Duration &dur) { return os << dur.ToString(); } Duration operator-() const; friend Duration operator+(const Duration &lhs, const Duration rhs) { if (Overflows(lhs.microseconds, rhs.microseconds)) { throw utils::BasicException("Duration arithmetic overflows"); } if (Underflows(lhs.microseconds, rhs.microseconds)) { throw utils::BasicException("Duration arithmetic underflows"); } return Duration(lhs.microseconds + rhs.microseconds); } friend Duration operator-(const Duration &lhs, const Duration rhs) { return lhs + (-rhs); } int64_t microseconds; }; struct DurationHash { size_t operator()(const Duration &duration) const; }; struct DateParameters { int64_t year{0}; int64_t month{1}; int64_t day{1}; bool operator==(const DateParameters &) const = default; }; // boolean indicates whether the parsed string was in extended format std::pair ParseDateParameters(std::string_view date_string); constexpr std::chrono::year_month_day ToChronoYMD(uint16_t year, uint8_t month, uint8_t day) { namespace chrono = std::chrono; return chrono::year_month_day(chrono::year(year), chrono::month(month), chrono::day(day)); } constexpr std::chrono::sys_days ToChronoSysDaysYMD(uint16_t year, uint8_t month, uint8_t day) { return std::chrono::sys_days(ToChronoYMD(year, month, day)); } constexpr std::chrono::days DaysSinceEpoch(uint16_t year, uint8_t month, uint8_t day) { return ToChronoSysDaysYMD(year, month, day).time_since_epoch(); } struct Date { explicit Date() : Date{DateParameters{}} {} // we assume we accepted date in microseconds which was normalized using the epoch time point explicit Date(int64_t microseconds); explicit Date(const DateParameters &date_parameters); friend std::ostream &operator<<(std::ostream &os, const Date &date) { return os << date.ToString(); } int64_t MicrosecondsSinceEpoch() const; int64_t DaysSinceEpoch() const; std::string ToString() const; friend Date operator+(const Date &date, const Duration &dur) { namespace chrono = std::chrono; const auto date_as_duration = Duration(date.MicrosecondsSinceEpoch()); const auto result = date_as_duration + dur; const auto ymd = chrono::year_month_day(chrono::sys_days(chrono::days(result.Days()))); return Date({static_cast(ymd.year()), static_cast(ymd.month()), static_cast(ymd.day())}); } friend Date operator+(const Duration &dur, const Date &date) { return date + dur; } friend Date operator-(const Date &date, const Duration &dur) { return date + (-dur); } friend Duration operator-(const Date &lhs, const Date &rhs) { namespace chrono = std::chrono; const auto lhs_days = utils::DaysSinceEpoch(lhs.year, lhs.month, lhs.day); const auto rhs_days = utils::DaysSinceEpoch(rhs.year, rhs.month, rhs.day); const auto days_elapsed = lhs_days - rhs_days; return Duration(chrono::duration_cast(days_elapsed).count()); } auto operator<=>(const Date &) const = default; uint16_t year; uint8_t month; uint8_t day; }; struct DateHash { size_t operator()(const Date &date) const; }; struct LocalTimeParameters { int64_t hour{0}; int64_t minute{0}; int64_t second{0}; int64_t millisecond{0}; int64_t microsecond{0}; bool operator==(const LocalTimeParameters &) const = default; }; struct LocalTimeParsing { LocalTimeParameters parameters; bool extended_format; uint64_t remainder_length; }; LocalTimeParsing ParseLocalTimeParameters(std::string_view string, bool in_zoned_dt = false); struct LocalTime { explicit LocalTime() : LocalTime{LocalTimeParameters{}} {} 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; std::string ToString() const; auto operator<=>(const LocalTime &) const = default; friend std::ostream &operator<<(std::ostream &os, const LocalTime <) { return os << lt.ToString(); } friend LocalTime operator+(const LocalTime &local_time, const Duration &dur) { namespace chrono = std::chrono; auto rhs = dur.SubDaysAsMicroseconds(); auto abs = [](auto value) { return (value >= 0) ? value : -value; }; const auto lhs = local_time.MicrosecondsSinceEpoch(); if (rhs < 0 && lhs < abs(rhs)) { static constexpr int64_t one_day_in_microseconds = chrono::duration_cast(chrono::days(1)).count(); rhs = one_day_in_microseconds + rhs; } auto result = chrono::microseconds(lhs + rhs); const auto h = GetAndSubtractDuration(result) % 24; const auto m = GetAndSubtractDuration(result); const auto s = GetAndSubtractDuration(result); const auto milli = GetAndSubtractDuration(result); const auto micro = result.count(); return LocalTime(LocalTimeParameters{h, m, s, milli, micro}); } friend LocalTime operator+(const Duration &dur, const LocalTime &local_time) { return local_time + dur; } friend LocalTime operator-(const LocalTime &local_time, const Duration &duration) { return local_time + (-duration); } friend Duration operator-(const LocalTime &lhs, const LocalTime &rhs) { Duration lhs_dur(lhs.MicrosecondsSinceEpoch()); Duration rhs_dur(rhs.MicrosecondsSinceEpoch()); return lhs_dur - rhs_dur; } uint8_t hour; uint8_t minute; uint8_t second; uint16_t millisecond; uint16_t microsecond; }; struct LocalTimeHash { size_t operator()(const LocalTime &local_time) const; }; struct LocalDateTimeParsing { DateParameters date_parameters; LocalTimeParameters local_time_parameters; uint64_t remainder_length; }; LocalDateTimeParsing ParseLocalDateTimeParameters(std::string_view string, bool in_zoned_dt = false); struct LocalDateTime { explicit LocalDateTime(int64_t microseconds); explicit LocalDateTime(const DateParameters &date_parameters, const LocalTimeParameters &local_time_parameters); explicit LocalDateTime(const Date &date, const LocalTime &local_time); int64_t MicrosecondsSinceEpoch() const; int64_t SecondsSinceEpoch() const; // seconds since epoch int64_t SubSecondsAsNanoseconds() const; std::string ToString() const; auto operator<=>(const LocalDateTime &) const = default; friend std::ostream &operator<<(std::ostream &os, const LocalDateTime &ldt) { return os << ldt.ToString(); } friend LocalDateTime operator+(const LocalDateTime &dt, const Duration &dur) { const auto local_date_time_as_duration = Duration(dt.MicrosecondsSinceEpoch()); const auto result = local_date_time_as_duration + dur; namespace chrono = std::chrono; const auto ymd = chrono::year_month_day(chrono::sys_days(chrono::days(result.Days()))); const auto date_part = Date({static_cast(ymd.year()), static_cast(ymd.month()), static_cast(ymd.day())}); const auto local_time_part = LocalTime(result.SubDaysAsMicroseconds()); return LocalDateTime(date_part, local_time_part); } friend LocalDateTime operator+(const Duration &dur, const LocalDateTime &dt) { return dt + dur; } friend LocalDateTime operator-(const LocalDateTime &dt, const Duration &dur) { return dt + (-dur); } friend Duration operator-(const LocalDateTime &lhs, const LocalDateTime &rhs) { return Duration(lhs.MicrosecondsSinceEpoch()) - Duration(rhs.MicrosecondsSinceEpoch()); } Date date; LocalTime local_time; }; struct LocalDateTimeHash { size_t operator()(const LocalDateTime &local_date_time) const; }; class Timezone { private: std::variant offset_; public: explicit Timezone(const std::chrono::minutes offset) : offset_{offset} {} explicit Timezone(const std::chrono::time_zone *timezone) : offset_{timezone} {} explicit Timezone(const std::string &timezone_name) : offset_{std::chrono::locate_zone(timezone_name)} {} const Timezone *operator->() const { return this; } bool operator==(const Timezone &) const = default; template std::chrono::minutes OffsetInMinutes(std::chrono::sys_time time_point) const { if (std::holds_alternative(offset_)) { return std::get(offset_); } return std::chrono::duration_cast( std::get(offset_)->get_info(time_point).offset); } template std::chrono::sys_info get_info(std::chrono::sys_time time_point) const { if (std::holds_alternative(offset_)) { const auto offset = std::get(offset_); return std::chrono::sys_info{ .begin = std::chrono::sys_seconds::min(), .end = std::chrono::sys_seconds::max(), .offset = std::chrono::duration_cast(offset), .save = std::chrono::minutes{0}, .abbrev = "", // offset >= std::chrono::minutes{0} ? "+" + std::format("{0:%H}:{0:%M}", offset) // : "-" + std::format("{0:%H}:{0:%M}", -offset) }; } return std::get(offset_)->get_info(time_point); } template auto to_local(std::chrono::sys_time time_point) const { if (std::holds_alternative(offset_)) { using local_time = std::chrono::local_time>; return local_time{(time_point + OffsetInMinutes(time_point)).time_since_epoch()}; } return std::get(offset_)->to_local(time_point); } template auto to_sys(std::chrono::local_time time_point) const { if (std::holds_alternative(offset_)) { using sys_time = std::chrono::sys_time>; return sys_time{(time_point - std::get(offset_)).time_since_epoch()}; } return std::get(offset_)->to_sys(time_point); } std::string_view TimezoneName() const { if (std::holds_alternative(offset_)) { return ""; } return std::get(offset_)->name(); } }; } // namespace memgraph::utils namespace std::chrono { template <> struct zoned_traits { static memgraph::utils::Timezone default_zone() { return memgraph::utils::Timezone{minutes{0}}; } }; } // namespace std::chrono namespace memgraph::utils { struct ZonedDateTimeParameters { DateParameters date; LocalTimeParameters local_time; Timezone timezone; bool operator==(const ZonedDateTimeParameters &) const = default; }; ZonedDateTimeParameters ParseZonedDateTimeParameters(std::string_view string); struct ZonedDateTime { explicit ZonedDateTime(const ZonedDateTimeParameters &zoned_date_time_parameters); explicit ZonedDateTime(const ZonedDateTime &zoned_date_time); explicit ZonedDateTime(const std::chrono::zoned_time &zoned_time); int64_t MicrosecondsSinceEpoch() const; int64_t SecondsSinceEpoch() const; int64_t SubSecondsAsNanoseconds() const; std::string ToString() const; bool operator==(const ZonedDateTime &other) const; std::strong_ordering operator<=>(const ZonedDateTime &other) const; std::chrono::minutes OffsetInMinutes() const { return zoned_time.get_time_zone().OffsetInMinutes(zoned_time.get_sys_time()); } std::string_view TimezoneName() const { return zoned_time.get_time_zone().TimezoneName(); } friend std::ostream &operator<<(std::ostream &os, const ZonedDateTime &zdt) { return os << zdt.ToString(); } friend ZonedDateTime operator+(const ZonedDateTime &zdt, const Duration &dur) { return ZonedDateTime(std::chrono::zoned_time( zdt.zoned_time.get_time_zone(), zdt.zoned_time.get_sys_time() + std::chrono::microseconds(dur.microseconds))); } friend ZonedDateTime operator+(const Duration &dur, const ZonedDateTime &zdt) { return zdt + dur; } friend ZonedDateTime operator-(const ZonedDateTime &zdt, const Duration &dur) { return zdt + (-dur); } friend Duration operator-(const ZonedDateTime &lhs, const ZonedDateTime &rhs) { return Duration(lhs.MicrosecondsSinceEpoch()) - Duration(rhs.MicrosecondsSinceEpoch()); } std::chrono::zoned_time zoned_time; }; Date CurrentDate(); LocalTime CurrentLocalTime(); LocalDateTime CurrentLocalDateTime(); } // namespace memgraph::utils