From a618c38be24ba60fd8fb3ffe5327033f093daab0 Mon Sep 17 00:00:00 2001 From: mcb Date: Thu, 3 Jul 2025 16:09:17 +0200 Subject: [PATCH 1/9] first version --- src/rdf4cpp/Literal.cpp | 12 +- src/rdf4cpp/Timezone.hpp | 844 ++++++++++-------- .../datatypes/registry/util/DateTimeUtils.hpp | 221 ++--- src/rdf4cpp/datatypes/xsd/time/Date.cpp | 45 +- src/rdf4cpp/datatypes/xsd/time/DateTime.cpp | 46 +- .../datatypes/xsd/time/DateTimeStamp.cpp | 46 +- src/rdf4cpp/datatypes/xsd/time/Day.cpp | 2 +- .../datatypes/xsd/time/DayTimeDuration.cpp | 24 +- src/rdf4cpp/datatypes/xsd/time/Duration.cpp | 36 +- src/rdf4cpp/datatypes/xsd/time/Month.cpp | 2 +- src/rdf4cpp/datatypes/xsd/time/MonthDay.cpp | 2 +- src/rdf4cpp/datatypes/xsd/time/Time.cpp | 32 +- src/rdf4cpp/datatypes/xsd/time/Year.cpp | 12 +- src/rdf4cpp/datatypes/xsd/time/YearMonth.cpp | 9 +- .../datatypes/xsd/time/YearMonthDuration.cpp | 26 +- src/rdf4cpp/util/CheckedInt.hpp | 55 +- src/rdf4cpp/util/Int128.hpp | 12 +- tests/datatype/tests_time_types.cpp | 41 +- 18 files changed, 811 insertions(+), 656 deletions(-) diff --git a/src/rdf4cpp/Literal.cpp b/src/rdf4cpp/Literal.cpp index b4541a8cd..88a84ef19 100644 --- a/src/rdf4cpp/Literal.cpp +++ b/src/rdf4cpp/Literal.cpp @@ -2412,7 +2412,7 @@ std::optional Literal::year() const noexcept { auto casted = this->cast_to_value(); if (!casted.has_value()) return std::nullopt; - auto [date, _] = util::deconstruct_timepoint(casted->first); + auto [date, _] = *util::deconstruct_timepoint(casted->first); return date.year(); } @@ -2430,7 +2430,7 @@ std::optional Literal::month() const noexcept { auto casted = this->cast_to_value(); if (!casted.has_value()) return std::nullopt; - auto [date, _] = util::deconstruct_timepoint(casted->first); + auto [date, _] = *util::deconstruct_timepoint(casted->first); return date.month(); } @@ -2448,7 +2448,7 @@ std::optional Literal::day() const noexcept { auto casted = this->cast_to_value(); if (!casted.has_value()) return std::nullopt; - auto [date, _] = util::deconstruct_timepoint(casted->first); + auto [date, _] = *util::deconstruct_timepoint(casted->first); return date.day(); } @@ -2465,7 +2465,7 @@ std::optional Literal::hours() const noexcept { auto casted = this->cast_to_value(); if (!casted.has_value()) return std::nullopt; - auto [_, time] = util::deconstruct_timepoint(casted->first); + auto [_, time] = *util::deconstruct_timepoint(casted->first); return std::chrono::hh_mm_ss{std::chrono::duration_cast(time)}.hours(); } @@ -2482,7 +2482,7 @@ std::optional Literal::minutes() const noexcept { auto casted = this->cast_to_value(); if (!casted.has_value()) return std::nullopt; - auto [_, time] = util::deconstruct_timepoint(casted->first); + auto [_, time] = *util::deconstruct_timepoint(casted->first); return std::chrono::hh_mm_ss{std::chrono::duration_cast(time)}.minutes(); } @@ -2499,7 +2499,7 @@ std::optional Literal::seconds() const noexcept { auto casted = this->cast_to_value(); if (!casted.has_value()) return std::nullopt; - auto [_, t] = util::deconstruct_timepoint(casted->first); + auto [_, t] = *util::deconstruct_timepoint(casted->first); std::chrono::hh_mm_ss const time{std::chrono::duration_cast(t)}; return time.seconds() + time.subseconds(); } diff --git a/src/rdf4cpp/Timezone.hpp b/src/rdf4cpp/Timezone.hpp index 89d79ad90..43970f81a 100644 --- a/src/rdf4cpp/Timezone.hpp +++ b/src/rdf4cpp/Timezone.hpp @@ -9,453 +9,541 @@ #include #include - -#include +#include namespace rdf4cpp { -struct Timezone { - // heavily inspired by https://howardhinnant.github.io/date/tz.html#Examples + struct Timezone { + // heavily inspired by https://howardhinnant.github.io/date/tz.html#Examples - std::chrono::minutes offset = std::chrono::minutes{0}; + std::chrono::minutes offset = std::chrono::minutes{0}; - static constexpr const char *begin_tokens = "Z+-"; + static constexpr char const *begin_tokens = "Z+-"; - constexpr Timezone() = default; + constexpr Timezone() = default; - inline explicit Timezone(const std::chrono::time_zone *tz, std::chrono::time_point n = std::chrono::system_clock::now()) - : offset(std::chrono::duration_cast(tz->get_info(n).offset)) { - } + inline explicit Timezone(std::chrono::time_zone const *tz, std::chrono::time_point n = std::chrono::system_clock::now()) + : offset(std::chrono::duration_cast(tz->get_info(n).offset)) { + } - constexpr explicit Timezone(std::chrono::hours h) noexcept - : offset(h) {} + constexpr explicit Timezone(std::chrono::hours h) noexcept + : offset(h) { + } - constexpr explicit Timezone(std::chrono::minutes h) noexcept - : offset(h) {} + constexpr explicit Timezone(std::chrono::minutes h) noexcept + : offset(h) { + } - constexpr auto operator<=>(const Timezone &) const noexcept = default; + constexpr auto operator<=>(Timezone const &) const noexcept = default; - static constexpr Timezone parse(std::string_view v, std::string_view dt) { - Timezone tz{}; - if (v == "Z") { + static constexpr Timezone parse(std::string_view v, std::string_view dt) { + Timezone tz{}; + if (v == "Z") { + return tz; + } + bool negative = false; + if (v[0] == '-') { + negative = true; + } + v = v.substr(1); + auto sep = v.find(':'); + if (sep == std::string::npos) { + throw InvalidNode{std::format("{} parsing error: timezone expected :", dt)}; + } + std::chrono::hours const h{datatypes::registry::util::from_chars(v.substr(0, sep))}; + tz.offset = std::chrono::minutes{datatypes::registry::util::from_chars(v.substr(sep + 1))} + std::chrono::minutes{h}; + if (negative) { + tz.offset *= -1; + } + if (tz.offset.count() < -840 || tz.offset.count() > 840) { + throw InvalidNode{std::format("{} parsing error: timezone offset too big", dt)}; + } return tz; } - bool negative = false; - if (v[0] == '-') { - negative = true; - } - v = v.substr(1); - auto sep = v.find(':'); - if (sep == std::string::npos) { - throw InvalidNode{std::format("{} parsing error: timezone expected :", dt)}; + + static constexpr std::optional parse_optional(std::string_view &s, std::string_view dt) { + auto p = s.find_first_of(begin_tokens, 1); + if (p == 0 || p == std::string::npos) + return std::nullopt; + auto pre = s.substr(0, p); + auto tz = parse(s.substr(p), dt); + s = pre; + return tz; } - std::chrono::hours const h{datatypes::registry::util::from_chars(v.substr(0, sep))}; - tz.offset = std::chrono::minutes{datatypes::registry::util::from_chars(v.substr(sep + 1))} + std::chrono::minutes{h}; - if (negative) { - tz.offset *= -1; + + // sign, hours, :, minutes + static constexpr size_t max_canonical_string_chars = 1 + (std::numeric_limits::digits10 + 1) + 1 + 2; + template T> + T to_canonical_string(T o) const noexcept { + if (offset == std::chrono::minutes{0}) { + *o = 'Z'; + ++o; + return o; + } + auto h = std::chrono::floor(std::chrono::abs(offset)); + auto m = std::chrono::abs(offset) - h; + return std::format_to(o, "{}{:02}:{:02}", offset >= std::chrono::minutes{0} ? '+' : '-', h.count(), m.count()); } - if (tz.offset.count() < -840 || tz.offset.count() > 840) { - throw InvalidNode{std::format("{} parsing error: timezone offset too big", dt)}; + [[nodiscard]] std::string to_canonical_string() const noexcept { + std::string buf{}; + buf.reserve(max_canonical_string_chars); + to_canonical_string(std::back_inserter(buf)); + return buf; } - return tz; - } - static constexpr std::optional parse_optional(std::string_view &s, std::string_view dt) { - auto p = s.find_first_of(begin_tokens, 1); - if (p == 0 || p == std::string::npos) - return std::nullopt; - auto pre = s.substr(0, p); - auto tz = parse(s.substr(p), dt); - s = pre; - return tz; - } - - // sign, hours, :, minutes - static constexpr size_t max_canonical_string_chars = 1+(std::numeric_limits::digits10+1)+1+2; - template T> - T to_canonical_string(T o) const noexcept { - if (offset == std::chrono::minutes{0}) { - *o = 'Z'; - ++o; - return o; - } - auto h = std::chrono::floor(std::chrono::abs(offset)); - auto m = std::chrono::abs(offset) - h; - return std::format_to(o, "{}{:02}:{:02}", offset >= std::chrono::minutes{0} ? '+' : '-', h.count(), m.count()); - } - [[nodiscard]] std::string to_canonical_string() const noexcept { - std::string buf{}; - buf.reserve(max_canonical_string_chars); - to_canonical_string(std::back_inserter(buf)); - return buf; - } - - [[nodiscard]] const std::chrono::time_zone *get_tz(std::chrono::time_point n = std::chrono::system_clock::now()) const { - for (const auto &tz : std::chrono::get_tzdb().zones) { - if (tz.get_info(n).offset == std::chrono::seconds(offset)) { - return &tz; + [[nodiscard]] std::chrono::time_zone const *get_tz(std::chrono::time_point n = std::chrono::system_clock::now()) const { + for (auto const &tz : std::chrono::get_tzdb().zones) { + if (tz.get_info(n).offset == std::chrono::seconds(offset)) { + return &tz; + } } + return nullptr; } - return nullptr; - } - template - [[nodiscard]] auto to_sys(const std::chrono::local_time &tp) const noexcept { - return std::chrono::sys_time>{(tp - offset).time_since_epoch()}; - } + template + [[nodiscard]] auto to_sys(std::chrono::local_time const &tp) const noexcept { + return std::chrono::sys_time>{(tp - offset).time_since_epoch()}; + } - template - [[nodiscard]] auto to_local(const std::chrono::sys_time &tp) const noexcept { - return std::chrono::local_time>{(tp + offset).time_since_epoch()}; - } + template + [[nodiscard]] auto to_local(std::chrono::sys_time const &tp) const noexcept { + return std::chrono::local_time>{(tp + offset).time_since_epoch()}; + } - template - [[nodiscard]] std::chrono::sys_info get_info(const std::chrono::sys_time &) const noexcept { - return std::chrono::sys_info{ - std::chrono::sys_seconds{std::chrono::seconds{0L}}, - std::chrono::sys_seconds{std::chrono::seconds{std::numeric_limits::max()}}, - offset, - std::chrono::minutes{0}, - to_canonical_string()}; - } + template + [[nodiscard]] std::chrono::sys_info get_info(std::chrono::sys_time const &) const noexcept { + return std::chrono::sys_info{ + std::chrono::sys_seconds{std::chrono::seconds{0L}}, + std::chrono::sys_seconds{std::chrono::seconds{std::numeric_limits::max()}}, + offset, + std::chrono::minutes{0}, + to_canonical_string()}; + } - const Timezone *operator->() const noexcept { - return this; - } + Timezone const *operator->() const noexcept { + return this; + } - static constexpr Timezone max_value() noexcept { - return Timezone{std::chrono::hours{14}}; - }; - static constexpr Timezone min_value() noexcept { - return Timezone{std::chrono::hours{-14}}; + static constexpr Timezone max_value() noexcept { + return Timezone{std::chrono::hours{14}}; + }; + static constexpr Timezone min_value() noexcept { + return Timezone{std::chrono::hours{-14}}; + }; }; -}; -using OptionalTimezone = std::optional; + namespace util { + /** + * turns any duration to its CheckedIntegral counterpart. + * @tparam R + * @param v + * @return + */ + template + constexpr std::chrono::duration, R> to_checked(std::chrono::duration v) noexcept { + return std::chrono::duration, R>{v.count()}; + } + /** + * turns any CheckedIntegral duration back to its integer based duration. + * @note undefined behavior, if v is invalid + * @tparam R + * @param v + * @return + */ + template + constexpr std::optional> from_checked(std::chrono::duration, R> v) noexcept { + if (v.count().is_invalid()) { + return std::nullopt; + } + return std::chrono::duration{v.count().get_value()}; + } + /** + * turns any time_point to its CheckedIntegral counterpart. + * @tparam C + * @tparam R + * @param v + * @return + */ + template + constexpr std::chrono::time_point, R>> to_checked(std::chrono::time_point> v) noexcept { + return std::chrono::time_point, R>>{to_checked(v.time_since_epoch())}; + } + /** + * turns any CheckedIntegral time_point back to its integer based time_point. + * @note undefined behavior, if v is invalid + * @tparam C + * @tparam R + * @param v + * @return + */ + template + constexpr std::optional>> from_checked(std::chrono::time_point, R>> v) noexcept { + if (v.time_since_epoch().count().is_invalid()) { + return std::nullopt; + } + return std::chrono::time_point>{*from_checked(v.time_since_epoch())}; + } + } // namespace util -using Month = std::chrono::month; -using Day = std::chrono::day; -/** + using OptionalTimezone = std::optional; + + using Month = std::chrono::month; + using Day = std::chrono::day; + + /** * Like std::chrono::year, except it has a greater range. * adapted from https://howardhinnant.github.io/date_algorithms.html */ -struct Year { -private: - int64_t value_; - -public: - explicit constexpr Year(int64_t y = 0) noexcept : value_{y} { - } - - constexpr explicit operator int64_t() const noexcept { - return value_; - } - - [[nodiscard]] constexpr bool is_leap() const noexcept(noexcept(value_ % 100)) { - return value_ % 4 == 0 && (value_ % 100 != 0 || value_ % 400 == 0); - } + struct Year { + private: + int64_t value_; - constexpr auto operator<=>(Year const &) const noexcept = default; - - friend constexpr Year operator+(Year const &y, std::chrono::years d) noexcept { - return Year{y.value_ + d.count()}; - } - friend constexpr Year operator+(std::chrono::years d, Year const &y) noexcept { - return Year{y.value_ + d.count()}; - } + public: + explicit constexpr Year(int64_t y = 0) noexcept : value_{y} { + } - constexpr Year operator+=(std::chrono::years d) noexcept { - *this = *this + d; - return *this; - } + constexpr explicit operator int64_t() const noexcept { + return value_; + } - friend constexpr Year operator-(Year const &y, std::chrono::years d) noexcept { - return Year{y.value_ - d.count()}; - } - friend constexpr std::chrono::years operator-(Year const &a, Year const &b) noexcept { - return std::chrono::years{a.value_ - b.value_}; - } + [[nodiscard]] constexpr bool is_leap() const noexcept(noexcept(value_ % 100)) { + return value_ % 4 == 0 && (value_ % 100 != 0 || value_ % 400 == 0); + } - constexpr Year operator-=(std::chrono::years d) noexcept { - *this = *this - d; - return *this; - } + constexpr auto operator<=>(Year const &) const noexcept = default; - constexpr Year &operator++() noexcept { - *this += std::chrono::years{1}; - return *this; - } - constexpr Year operator++(int) noexcept { - Year r = *this; - ++(*this); - return r; - } + friend constexpr Year operator+(Year const &y, std::chrono::years d) noexcept { + return Year{y.value_ + d.count()}; + } + friend constexpr Year operator+(std::chrono::years d, Year const &y) noexcept { + return Year{y.value_ + d.count()}; + } - constexpr Year &operator--() noexcept { - *this -= std::chrono::years{1}; - return *this; - } - constexpr Year operator--(int) noexcept { - Year r = *this; - --(*this); - return r; - } + constexpr Year operator+=(std::chrono::years d) noexcept { + *this = *this + d; + return *this; + } - static constexpr Year max() noexcept { - return Year{std::numeric_limits::max()}; - } - static constexpr Year min() noexcept { - return Year{std::numeric_limits::min()}; - } -}; + friend constexpr Year operator-(Year const &y, std::chrono::years d) noexcept { + return Year{y.value_ - d.count()}; + } + friend constexpr std::chrono::years operator-(Year const &a, Year const &b) noexcept { + return std::chrono::years{a.value_ - b.value_}; + } -struct YearMonth { -private: - Year year_ = Year{0}; - Month month_ = Month{1}; + constexpr Year operator-=(std::chrono::years d) noexcept { + *this = *this - d; + return *this; + } - static constexpr YearMonth create_normalized(int64_t y, int64_t mo) noexcept { - --mo; - y += mo / 12; - mo %= 12; - if (mo < 0) { // fix result of % being in [-11,11] - --y; - mo += 12; + constexpr Year &operator++() noexcept { + *this += std::chrono::years{1}; + return *this; + } + constexpr Year operator++(int) noexcept { + Year r = *this; + ++(*this); + return r; } - return YearMonth{Year{y}, std::chrono::month{static_cast(mo+1)}}; - } -public: - constexpr YearMonth() noexcept = default; + constexpr Year &operator--() noexcept { + *this -= std::chrono::years{1}; + return *this; + } + constexpr Year operator--(int) noexcept { + Year r = *this; + --(*this); + return r; + } - constexpr YearMonth(Year y, std::chrono::month m) noexcept : year_{y}, month_{m} { - } + static constexpr Year max() noexcept { + return Year{std::numeric_limits::max()}; + } + static constexpr Year min() noexcept { + return Year{std::numeric_limits::min()}; + } + }; - [[nodiscard]] constexpr Year year() const noexcept { - return year_; - } + struct YearMonth { + private: + Year year_ = Year{0}; + Month month_ = Month{1}; + + static constexpr YearMonth create_normalized(int64_t y, int64_t mo) noexcept { + --mo; + y += mo / 12; + mo %= 12; + if (mo < 0) { // fix result of % being in [-11,11] + --y; + mo += 12; + } + return YearMonth{Year{y}, std::chrono::month{static_cast(mo + 1)}}; + } - [[nodiscard]] constexpr Month month() const noexcept { - return month_; - } + public: + constexpr YearMonth() noexcept = default; - constexpr auto operator<=>(YearMonth const &) const noexcept = default; + constexpr YearMonth(Year y, std::chrono::month m) noexcept : year_{y}, month_{m} { + } - [[nodiscard]] constexpr bool ok() const noexcept { - return month_.ok(); - } + [[nodiscard]] constexpr Year year() const noexcept { + return year_; + } - friend constexpr YearMonth operator+(YearMonth const &ym, std::chrono::years d) noexcept { - return YearMonth{ym.year_ + d, ym.month_}; - } - friend constexpr YearMonth operator+(std::chrono::years d, YearMonth const &ym) noexcept { - return YearMonth{ym.year_ + d, ym.month_}; - } + [[nodiscard]] constexpr Month month() const noexcept { + return month_; + } - constexpr YearMonth& operator+=(std::chrono::years d) noexcept { - *this = *this + d; - return *this; - } + constexpr auto operator<=>(YearMonth const &) const noexcept = default; - friend constexpr YearMonth operator+(YearMonth const &ym, std::chrono::months d) noexcept { - return create_normalized(static_cast(ym.year_), static_cast(ym.month_) + d.count()); - } - friend constexpr YearMonth operator+(std::chrono::months d, YearMonth const &ym) noexcept { - return create_normalized(static_cast(ym.year_), static_cast(ym.month_) + d.count()); - } + [[nodiscard]] constexpr bool ok() const noexcept { + return month_.ok(); + } - constexpr YearMonth& operator+=(std::chrono::months d) noexcept { - *this = *this + d; - return *this; - } + friend constexpr YearMonth operator+(YearMonth const &ym, std::chrono::years d) noexcept { + return YearMonth{ym.year_ + d, ym.month_}; + } + friend constexpr YearMonth operator+(std::chrono::years d, YearMonth const &ym) noexcept { + return YearMonth{ym.year_ + d, ym.month_}; + } - friend constexpr YearMonth operator-(YearMonth const &ym, std::chrono::years d) noexcept { - return {ym.year_ - d, ym.month_}; - } - friend constexpr YearMonth operator-(YearMonth const &ym, std::chrono::months d) noexcept { - return create_normalized(static_cast(ym.year_), static_cast(ym.month_) - d.count()); - } + constexpr YearMonth &operator+=(std::chrono::years d) noexcept { + *this = *this + d; + return *this; + } - friend constexpr std::chrono::months operator-(YearMonth const &a, YearMonth const &b) noexcept { - return (a.year_ - b.year_) + (a.month_ - b.month_); - } + // TODO overflow + friend constexpr YearMonth operator+(YearMonth const &ym, std::chrono::months d) noexcept { + return create_normalized(static_cast(ym.year_), static_cast(ym.month_) + d.count()); + } + friend constexpr YearMonth operator+(std::chrono::months d, YearMonth const &ym) noexcept { + return create_normalized(static_cast(ym.year_), static_cast(ym.month_) + d.count()); + } - constexpr YearMonth& operator-=(std::chrono::years d) noexcept { - *this = *this - d; - return *this; - } - constexpr YearMonth& operator-=(std::chrono::months d) noexcept { - *this = *this - d; - return *this; - } -}; + constexpr YearMonth &operator+=(std::chrono::months d) noexcept { + *this = *this + d; + return *this; + } -struct YearMonthDay { - template - using time_point = std::chrono::time_point>; - template - using time_point_local = std::chrono::time_point>; - -private: - // adapted from https://howardhinnant.github.io/date_algorithms.html - Year year_ = Year{0}; - Month month_ = Month{1}; - Day day_ = Day{1}; - - static constexpr std::chrono::day last_day_in_month(Year year, Month month) noexcept { - assert(month.ok()); - constexpr unsigned char common[]{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - auto m = static_cast(month); - return std::chrono::day{m != 2 || !year.is_leap() ? common[m - 1] : 29u}; - } + friend constexpr YearMonth operator-(YearMonth const &ym, std::chrono::years d) noexcept { + return {ym.year_ - d, ym.month_}; + } + friend constexpr YearMonth operator-(YearMonth const &ym, std::chrono::months d) noexcept { + return create_normalized(static_cast(ym.year_), static_cast(ym.month_) - d.count()); + } -public: - constexpr YearMonthDay() noexcept = default; + friend constexpr std::chrono::months operator-(YearMonth const &a, YearMonth const &b) noexcept { + return (a.year_ - b.year_) + (a.month_ - b.month_); + } - constexpr explicit YearMonthDay(std::chrono::year_month_day ymd) noexcept - : year_(static_cast(ymd.year())), month_(ymd.month()), day_(ymd.day()) { - } - constexpr YearMonthDay(Year const &y, Month m, std::chrono::day d) noexcept - : year_(y), month_(m), day_(d) { - } - constexpr YearMonthDay(Year const &y, Month m, std::chrono::last_spec) noexcept - : year_(y), month_(m), day_(last_day_in_month(y, m)) { - } - constexpr YearMonthDay(YearMonth const &ym, Day d) noexcept - : year_(ym.year()), month_(ym.month()), day_(d) { - } - constexpr YearMonthDay(YearMonth const &ym, std::chrono::last_spec) noexcept - : YearMonthDay(ym.year(), ym.month(), std::chrono::last) { - } - template - constexpr explicit YearMonthDay(time_point

sd) noexcept(noexcept(P{} + P{} * P{} - P{} / P{})) { - static_assert(std::numeric_limits::digits >= 18, "This algorithm has not been ported to a 16 bit unsigned integer"); - static_assert(std::numeric_limits::digits >= 20, "This algorithm has not been ported to a 16 bit signed integer"); - static_assert(std::numeric_limits

::digits >= 20, "This algorithm has not been ported to a 16 bit signed integer"); - P z = sd.time_since_epoch().count(); - z += 719468; - P const era = (z >= 0 ? z : z - 146096) / 146097; - auto const doe = static_cast

(z - era * 146097); // [0, 146096] - auto const yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; // [0, 399] - P const y = static_cast

(yoe) + era * 400; - auto const doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365] - auto const mp = (5 * doy + 2) / 153; // [0, 11] - auto const d = doy - (153 * mp + 2) / 5 + 1; // [1, 31] - auto const m = mp < 10 ? mp + 3 : mp - 9; // [1, 12] - year_ = Year{static_cast(y + (m <= 2))}; - month_ = std::chrono::month{static_cast(m)}; - day_ = std::chrono::day{static_cast(d)}; - } - template - constexpr explicit YearMonthDay(time_point_local

sd) noexcept(noexcept(P{} + P{} * P{} - P{} / P{})) - : YearMonthDay(time_point

(sd.time_since_epoch())) { - } + constexpr YearMonth &operator-=(std::chrono::years d) noexcept { + *this = *this - d; + return *this; + } + constexpr YearMonth &operator-=(std::chrono::months d) noexcept { + *this = *this - d; + return *this; + } + }; - [[nodiscard]] constexpr Year year() const noexcept { - return year_; - } + struct YearMonthDay { + template + using time_point = std::chrono::time_point>; + template + using time_point_local = std::chrono::time_point>; + + private: + // adapted from https://howardhinnant.github.io/date_algorithms.html + Year year_ = Year{0}; + Month month_ = Month{1}; + Day day_ = Day{1}; + + static constexpr std::chrono::day last_day_in_month(Year year, Month month) noexcept { + assert(month.ok()); + constexpr unsigned char common[]{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + auto m = static_cast(month); + return std::chrono::day{m != 2 || !year.is_leap() ? common[m - 1] : 29u}; + } - [[nodiscard]] constexpr Month month() const noexcept { - return month_; - } + public: + constexpr YearMonthDay() noexcept = default; - [[nodiscard]] constexpr Day day() const noexcept { - return day_; - } + constexpr explicit YearMonthDay(std::chrono::year_month_day ymd) noexcept + : year_(static_cast(ymd.year())), month_(ymd.month()), day_(ymd.day()) { + } + constexpr YearMonthDay(Year const &y, Month m, std::chrono::day d) noexcept + : year_(y), month_(m), day_(d) { + } + constexpr YearMonthDay(Year const &y, Month m, std::chrono::last_spec) noexcept + : year_(y), month_(m), day_(last_day_in_month(y, m)) { + } + constexpr YearMonthDay(YearMonth const &ym, Day d) noexcept + : year_(ym.year()), month_(ym.month()), day_(d) { + } + constexpr YearMonthDay(YearMonth const &ym, std::chrono::last_spec) noexcept + : YearMonthDay(ym.year(), ym.month(), std::chrono::last) { + } + template + static constexpr std::optional from_time_point_checked(time_point

sd) noexcept { + static_assert(std::numeric_limits::digits >= 18, "This algorithm has not been ported to a 16 bit unsigned integer"); + static_assert(std::numeric_limits::digits >= 20, "This algorithm has not been ported to a 16 bit signed integer"); + static_assert(std::numeric_limits

::digits >= 20, "This algorithm has not been ported to a 16 bit signed integer"); + rdf4cpp::util::CheckedIntegral

z = sd.time_since_epoch().count(); + z += 719468; + rdf4cpp::util::CheckedIntegral

const era = (z >= 0 ? z : z - 146096) / 146097; + rdf4cpp::util::CheckedIntegral

const doe = z - era * 146097; // [0, 146096] + rdf4cpp::util::CheckedIntegral

const yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; // [0, 399] + rdf4cpp::util::CheckedIntegral

const y = yoe + era * 400; + rdf4cpp::util::CheckedIntegral

const doy = doe - (yoe * 365 + yoe / 4 - yoe / 100); // [0, 365] + rdf4cpp::util::CheckedIntegral

const mp = (doy * 5 + 2) / 153; // [0, 11] + rdf4cpp::util::CheckedIntegral

const d = doy - (mp * 153 + 2) / 5 + 1; // [1, 31] + rdf4cpp::util::CheckedIntegral

const m = mp < 10 ? mp + 3 : mp - 9; // [1, 12] + auto const y_out = (y + (m <= 2)).template checked_cast(); + auto const m_out = m.template checked_cast(); + auto const d_out = d.template checked_cast(); + if (y_out.is_invalid() || m_out.is_invalid() || d_out.is_invalid()) { + return std::nullopt; + } + return YearMonthDay{Year{y_out.get_value()}, std::chrono::month{m_out.get_value()}, std::chrono::day{d_out.get_value()}}; + } + template + static constexpr std::optional from_local_time_point_checked(time_point_local

sd) noexcept { + return from_time_point_checked(time_point

(sd.time_since_epoch())); + } + template + constexpr explicit YearMonthDay(time_point

sd) : YearMonthDay(from_time_point_checked(sd).value()) { + } + template + constexpr explicit YearMonthDay(time_point_local

sd) : YearMonthDay(from_local_time_point_checked(sd).value()) { + } - [[nodiscard]] constexpr time_point to_time_point() const { - static_assert(std::numeric_limits::digits >= 18, "This algorithm has not been ported to a 16 bit unsigned integer"); - static_assert(std::numeric_limits::digits >= 20, "This algorithm has not been ported to a 16 bit signed integer"); - static_assert(std::numeric_limits::digits >= 20, "This algorithm has not been ported to a 16 bit signed integer"); - boost::multiprecision::checked_int128_t y = static_cast(year_); - auto m = static_cast(month_); - auto d = static_cast(day_); - y -= m <= 2; - boost::multiprecision::checked_int128_t const era = (y >= 0 ? y : y - 399) / 400; - auto const yoe = static_cast(y - era * 400); // [0, 399] - unsigned const doy = (153 * (m > 2 ? m - 3 : m + 9) + 2) / 5 + d - 1; // [0, 365] - unsigned const doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096] - // note that the epoch of system_clock is specified as 00:00:00 Coordinated Universal Time (UTC), Thursday, 1 January 1970 - return time_point{typename time_point::duration{era * 146097 + static_cast(doe) - 719468}}; - } - [[nodiscard]] constexpr time_point_local to_time_point_local() const{ - return time_point_local{to_time_point().time_since_epoch()}; - } + [[nodiscard]] constexpr Year year() const noexcept { + return year_; + } - [[nodiscard]] constexpr bool ok() const noexcept { - return month_.ok() && day_.ok() && day_ <= last_day_in_month(year_, month_); - } + [[nodiscard]] constexpr Month month() const noexcept { + return month_; + } - constexpr auto operator<=>(YearMonthDay const &) const noexcept = default; + [[nodiscard]] constexpr Day day() const noexcept { + return day_; + } - friend constexpr YearMonthDay operator+(YearMonthDay const &ym, std::chrono::years d) noexcept { - return {ym.year_ + d, ym.month_, ym.day_}; - } - friend constexpr YearMonthDay operator+(std::chrono::years d, YearMonthDay const &ym) noexcept { - return {ym.year_ + d, ym.month_, ym.day_}; - } + [[nodiscard]] constexpr std::optional> to_time_point() const { + static_assert(std::numeric_limits::digits >= 18, "This algorithm has not been ported to a 16 bit unsigned integer"); + static_assert(std::numeric_limits::digits >= 20, "This algorithm has not been ported to a 16 bit signed integer"); + static_assert(std::numeric_limits::digits >= 20, "This algorithm has not been ported to a 16 bit signed integer"); + util::CheckedIntegral y = static_cast(year_); + auto m = static_cast(month_); + auto d = static_cast(day_); + y -= m <= 2; + util::CheckedIntegral const era = (y >= 0 ? y : y - 399) / 400; + auto const yoe = y - era * 400; // [0, 399] + auto const doy = (153 * (m > 2 ? m - 3 : m + 9) + 2) / 5 + d - 1; // [0, 365] + auto const doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096] + // note that the epoch of system_clock is specified as 00:00:00 Coordinated Universal Time (UTC), Thursday, 1 January 1970 + time_point> const r{typename time_point>::duration{era * 146097 + static_cast>(doe) - 719468}}; + return util::from_checked(r); + } + [[nodiscard]] constexpr std::optional> to_time_point_local() const { + auto const v = to_time_point(); + return time_point_local{v.value().time_since_epoch()}; + } - constexpr YearMonthDay & operator+=(std::chrono::years d) noexcept { - *this = *this + d; - return *this; - } + [[nodiscard]] constexpr bool ok() const noexcept { + return month_.ok() && day_.ok() && day_ <= last_day_in_month(year_, month_); + } - friend constexpr YearMonthDay operator+(YearMonthDay const &d, std::chrono::months m) noexcept { - return YearMonthDay{YearMonth{d.year_, d.month_} + m, d.day_}; - } - friend constexpr YearMonthDay operator+(std::chrono::months m, YearMonthDay const &d) noexcept { - return YearMonthDay{YearMonth{d.year_, d.month_} + m, d.day_}; - } + constexpr auto operator<=>(YearMonthDay const &) const noexcept = default; - constexpr YearMonthDay & operator+=(std::chrono::months d) noexcept { - *this = *this + d; - return *this; - } + friend constexpr YearMonthDay operator+(YearMonthDay const &ym, std::chrono::years d) noexcept { + return {ym.year_ + d, ym.month_, ym.day_}; + } + friend constexpr YearMonthDay operator+(std::chrono::years d, YearMonthDay const &ym) noexcept { + return {ym.year_ + d, ym.month_, ym.day_}; + } - friend constexpr YearMonthDay operator-(YearMonthDay const &ym, std::chrono::years d) noexcept { - return {ym.year_ - d, ym.month_, ym.day_}; - } - friend constexpr YearMonthDay operator-(YearMonthDay const &d, std::chrono::months m) noexcept { - return YearMonthDay{YearMonth{d.year_, d.month_} - m, d.day_}; - } + constexpr YearMonthDay &operator+=(std::chrono::years d) noexcept { + *this = *this + d; + return *this; + } - constexpr YearMonthDay & operator-=(std::chrono::years d) noexcept { - *this = *this - d; - return *this; - } - constexpr YearMonthDay & operator-=(std::chrono::months d) noexcept { - *this = *this - d; - return *this; - } -}; + friend constexpr YearMonthDay operator+(YearMonthDay const &d, std::chrono::months m) noexcept { + return YearMonthDay{YearMonth{d.year_, d.month_} + m, d.day_}; + } + friend constexpr YearMonthDay operator+(std::chrono::months m, YearMonthDay const &d) noexcept { + return YearMonthDay{YearMonth{d.year_, d.month_} + m, d.day_}; + } -using DurationNano = std::chrono::duration; -using TimePoint = std::chrono::time_point; -// system_clock does not use leap seconds, as required by rdf (xsd) -using TimePointSys = std::chrono::time_point; -using ZonedTime = std::chrono::zoned_time; + constexpr YearMonthDay &operator+=(std::chrono::months d) noexcept { + *this = *this + d; + return *this; + } -namespace util { + friend constexpr YearMonthDay operator-(YearMonthDay const &ym, std::chrono::years d) noexcept { + return {ym.year_ - d, ym.month_, ym.day_}; + } + friend constexpr YearMonthDay operator-(YearMonthDay const &d, std::chrono::months m) noexcept { + return YearMonthDay{YearMonth{d.year_, d.month_} - m, d.day_}; + } -// see https://www.w3.org/TR/xpath-functions/#comp.datetime -inline constexpr YearMonthDay time_point_replacement_date{Year(1972), std::chrono::January, std::chrono::day{1}}; -inline constexpr DurationNano time_point_replacement_time_of_day{0}; -// implementation defined, not from standard -inline constexpr Timezone time_point_replacement_timezone{std::chrono::minutes{0}}; + constexpr YearMonthDay &operator-=(std::chrono::years d) noexcept { + *this = *this - d; + return *this; + } + constexpr YearMonthDay &operator-=(std::chrono::months d) noexcept { + *this = *this - d; + return *this; + } + }; -constexpr TimePoint construct_timepoint(YearMonthDay const &date, const DurationNano& time_of_day) { - auto sd = date.to_time_point_local(); - auto ms = static_cast(sd); - ms += time_of_day; - return ms; -} + using DurationNano = std::chrono::duration; + using TimePoint = std::chrono::time_point; + // system_clock does not use leap seconds, as required by rdf (xsd) + using TimePointSys = std::chrono::time_point; + using ZonedTime = std::chrono::zoned_time; + + namespace util { + using DurationNano_Checked = std::chrono::duration, std::chrono::nanoseconds::period>; + using TimePoint_Checked = std::chrono::time_point; + using TimePointSys_Checked = std::chrono::time_point; + using ZonedTime_Checked = std::chrono::zoned_time; + + // see https://www.w3.org/TR/xpath-functions/#comp.datetime + inline constexpr YearMonthDay time_point_replacement_date{Year(1972), std::chrono::January, std::chrono::day{1}}; + inline constexpr DurationNano time_point_replacement_time_of_day{0}; + // implementation defined, not from standard + inline constexpr Timezone time_point_replacement_timezone{std::chrono::minutes{0}}; + + // TODO check return values + constexpr std::optional construct_timepoint(YearMonthDay const &date, DurationNano const &time_of_day) noexcept { + auto sd = date.to_time_point_local(); + if (!sd.has_value()) { + return std::nullopt; + } + auto ms = static_cast(to_checked(*sd)); + ms += time_of_day; + return from_checked(ms); + } -constexpr std::pair deconstruct_timepoint(TimePoint const &tp) { - auto days = std::chrono::floor>(tp); - return {YearMonthDay{days}, tp - days}; -} + // TODO check return values + constexpr std::optional> deconstruct_timepoint(TimePoint const &tp) noexcept { + auto const days = std::chrono::floor>(to_checked(tp)); + if (days.time_since_epoch().count().is_invalid()) { + return std::nullopt; + } + auto const h = tp - days; + if (h.count().is_invalid()) { + return std::nullopt; + } + auto ymd = YearMonthDay::from_local_time_point_checked(*from_checked(days)); + if (!ymd.has_value()) { + return std::nullopt; + } + return std::pair{*ymd, *from_checked(h)}; + } -} // namespace util + } // namespace util } // namespace rdf4cpp @@ -466,7 +554,7 @@ namespace std::chrono { return ::rdf4cpp::Timezone{}; } }; -} // namespace std::chrono +} // namespace std::chrono #ifndef DOXYGEN_PARSER template @@ -523,7 +611,11 @@ struct std::formatter : std::formatter { template<> struct std::formatter : std::formatter { inline auto format(rdf4cpp::TimePoint const &p, format_context &ctx) const { - auto [date, time] = rdf4cpp::util::deconstruct_timepoint(p); + auto const dc = rdf4cpp::util::deconstruct_timepoint(p); + if (!dc.has_value()) { + return std::format_to(ctx.out(), "{}", "invalid timepoint"); + } + auto [date, time] = *dc; return std::format_to(ctx.out(), "{}T{:%H:%M:%S}", date, std::chrono::hh_mm_ss{std::chrono::duration_cast(time)}); } }; @@ -547,7 +639,7 @@ namespace rdf4cpp { std::format_to(std::ostream_iterator{os}, "{}", value); return os; } -} +} // namespace rdf4cpp #endif #endif //RDF4CPP_TIMEZONE_HPP diff --git a/src/rdf4cpp/datatypes/registry/util/DateTimeUtils.hpp b/src/rdf4cpp/datatypes/registry/util/DateTimeUtils.hpp index 06426d150..10f75d1d1 100644 --- a/src/rdf4cpp/datatypes/registry/util/DateTimeUtils.hpp +++ b/src/rdf4cpp/datatypes/registry/util/DateTimeUtils.hpp @@ -18,94 +18,6 @@ */ namespace rdf4cpp::datatypes::registry::util { -using CheckedMilliseconds = std::chrono::duration, std::milli>; -using CheckedMonths = std::chrono::duration, std::ratio<2629746>>; -static_assert(std::same_as); -using CheckedTimePoint = std::chrono::time_point; -using CheckedZonedTime = std::chrono::zoned_time; -using CheckedTimePointSys = std::chrono::time_point; - -/** - * turns any duration to its CheckedIntegral counterpart. - * @tparam R - * @param v - * @return - */ -template -std::chrono::duration, R> to_checked(std::chrono::duration v) noexcept { - return std::chrono::duration, R>{v.count()}; -} -/** - * turns any CheckedIntegral duration back to its integer based duration. - * @note undefined behavior, if v is invalid - * @tparam R - * @param v - * @return - */ -template -std::chrono::duration from_checked(std::chrono::duration, R> v) noexcept { - assert(!v.count().is_invalid()); - return std::chrono::duration{v.count().get_value()}; -} -/** - * turns any time_point to its CheckedIntegral counterpart. - * @tparam C - * @tparam R - * @param v - * @return - */ -template -std::chrono::time_point, R>> to_checked(std::chrono::time_point> v) noexcept { - return std::chrono::time_point, R>>{to_checked(v.time_since_epoch())}; -} -/** - * turns any CheckedIntegral time_point back to its integer based time_point. - * @note undefined behavior, if v is invalid - * @tparam C - * @tparam R - * @param v - * @return - */ -template -std::chrono::time_point> from_checked(std::chrono::time_point, R>> v) noexcept { - return std::chrono::time_point>{from_checked(v.time_since_epoch())}; -} - -/** - * checks if a double fits into a specified integer type. - * @tparam I - * @param d - * @return - */ -template -bool fits_into(J d) { - if constexpr (std::is_floating_point_v) { - if (std::isnan(d) || std::isinf(d)) { - return false; - } - } - if (d >= static_cast(std::numeric_limits::max())) { - return false; - } - if (d <= static_cast(std::numeric_limits::min())) { - return false; - } - return true; -} - -/** - * checks if a boost::multiprecision::number fits into a specified integer type. - * @tparam I - * @tparam B - * @tparam e - * @param n - * @return - */ -template -constexpr bool fits_into(boost::multiprecision::number const &n) { - return n <= std::numeric_limits::max() && n >= std::numeric_limits::min(); -} - template ResultType parse_date_time_fragment(std::string_view &s) { std::string_view res_s = s; @@ -174,84 +86,109 @@ inline char *canonical_seconds_remove_empty_millis(char *it) { return it; } -inline TimePoint add_duration_to_date_time(TimePoint const &tp, std::pair d) { - // only gets smaller, no overflow possible - auto [ymd, time] = rdf4cpp::util::deconstruct_timepoint(tp); +template +inline nonstd::expected optional_to_overflow(std::optional const &o) { + if (o.has_value()) { + return *o; + } else { + return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); + } +} + +/** + * returns nullopt on overflow + * @param tp + * @param d + * @return + */ +inline nonstd::expected add_duration_to_date_time(TimePoint const &tp, std::pair d) noexcept { + auto dec_tp = rdf4cpp::util::deconstruct_timepoint(tp); + if (!dec_tp.has_value()) { + return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); + } + auto [ymd, time] = *dec_tp; + + using Checked = rdf4cpp::util::CheckedIntegral; - boost::multiprecision::checked_int128_t checked_m = static_cast(ymd.month()); - checked_m += boost::multiprecision::checked_int128_t{static_cast(ymd.year())} * 12; + Checked checked_m = static_cast(ymd.month()); + checked_m += Checked{static_cast(ymd.year())} * 12; checked_m += d.first.count(); auto const checked_y = (checked_m -1 ) / 12; checked_m = abs(checked_m - 1); - auto const y = Year{static_cast(checked_y)}; - auto const m = std::chrono::month{static_cast(static_cast(checked_m % 12 + 1))}; + auto const y_casted = checked_y.checked_cast(); + if (y_casted.is_invalid()) { + return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); + } + + auto const y = Year{y_casted.get_value()}; + + auto const m_casted = (checked_m % 12 + 1).checked_cast(); + if (m_casted.is_invalid()) { + return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); + } + auto const m = std::chrono::month{m_casted.get_value()}; ymd = YearMonthDay{y, m, ymd.day()}; if (!ymd.ok()) { ymd = YearMonthDay{ymd.year(), ymd.month(), std::chrono::last}; } - TimePoint date = ymd.to_time_point_local(); + auto date_opt = ymd.to_time_point_local(); + if (!date_opt.has_value()) { + return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); + } + rdf4cpp::util::TimePoint_Checked date = *date_opt; date += time; date += d.second; - rdf4cpp::util::deconstruct_timepoint(date); // check if it still fits into year, throws if not + auto date_uc = rdf4cpp::util::from_checked(date); + if (!date_uc.has_value()) { + return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); + } + + auto const deconstructed = rdf4cpp::util::deconstruct_timepoint(*date_uc); + if (!deconstructed.has_value()) { + return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); + } - return date; + return *date_uc; } -inline nonstd::expected timepoint_sub(std::pair const &lhs, std::pair const &rhs) noexcept { - try { - ZonedTime const this_tp{lhs.second.has_value() ? *lhs.second : Timezone{}, - lhs.first}; +inline rdf4cpp::util::ZonedTime_Checked apply_tz_checked(TimePoint tp, OptionalTimezone tz) { + return {tz.has_value() ? *tz : rdf4cpp::util::time_point_replacement_timezone, rdf4cpp::util::to_checked(tp)}; +} +inline rdf4cpp::util::ZonedTime_Checked apply_tz_checked(std::pair const &tp) { + return apply_tz_checked(tp.first, tp.second); +} - ZonedTime const other_tp{rhs.second.has_value() ? *rhs.second : Timezone{}, - rhs.first}; +inline nonstd::expected timepoint_sub(std::pair const &lhs, std::pair const &rhs) noexcept { + rdf4cpp::util::ZonedTime_Checked const this_tp = apply_tz_checked(lhs); + rdf4cpp::util::ZonedTime_Checked const other_tp = apply_tz_checked(rhs); - auto d = this_tp.get_sys_time() - other_tp.get_sys_time(); - return std::chrono::duration_cast(d); - } catch (std::overflow_error const &) { - return nonstd::make_unexpected(DynamicError::OverOrUnderFlow); - } + auto d = this_tp.get_sys_time() - other_tp.get_sys_time(); + return util::optional_to_overflow(rdf4cpp::util::from_checked(std::chrono::duration_cast, std::chrono::nanoseconds::period>>(d))); } static inline std::partial_ordering compare_time_points(const rdf4cpp::TimePoint& a, std::optional atz, const rdf4cpp::TimePoint& b, std::optional btz) noexcept { - auto apply_timezone = [](const rdf4cpp::TimePoint& t, rdf4cpp::Timezone tz) noexcept -> std::optional { - try { - return rdf4cpp::ZonedTime{tz, t}.get_sys_time(); - } catch (const std::overflow_error&) { - return std::nullopt; - } - }; - - auto const cmp = [](const auto& a, const auto& b) noexcept -> std::partial_ordering { - if (a == b) - return std::partial_ordering::equivalent; - else if (a < b) - return std::partial_ordering::less; - else if (a > b) - return std::partial_ordering::greater; + auto const a_sys = apply_tz_checked(a, atz).get_sys_time(); + auto const b_sys = apply_tz_checked(b, btz).get_sys_time(); + if (a_sys.time_since_epoch().count().is_invalid() || b_sys.time_since_epoch().count().is_invalid()) { return std::partial_ordering::unordered; - }; - auto const cmp_opt = [cmp](const std::optional& a, const std::optional& b) noexcept -> std::partial_ordering { - if (!a.has_value() || !b.has_value()) - return std::partial_ordering::unordered; - return cmp(*a, *b); - }; - - if (!atz.has_value()) { - atz = rdf4cpp::util::time_point_replacement_timezone; } - if (!btz.has_value()) { - btz = rdf4cpp::util::time_point_replacement_timezone; + return a_sys <=> b_sys; +} +template +requires std::same_as> || std::same_as> +inline static std::partial_ordering compare_time_points(T const &a, std::optional atz, + T const &b, std::optional btz) noexcept { + if (!a.has_value() || !b.has_value()) { + return std::partial_ordering::unordered; } - - auto a_sys = apply_timezone(a, *atz); - return cmp_opt(a_sys, apply_timezone(b, *btz)); + return compare_time_points(*a, atz, *b, btz); } template constexpr T number_of_bits(T x) noexcept { @@ -315,10 +252,14 @@ struct __attribute__((__packed__)) InliningHelperPacked { } }; -inline YearMonthDay normalize(YearMonthDay const &i) { +inline nonstd::expected normalize(YearMonthDay const &i) { // normalize // see https://en.cppreference.com/w/cpp/chrono/year_month_day/operator_days - return YearMonthDay{(i + std::chrono::months{0}).to_time_point()}; + auto t = (i + std::chrono::months{0}).to_time_point(); + if (!t.has_value()) { + return nonstd::make_unexpected(DynamicError::OverOrUnderFlow); + } + return util::optional_to_overflow(YearMonthDay::from_time_point_checked(*t)); } template diff --git a/src/rdf4cpp/datatypes/xsd/time/Date.cpp b/src/rdf4cpp/datatypes/xsd/time/Date.cpp index 8d928c0e3..3946ff3de 100644 --- a/src/rdf4cpp/datatypes/xsd/time/Date.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/Date.cpp @@ -14,7 +14,11 @@ capabilities::Default::cpp_type capabilities::Default::from_ auto day = parse_date_time_fragment(s); auto date = YearMonthDay{year, month, day}; if (registry::relaxed_parsing_mode && !date.ok()) { - date = normalize(date); + auto const norm = normalize(date); + if (!norm.has_value()) { + throw InvalidNode(std::format("{} parsing error: failed to normalize", identifier, date)); + } + date = *norm; } if (!date.ok()) { throw InvalidNode(std::format("{} parsing error: {} is invalid", identifier, date)); @@ -50,11 +54,12 @@ std::optional capabilities::Inlineable if (value.second.has_value()) { return std::nullopt; } - auto i = value.first.to_time_point().time_since_epoch().count(); - if (!util::fits_into(i)) [[unlikely]] { + auto i = value.first.to_time_point()->time_since_epoch().count(); + int64_t i64; + if (rdf4cpp::util::detail::cast_checked(i, i64)) [[unlikely]] { return std::nullopt; } - return util::try_pack_integral(static_cast(i)); + return util::try_pack_integral(i64); } template<> @@ -64,7 +69,7 @@ capabilities::Inlineable::cpp_type capabilities::Inlineable: } rdf4cpp::TimePoint date_to_tp(YearMonthDay const &d) noexcept { - return rdf4cpp::util::construct_timepoint(d, rdf4cpp::util::time_point_replacement_time_of_day); + return *rdf4cpp::util::construct_timepoint(d, rdf4cpp::util::time_point_replacement_time_of_day); } template<> @@ -97,8 +102,15 @@ nonstd::expected::cpp_type, DynamicError> capabilities::Timepoint::timepoint_duration_add(cpp_type const &tp, timepoint_duration_operand_cpp_type const &dur) noexcept { auto const super_tp = Promotable::promote(tp); auto res_tp = util::add_duration_to_date_time(super_tp.first, dur); + if (!res_tp.has_value()) { + return nonstd::make_unexpected(res_tp.error()); + } - auto [date, _] = rdf4cpp::util::deconstruct_timepoint(res_tp); + auto deconstructed = rdf4cpp::util::deconstruct_timepoint(*res_tp); + if (!deconstructed.has_value()) { + return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); + } + auto [date, _] = *deconstructed; return std::make_pair(date, super_tp.second); } @@ -106,9 +118,26 @@ template<> nonstd::expected::cpp_type, DynamicError> capabilities::Timepoint::timepoint_duration_sub(cpp_type const &tp, timepoint_duration_operand_cpp_type const &dur) noexcept { auto const super_tp = Promotable::promote(tp); - auto res_tp = util::add_duration_to_date_time(super_tp.first, std::make_pair(-dur.first, -dur.second)); - auto [date, _] = rdf4cpp::util::deconstruct_timepoint(res_tp); + auto const sdur_month = rdf4cpp::util::from_checked(-rdf4cpp::util::to_checked(dur.first)); + auto const sdur_nanos = rdf4cpp::util::from_checked(-rdf4cpp::util::to_checked((dur.second))); + if (!sdur_month.has_value()) { + return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); + } + if (!sdur_nanos.has_value()) { + return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); + } + + auto res_tp = util::add_duration_to_date_time(super_tp.first, std::make_pair(*sdur_month, *sdur_nanos)); + if (!res_tp.has_value()) { + return nonstd::make_unexpected(res_tp.error()); + } + + auto deconstructed = rdf4cpp::util::deconstruct_timepoint(*res_tp); + if (!deconstructed.has_value()) { + return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); + } + auto [date, _] = *deconstructed; return std::make_pair(date, super_tp.second); } diff --git a/src/rdf4cpp/datatypes/xsd/time/DateTime.cpp b/src/rdf4cpp/datatypes/xsd/time/DateTime.cpp index 063dc3b8c..a530448ab 100644 --- a/src/rdf4cpp/datatypes/xsd/time/DateTime.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/DateTime.cpp @@ -17,7 +17,11 @@ capabilities::Default::cpp_type capabilities::Default(s); auto date = YearMonthDay{year, month, day}; if (registry::relaxed_parsing_mode && !date.ok()) { - date = normalize(date); + auto const norm = normalize(date); + if (!norm.has_value()) { + throw InvalidNode(std::format("{} parsing error: failed to normalize", identifier, date)); + } + date = *norm; } if (!date.ok()) { throw InvalidNode(std::format("{} parsing error: {} is invalid", identifier, date)); @@ -36,7 +40,11 @@ capabilities::Default::cpp_type capabilities::Default::cpp_type capabilities::Default @@ -59,7 +71,7 @@ bool capabilities::Default::serialize_canonical_string(cpp_type co registry::util::chrono_max_canonical_string_chars::minutes + 1 + registry::util::chrono_max_canonical_string_chars::seconds + Timezone::max_canonical_string_chars> buff; - auto [date, time] = rdf4cpp::util::deconstruct_timepoint(value.first); + auto [date, time] = *rdf4cpp::util::deconstruct_timepoint(value.first); char *it = std::format_to(buff.data(), "{}T{:%H:%M:%S}", date, std::chrono::hh_mm_ss{std::chrono::duration_cast(time)}); it = util::canonical_seconds_remove_empty_millis(it); if (value.second.has_value()) { @@ -74,14 +86,14 @@ template<> std::optional capabilities::Inlineable::try_into_inlined(cpp_type const &value) noexcept { if (value.second.has_value()) return std::nullopt; - auto tp_sec = std::chrono::floor>(value.first); + auto tp_sec = std::chrono::floor>(value.first); if ((value.first - tp_sec).count() != 0) return std::nullopt; - if (!util::fits_into(tp_sec.time_since_epoch().count())) { + int64_t i64; + if (rdf4cpp::util::detail::cast_checked(tp_sec.time_since_epoch().count(), i64)) { return std::nullopt; } - auto s = static_cast(tp_sec.time_since_epoch().count()); - return util::try_pack_integral(s); + return util::try_pack_integral(i64); } template<> @@ -103,23 +115,21 @@ capabilities::Timepoint::timepoint_sub(cpp_type const &lhs, cpp_ty template<> nonstd::expected::cpp_type, DynamicError> capabilities::Timepoint::timepoint_duration_add(cpp_type const &tp, timepoint_duration_operand_cpp_type const &dur) noexcept { - try { - auto ret_tp = util::add_duration_to_date_time(tp.first, dur); - return std::make_pair(ret_tp, tp.second); - } catch (std::overflow_error const &) { - return nonstd::make_unexpected(DynamicError::OverOrUnderFlow); + auto ret_tp = util::add_duration_to_date_time(tp.first, dur); + if (!ret_tp.has_value()) { + return nonstd::make_unexpected(ret_tp.error()); } + return std::make_pair(*ret_tp, tp.second); } template<> nonstd::expected::cpp_type, DynamicError> capabilities::Timepoint::timepoint_duration_sub(cpp_type const &tp, timepoint_duration_operand_cpp_type const &dur) noexcept { - try { - auto ret_tp = util::add_duration_to_date_time(tp.first, std::make_pair(-dur.first, -dur.second)); - return std::make_pair(ret_tp, tp.second); - } catch (std::overflow_error const &) { - return nonstd::make_unexpected(DynamicError::OverOrUnderFlow); + auto ret_tp = util::add_duration_to_date_time(tp.first, std::make_pair(-dur.first, -dur.second)); + if (!ret_tp.has_value()) { + return nonstd::make_unexpected(ret_tp.error()); } + return std::make_pair(*ret_tp, tp.second); } #endif diff --git a/src/rdf4cpp/datatypes/xsd/time/DateTimeStamp.cpp b/src/rdf4cpp/datatypes/xsd/time/DateTimeStamp.cpp index bd5cc7d79..5b563c1f3 100644 --- a/src/rdf4cpp/datatypes/xsd/time/DateTimeStamp.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/DateTimeStamp.cpp @@ -24,7 +24,11 @@ capabilities::Default::cpp_type capabilities::Default(s.substr(0, p)); auto date = YearMonthDay{year, month, day}; if (registry::relaxed_parsing_mode && !date.ok()) { - date = normalize(date); + auto const norm = normalize(date); + if (!norm.has_value()) { + throw InvalidNode(std::format("{} parsing error: failed to normalize", identifier, date)); + } + date = *norm; } if (!date.ok()) { throw InvalidNode(std::format("{} parsing error: {} is invalid", identifier, date)); @@ -43,7 +47,11 @@ capabilities::Default::cpp_type capabilities::Default::cpp_type capabilities::Default @@ -66,7 +77,7 @@ bool capabilities::Default::serialize_canonical_string(cpp_ty registry::util::chrono_max_canonical_string_chars::minutes + 1 + registry::util::chrono_max_canonical_string_chars::seconds + Timezone::max_canonical_string_chars> buff; - auto [date, time] = rdf4cpp::util::deconstruct_timepoint(value.get_local_time()); + auto [date, time] = *rdf4cpp::util::deconstruct_timepoint(value.get_local_time()); char *it = std::format_to(buff.data(), "{}T{:%H:%M:%S}", date, std::chrono::hh_mm_ss{std::chrono::duration_cast(time)}); it = util::canonical_seconds_remove_empty_millis(it); it = value.get_time_zone().to_canonical_string(it); @@ -82,10 +93,11 @@ std::optional capabilities::Inlineable(value.get_sys_time()); if ((value.get_sys_time() - tp_sec).count() != 0) return std::nullopt; - if (!util::fits_into(tp_sec.time_since_epoch().count())) + int64_t i64; + if (rdf4cpp::util::detail::cast_checked(tp_sec.time_since_epoch().count(), i64)) { return std::nullopt; - auto s = static_cast(tp_sec.time_since_epoch().count()); - return util::try_pack_integral(s); + } + return util::try_pack_integral(i64); } template<> @@ -133,23 +145,21 @@ capabilities::Timepoint::timepoint_sub(cpp_type const &lhs, c template<> nonstd::expected::cpp_type, DynamicError> capabilities::Timepoint::timepoint_duration_add(cpp_type const &tp, timepoint_duration_operand_cpp_type const &dur) noexcept { - try { - auto ret_tp = util::add_duration_to_date_time(tp.get_local_time(), dur); - return ZonedTime{tp.get_time_zone(), ret_tp}; - } catch (std::overflow_error const &) { - return nonstd::make_unexpected(DynamicError::OverOrUnderFlow); + auto ret_tp = util::add_duration_to_date_time(tp.get_local_time(), dur); + if (!ret_tp.has_value()) { + return nonstd::make_unexpected(ret_tp.error()); } + return ZonedTime{tp.get_time_zone(), *ret_tp}; } template<> nonstd::expected::cpp_type, DynamicError> capabilities::Timepoint::timepoint_duration_sub(cpp_type const &tp, timepoint_duration_operand_cpp_type const &dur) noexcept { - try { - auto ret_tp = util::add_duration_to_date_time(tp.get_local_time(), std::make_pair(-dur.first, -dur.second)); - return ZonedTime{tp.get_time_zone(), ret_tp}; - } catch (std::overflow_error const &) { - return nonstd::make_unexpected(DynamicError::OverOrUnderFlow); + auto ret_tp = util::add_duration_to_date_time(tp.get_local_time(), std::make_pair(-dur.first, -dur.second)); + if (!ret_tp.has_value()) { + return nonstd::make_unexpected(ret_tp.error()); } + return ZonedTime{tp.get_time_zone(), *ret_tp}; } #endif diff --git a/src/rdf4cpp/datatypes/xsd/time/Day.cpp b/src/rdf4cpp/datatypes/xsd/time/Day.cpp index 819d9dd53..56b0e0509 100644 --- a/src/rdf4cpp/datatypes/xsd/time/Day.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/Day.cpp @@ -38,7 +38,7 @@ bool capabilities::Default::serialize_canonical_string(cpp_type const template<> std::partial_ordering capabilities::Comparable::compare(cpp_type const &lhs, cpp_type const &rhs) noexcept { - auto day_to_tp = [](std::chrono::day d) noexcept -> rdf4cpp::TimePoint { + auto day_to_tp = [](std::chrono::day d) noexcept -> std::optional { return rdf4cpp::util::construct_timepoint(YearMonthDay{rdf4cpp::util::time_point_replacement_date.year(), rdf4cpp::util::time_point_replacement_date.month(), d}, rdf4cpp::util::time_point_replacement_time_of_day); }; return registry::util::compare_time_points(day_to_tp(lhs.first), lhs.second, day_to_tp(rhs.first), rhs.second); diff --git a/src/rdf4cpp/datatypes/xsd/time/DayTimeDuration.cpp b/src/rdf4cpp/datatypes/xsd/time/DayTimeDuration.cpp index 2e03233d3..926cbe08a 100644 --- a/src/rdf4cpp/datatypes/xsd/time/DayTimeDuration.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/DayTimeDuration.cpp @@ -148,23 +148,15 @@ nonstd::expected::cpp_type, DynamicEr template<> nonstd::expected::cpp_type, DynamicError> capabilities::Duration::duration_add(cpp_type const &lhs, cpp_type const &rhs) noexcept { - auto r = util::to_checked(lhs) + util::to_checked(rhs); - if (r.count().is_invalid()) { - return nonstd::make_unexpected(DynamicError::OverOrUnderFlow); - } - - return util::from_checked(r); + auto r = rdf4cpp::util::to_checked(lhs) + rdf4cpp::util::to_checked(rhs); + return util::optional_to_overflow(rdf4cpp::util::from_checked(r)); } template<> nonstd::expected::cpp_type, DynamicError> capabilities::Duration::duration_sub(cpp_type const &lhs, cpp_type const &rhs) noexcept { - auto r = util::to_checked(lhs) - util::to_checked(rhs); - if (r.count().is_invalid()) { - return nonstd::make_unexpected(DynamicError::OverOrUnderFlow); - } - - return util::from_checked(r); + auto r = rdf4cpp::util::to_checked(lhs) - rdf4cpp::util::to_checked(rhs); + return util::optional_to_overflow(rdf4cpp::util::from_checked(r)); } template<> @@ -182,7 +174,8 @@ template<> nonstd::expected::cpp_type, DynamicError> capabilities::Duration::duration_scalar_mul(cpp_type const &dur, duration_scalar_cpp_type const &scalar) noexcept { auto r = std::round(static_cast(dur.count()) * scalar); - if (!datatypes::registry::util::fits_into(r)) { + int64_t i64; + if (rdf4cpp::util::detail::cast_checked(r, i64)) { return nonstd::make_unexpected(DynamicError::OverOrUnderFlow); } @@ -197,11 +190,12 @@ capabilities::Duration::duration_scalar_div(cpp_type const } auto r = std::round(static_cast(dur.count()) / scalar); - if (!datatypes::registry::util::fits_into(r)) { + int64_t i64; + if (rdf4cpp::util::detail::cast_checked(r, i64)) { return nonstd::make_unexpected(DynamicError::OverOrUnderFlow); } - return std::chrono::nanoseconds{static_cast(r)}; + return std::chrono::nanoseconds{i64}; } #endif diff --git a/src/rdf4cpp/datatypes/xsd/time/Duration.cpp b/src/rdf4cpp/datatypes/xsd/time/Duration.cpp index d04098c53..49ac8dd85 100644 --- a/src/rdf4cpp/datatypes/xsd/time/Duration.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/Duration.cpp @@ -189,10 +189,10 @@ capabilities::Inlineable::cpp_type capabilities::Inlineable std::partial_ordering capabilities::Comparable::compare(cpp_type const &lhs, cpp_type const &rhs) noexcept { static constexpr std::array to_compare{ - rdf4cpp::util::construct_timepoint(YearMonthDay{Year{1696}, std::chrono::month{9}, std::chrono::day{1}}, std::chrono::milliseconds{0}), - rdf4cpp::util::construct_timepoint(YearMonthDay{Year{1697}, std::chrono::month{2}, std::chrono::day{1}}, std::chrono::milliseconds{0}), - rdf4cpp::util::construct_timepoint(YearMonthDay{Year{1903}, std::chrono::month{3}, std::chrono::day{1}}, std::chrono::milliseconds{0}), - rdf4cpp::util::construct_timepoint(YearMonthDay{Year{1903}, std::chrono::month{7}, std::chrono::day{1}}, std::chrono::milliseconds{0}), + *rdf4cpp::util::construct_timepoint(YearMonthDay{Year{1696}, std::chrono::month{9}, std::chrono::day{1}}, std::chrono::milliseconds{0}), + *rdf4cpp::util::construct_timepoint(YearMonthDay{Year{1697}, std::chrono::month{2}, std::chrono::day{1}}, std::chrono::milliseconds{0}), + *rdf4cpp::util::construct_timepoint(YearMonthDay{Year{1903}, std::chrono::month{3}, std::chrono::day{1}}, std::chrono::milliseconds{0}), + *rdf4cpp::util::construct_timepoint(YearMonthDay{Year{1903}, std::chrono::month{7}, std::chrono::day{1}}, std::chrono::milliseconds{0}), }; auto cmp = [lhs, rhs](const rdf4cpp::TimePoint& tp) noexcept { try { @@ -214,8 +214,8 @@ std::partial_ordering capabilities::Comparable::compare(cpp_type c template<> nonstd::expected::cpp_type, DynamicError> capabilities::Duration::duration_add(cpp_type const &lhs, cpp_type const &rhs) noexcept { - auto mon = util::to_checked(lhs.first) + util::to_checked(rhs.first); - auto nanos = util::to_checked(lhs.second) + util::to_checked(rhs.second); + auto mon = rdf4cpp::util::to_checked(lhs.first) + rdf4cpp::util::to_checked(rhs.first); + auto nanos = rdf4cpp::util::to_checked(lhs.second) + rdf4cpp::util::to_checked(rhs.second); if (mon.count().is_invalid() || nanos.count().is_invalid()) { return nonstd::make_unexpected(DynamicError::OverOrUnderFlow); } @@ -225,14 +225,14 @@ capabilities::Duration::duration_add(cpp_type const &lhs, cpp_type if (mon > std::chrono::months{0} && nanos < std::chrono::nanoseconds{0}) { return nonstd::make_unexpected(DynamicError::Unsupported); } - return std::make_pair(util::from_checked(mon), util::from_checked(nanos)); + return std::make_pair(*rdf4cpp::util::from_checked(mon), *rdf4cpp::util::from_checked(nanos)); } template<> nonstd::expected::cpp_type, DynamicError> capabilities::Duration::duration_sub(cpp_type const &lhs, cpp_type const &rhs) noexcept { - auto mon = util::to_checked(lhs.first) - util::to_checked(rhs.first); - auto nanos = util::to_checked(lhs.second) - util::to_checked(rhs.second); + auto mon = rdf4cpp::util::to_checked(lhs.first) - rdf4cpp::util::to_checked(rhs.first); + auto nanos = rdf4cpp::util::to_checked(lhs.second) - rdf4cpp::util::to_checked(rhs.second); if (mon.count().is_invalid() || nanos.count().is_invalid()) { return nonstd::make_unexpected(DynamicError::OverOrUnderFlow); } @@ -242,7 +242,7 @@ capabilities::Duration::duration_sub(cpp_type const &lhs, cpp_type if (mon > std::chrono::months{0} && nanos < std::chrono::nanoseconds{0}) { return nonstd::make_unexpected(DynamicError::Unsupported); } - return std::make_pair(util::from_checked(mon), util::from_checked(nanos)); + return std::make_pair(*rdf4cpp::util::from_checked(mon), *rdf4cpp::util::from_checked(nanos)); } template<> @@ -255,15 +255,17 @@ template<> nonstd::expected::cpp_type, DynamicError> capabilities::Duration::duration_scalar_mul(cpp_type const &dur, duration_scalar_cpp_type const &scalar) noexcept { auto mon = std::round(static_cast(dur.first.count()) * scalar); - if (!datatypes::registry::util::fits_into(mon)) { + int64_t m64; + if (rdf4cpp::util::detail::cast_checked(mon, m64)) { return nonstd::make_unexpected(DynamicError::OverOrUnderFlow); } auto nanos = std::round(static_cast(dur.second.count()) * scalar); - if (!datatypes::registry::util::fits_into(nanos)) { + int64_t n64; + if (rdf4cpp::util::detail::cast_checked(nanos, n64)) { return nonstd::make_unexpected(DynamicError::OverOrUnderFlow); } - return std::make_pair(std::chrono::months{static_cast(mon)}, std::chrono::nanoseconds{static_cast(nanos)}); + return std::make_pair(std::chrono::months{m64}, std::chrono::nanoseconds{n64}); } template<> @@ -274,15 +276,17 @@ capabilities::Duration::duration_scalar_div(cpp_type const &dur, d } auto mon = std::round(static_cast(dur.first.count()) / scalar); - if (!datatypes::registry::util::fits_into(mon)) { + int64_t m64; + if (rdf4cpp::util::detail::cast_checked(mon, m64)) { return nonstd::make_unexpected(DynamicError::OverOrUnderFlow); } auto nanos = std::round(static_cast(dur.second.count()) / scalar); - if (!datatypes::registry::util::fits_into(nanos)) { + int64_t n64; + if (rdf4cpp::util::detail::cast_checked(nanos, n64)) { return nonstd::make_unexpected(DynamicError::OverOrUnderFlow); } - return std::make_pair(std::chrono::months{static_cast(mon)}, std::chrono::nanoseconds{static_cast(nanos)}); + return std::make_pair(std::chrono::months{m64}, std::chrono::nanoseconds{n64}); } #endif diff --git a/src/rdf4cpp/datatypes/xsd/time/Month.cpp b/src/rdf4cpp/datatypes/xsd/time/Month.cpp index 95c7eeef3..bf22e77c9 100644 --- a/src/rdf4cpp/datatypes/xsd/time/Month.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/Month.cpp @@ -38,7 +38,7 @@ bool capabilities::Default::serialize_canonical_string(cpp_type cons template<> std::partial_ordering capabilities::Comparable::compare(cpp_type const &lhs, cpp_type const &rhs) noexcept { - auto month_to_tp = [](std::chrono::month m) noexcept -> rdf4cpp::TimePoint { + auto month_to_tp = [](std::chrono::month m) noexcept -> std::optional { return rdf4cpp::util::construct_timepoint(YearMonthDay{rdf4cpp::util::time_point_replacement_date.year(), m, std::chrono::last}, rdf4cpp::util::time_point_replacement_time_of_day); }; return registry::util::compare_time_points(month_to_tp(lhs.first), lhs.second, month_to_tp(rhs.first), rhs.second); diff --git a/src/rdf4cpp/datatypes/xsd/time/MonthDay.cpp b/src/rdf4cpp/datatypes/xsd/time/MonthDay.cpp index c938853cf..c04f76669 100644 --- a/src/rdf4cpp/datatypes/xsd/time/MonthDay.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/MonthDay.cpp @@ -61,7 +61,7 @@ capabilities::Inlineable::cpp_type capabilities::Inlineable std::partial_ordering capabilities::Comparable::compare(cpp_type const &lhs, cpp_type const &rhs) noexcept { - auto md_to_tp = [](std::chrono::month_day md) noexcept -> rdf4cpp::TimePoint { + auto md_to_tp = [](std::chrono::month_day md) noexcept -> std::optional { return rdf4cpp::util::construct_timepoint(YearMonthDay{rdf4cpp::util::time_point_replacement_date.year(), md.month(), md.day()}, rdf4cpp::util::time_point_replacement_time_of_day); }; return registry::util::compare_time_points(md_to_tp(lhs.first), lhs.second, md_to_tp(rhs.first), rhs.second); diff --git a/src/rdf4cpp/datatypes/xsd/time/Time.cpp b/src/rdf4cpp/datatypes/xsd/time/Time.cpp index f489023be..3f411a008 100644 --- a/src/rdf4cpp/datatypes/xsd/time/Time.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/Time.cpp @@ -77,7 +77,8 @@ std::partial_ordering capabilities::Comparable::compare(cpp_type const template<> template<> capabilities::Promotable::promoted_cpp_type<0> capabilities::Promotable::promote<0>(cpp_type const &value) noexcept { - return std::make_pair(rdf4cpp::util::construct_timepoint(rdf4cpp::util::time_point_replacement_date, value.first), value.second); + // time_point_replacement_date can not cause an overflow + return std::make_pair(*rdf4cpp::util::construct_timepoint(rdf4cpp::util::time_point_replacement_date, value.first), value.second); } template<> @@ -101,8 +102,15 @@ capabilities::Timepoint::timepoint_duration_add(cpp_type const &tp, ti auto const super_dur = Subtype::into_supertype(dur); auto ret_tp = util::add_duration_to_date_time(super_tp.first, super_dur); + if (!ret_tp.has_value()) { + return nonstd::make_unexpected(ret_tp.error()); + } - auto [_, time] = rdf4cpp::util::deconstruct_timepoint(ret_tp); + auto deconstructed = rdf4cpp::util::deconstruct_timepoint(*ret_tp); + if (!deconstructed.has_value()) { + return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); + } + auto [_, time] = *deconstructed; return std::make_pair(std::chrono::duration_cast(time), super_tp.second); } @@ -112,9 +120,25 @@ capabilities::Timepoint::timepoint_duration_sub(cpp_type const &tp, ti auto const super_tp = Promotable::promote(tp); auto const super_dur = Subtype::into_supertype(dur); - auto ret_tp = util::add_duration_to_date_time(super_tp.first, std::make_pair(-super_dur.first, -super_dur.second)); + auto const sdur_month = rdf4cpp::util::from_checked(-rdf4cpp::util::to_checked(super_dur.first)); + auto const sdur_nanos = rdf4cpp::util::from_checked(-rdf4cpp::util::to_checked((super_dur.second))); + if (!sdur_month.has_value()) { + return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); + } + if (!sdur_nanos.has_value()) { + return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); + } - auto [_, time] = rdf4cpp::util::deconstruct_timepoint(ret_tp); + auto ret_tp = util::add_duration_to_date_time(super_tp.first, std::make_pair(*sdur_month, *sdur_nanos)); + if (!ret_tp.has_value()) { + return nonstd::make_unexpected(ret_tp.error()); + } + + auto deconstructed = rdf4cpp::util::deconstruct_timepoint(*ret_tp); + if (!deconstructed.has_value()) { + return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); + } + auto [_, time] = *deconstructed; return std::make_pair(std::chrono::duration_cast(time), super_tp.second); } diff --git a/src/rdf4cpp/datatypes/xsd/time/Year.cpp b/src/rdf4cpp/datatypes/xsd/time/Year.cpp index fddba2a64..4bfacc981 100644 --- a/src/rdf4cpp/datatypes/xsd/time/Year.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/Year.cpp @@ -26,14 +26,10 @@ bool capabilities::Default::serialize_canonical_string(cpp_type const template<> std::partial_ordering capabilities::Comparable::compare(cpp_type const &lhs, cpp_type const &rhs) noexcept { - try { - auto year_to_tp = [](Year const &t) -> rdf4cpp::TimePoint { - return rdf4cpp::util::construct_timepoint(YearMonthDay{t, rdf4cpp::util::time_point_replacement_date.month(), rdf4cpp::util::time_point_replacement_date.day()}, rdf4cpp::util::time_point_replacement_time_of_day); - }; - return registry::util::compare_time_points(year_to_tp(lhs.first), lhs.second, year_to_tp(rhs.first), rhs.second); - } catch (std::overflow_error const &) { - return std::partial_ordering::unordered; - } + auto year_to_tp = [](Year const &t) -> std::optional { + return rdf4cpp::util::construct_timepoint(YearMonthDay{t, rdf4cpp::util::time_point_replacement_date.month(), rdf4cpp::util::time_point_replacement_date.day()}, rdf4cpp::util::time_point_replacement_time_of_day); + }; + return registry::util::compare_time_points(year_to_tp(lhs.first), lhs.second, year_to_tp(rhs.first), rhs.second); } template<> diff --git a/src/rdf4cpp/datatypes/xsd/time/YearMonth.cpp b/src/rdf4cpp/datatypes/xsd/time/YearMonth.cpp index c2c2f17ab..edc4fb75c 100644 --- a/src/rdf4cpp/datatypes/xsd/time/YearMonth.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/YearMonth.cpp @@ -46,11 +46,12 @@ std::optional capabilities::Inlineable(value.first.year()); - if (!util::fits_into(yearint)) [[unlikely]] { + int16_t yint16; + if (rdf4cpp::util::detail::cast_checked(yearint, yint16)) [[unlikely]] { return std::nullopt; } - return util::pack(InliningHelperYearMonth{static_cast(yearint), - static_cast(static_cast(value.first.month()))}); + return util::pack(InliningHelperYearMonth{yint16, + static_cast(static_cast(value.first.month()))}); } template<> @@ -61,7 +62,7 @@ capabilities::Inlineable::cpp_type capabilities::Inlineable std::partial_ordering capabilities::Comparable::compare(cpp_type const &lhs, cpp_type const &rhs) noexcept { - auto ym_to_tp = [](YearMonth const & t) -> rdf4cpp::TimePoint { + auto ym_to_tp = [](YearMonth const &t) -> std::optional { return rdf4cpp::util::construct_timepoint(YearMonthDay{t.year(), t.month(), std::chrono::last}, rdf4cpp::util::time_point_replacement_time_of_day); }; return registry::util::compare_time_points(ym_to_tp(lhs.first), lhs.second, ym_to_tp(rhs.first), rhs.second); diff --git a/src/rdf4cpp/datatypes/xsd/time/YearMonthDuration.cpp b/src/rdf4cpp/datatypes/xsd/time/YearMonthDuration.cpp index ddb23feb5..dc8ee70ab 100644 --- a/src/rdf4cpp/datatypes/xsd/time/YearMonthDuration.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/YearMonthDuration.cpp @@ -108,23 +108,15 @@ nonstd::expected::cpp_type, Dynamic template<> nonstd::expected::cpp_type, DynamicError> capabilities::Duration::duration_add(cpp_type const &lhs, cpp_type const &rhs) noexcept { - auto r = util::to_checked(lhs) + util::to_checked(rhs); - if (r.count().is_invalid()) { - return nonstd::make_unexpected(DynamicError::OverOrUnderFlow); - } - - return util::from_checked(r); + auto r = rdf4cpp::util::to_checked(lhs) + rdf4cpp::util::to_checked(rhs); + return util::optional_to_overflow(rdf4cpp::util::from_checked(r)); } template<> nonstd::expected::cpp_type, DynamicError> capabilities::Duration::duration_sub(cpp_type const &lhs, cpp_type const &rhs) noexcept { - auto r = util::to_checked(lhs) - util::to_checked(rhs); - if (r.count().is_invalid()) { - return nonstd::make_unexpected(DynamicError::OverOrUnderFlow); - } - - return util::from_checked(r); + auto r = rdf4cpp::util::to_checked(lhs) - rdf4cpp::util::to_checked(rhs); + return util::optional_to_overflow(rdf4cpp::util::from_checked(r)); } template<> @@ -142,11 +134,12 @@ template<> nonstd::expected::cpp_type, DynamicError> capabilities::Duration::duration_scalar_mul(cpp_type const &dur, duration_scalar_cpp_type const &scalar) noexcept { auto r = std::round(static_cast(dur.count()) * scalar); - if (!datatypes::registry::util::fits_into(r)) { + int64_t i64; + if (rdf4cpp::util::detail::cast_checked(r, i64)) { return nonstd::make_unexpected(DynamicError::OverOrUnderFlow); } - return std::chrono::months{static_cast(r)}; + return std::chrono::months{i64}; } template<> @@ -157,11 +150,12 @@ capabilities::Duration::duration_scalar_div(cpp_type cons } auto r = std::round(static_cast(dur.count()) / scalar); - if (!datatypes::registry::util::fits_into(r)) { + int64_t i64; + if (rdf4cpp::util::detail::cast_checked(r, i64)) { return nonstd::make_unexpected(DynamicError::OverOrUnderFlow); } - return std::chrono::months{static_cast(r)}; + return std::chrono::months{i64}; } #endif diff --git a/src/rdf4cpp/util/CheckedInt.hpp b/src/rdf4cpp/util/CheckedInt.hpp index cfc69f004..59895d2be 100644 --- a/src/rdf4cpp/util/CheckedInt.hpp +++ b/src/rdf4cpp/util/CheckedInt.hpp @@ -1,13 +1,15 @@ #ifndef RDF4CPP_CHECKEDINT_HPP #define RDF4CPP_CHECKEDINT_HPP +#include + namespace rdf4cpp::util { /** * Wraps an integer type and keeps track of Overflows and similar Undefined Behavior. * Designed to be used in std::chrono::duration, but may be used standalone. * @tparam I Integer type to wrap */ -template +template struct CheckedIntegral { private: I value; @@ -46,10 +48,13 @@ struct CheckedIntegral { return std::partial_ordering::unordered; return this->value <=> other.value; } + constexpr bool operator==(const CheckedIntegral &other) const noexcept { + return (*this <=> other) == std::partial_ordering::equivalent; + } constexpr CheckedIntegral &operator+=(const CheckedIntegral &other) noexcept { this->invalid |= other.invalid; - this->invalid |= __builtin_add_overflow(this->value, other.value, &this->value); + this->invalid |= detail::add_checked(this->value, other.value, this->value); return *this; } constexpr CheckedIntegral operator+(const CheckedIntegral &other) const noexcept { @@ -59,7 +64,7 @@ struct CheckedIntegral { } constexpr CheckedIntegral &operator-=(const CheckedIntegral &other) noexcept { this->invalid |= other.invalid; - this->invalid |= __builtin_sub_overflow(this->value, other.value, &this->value); + this->invalid |= detail::sub_checked(this->value, other.value, this->value); return *this; } constexpr CheckedIntegral operator-(const CheckedIntegral &other) const noexcept { @@ -67,9 +72,12 @@ struct CheckedIntegral { r -= other; return r; } + constexpr CheckedIntegral operator-() const noexcept { + return CheckedIntegral{I{0}} - *this; + } constexpr CheckedIntegral &operator*=(const CheckedIntegral &other) noexcept { this->invalid |= other.invalid; - this->invalid |= __builtin_mul_overflow(this->value, other.value, &this->value); + this->invalid |= detail::mul_checked(this->value, other.value, this->value); return *this; } constexpr CheckedIntegral operator*(const CheckedIntegral &other) const noexcept { @@ -90,6 +98,19 @@ struct CheckedIntegral { r /= other; return r; } + constexpr CheckedIntegral &operator%=(const CheckedIntegral &other) noexcept { + if (this->invalid || other.invalid || other.value == 0) { + this->invalid = true; + return *this; + } + this->value = this->value % other.value; + return *this; + } + constexpr CheckedIntegral operator%(const CheckedIntegral &other) const noexcept { + CheckedIntegral r = *this; + r %= other; + return r; + } friend constexpr CheckedIntegral abs(CheckedIntegral const &val) noexcept { if constexpr (std::is_unsigned_v) { @@ -100,12 +121,36 @@ struct CheckedIntegral { } CheckedIntegral ret{0, val.invalid}; - ret.invalid |= __builtin_sub_overflow(0, val.value, &ret.value); + ret.invalid |= detail::sub_checked(I{0}, val.value, ret.value); return ret; } } + + template + constexpr CheckedIntegral checked_cast() const noexcept { + To r = 0; + bool inv = detail::cast_checked(value, r); + return {r, inv || invalid}; + } + + template + requires std::convertible_to + constexpr CheckedIntegral(CheckedIntegral f) : CheckedIntegral(f.template checked_cast()) {} // chrono expects integer types to be implicitly convertible + template + requires (!std::convertible_to) + constexpr explicit CheckedIntegral(CheckedIntegral f) : CheckedIntegral(f.template checked_cast()) {} }; +static_assert(std::convertible_to, rdf4cpp::util::CheckedIntegral>); } // namespace rdf4cpp::util +template +struct std::common_type, rdf4cpp::util::CheckedIntegral> { + using type = rdf4cpp::util::CheckedIntegral::type>; +}; +static_assert(std::same_as, rdf4cpp::util::CheckedIntegral>::type, + rdf4cpp::util::CheckedIntegral<__int128>>); +static_assert(std::same_as, rdf4cpp::util::CheckedIntegral, std::intmax_t>::type, + rdf4cpp::util::CheckedIntegral<__int128>>); + #endif //RDF4CPP_CHECKEDINT_HPP diff --git a/src/rdf4cpp/util/Int128.hpp b/src/rdf4cpp/util/Int128.hpp index ae1285639..af254d3d3 100644 --- a/src/rdf4cpp/util/Int128.hpp +++ b/src/rdf4cpp/util/Int128.hpp @@ -305,8 +305,9 @@ namespace rdf4cpp::util { struct MakeUnsigned>> { using t = boost::multiprecision::number>; }; + // TODO check double->int64 conversion template - requires IntegralExt && IntegralExt + requires (IntegralExt || std::floating_point) && (IntegralExt || std::floating_point) static constexpr bool cast_checked(const From &f, To &result) noexcept { if constexpr (m == OverflowMode::Checked) { if constexpr (std::numeric_limits::is_signed == std::numeric_limits::is_signed) { @@ -420,6 +421,15 @@ inline std::ostream &operator<<(std::ostream &str, __int128 const &bn) { w.finalize(); return str; } +template<> +struct std::formatter<__int128> : std::formatter { + inline auto format(__int128 const &p, format_context &ctx) const { + rdf4cpp::writer::BufOutputIteratorWriter w{ctx.out()}; + rdf4cpp::util::to_chars_canonical(p, w); + w.finalize(); + return w.buffer().iter; + } +}; #endif #endif //RDF4CPP_INT128_HPP diff --git a/tests/datatype/tests_time_types.cpp b/tests/datatype/tests_time_types.cpp index df722d7eb..f5f4a6dda 100644 --- a/tests/datatype/tests_time_types.cpp +++ b/tests/datatype/tests_time_types.cpp @@ -176,12 +176,17 @@ TEST_CASE("precision") { CHECK(ys > std::chrono::years{10000}); CHECK(ys > std::chrono::years{static_cast(std::chrono::year::max())}); - rdf4cpp::DurationNano::rep end_of_universe{"100000000000000"}; + using I = rdf4cpp::util::Int128; + using CI = rdf4cpp::util::CheckedIntegral; + CI end_of_universe = rdf4cpp::datatypes::registry::util::from_chars("100000000000000"); CHECK(end_of_universe < std::numeric_limits::max()); - using checked_years = std::chrono::duration; - using checked_nanos = std::chrono::duration; - auto nanos = static_cast(checked_years{end_of_universe}); // throws, if out of range + using checked_years = std::chrono::duration; + using checked_nanos = std::chrono::duration; + auto nanos = static_cast(checked_years{end_of_universe}); + CHECK(!nanos.count().is_invalid()); CHECK(nanos.count() > end_of_universe); + auto r = CI(std::numeric_limits::max()) / nanos.count(); + CHECK(r > 0); } TEST_CASE("datatype gYear") { @@ -389,13 +394,13 @@ TEST_CASE("datatype dateTime") { CHECK(std::string(datatypes::xsd::DateTime::identifier) == "http://www.w3.org/2001/XMLSchema#dateTime"); rdf4cpp::OptionalTimezone const tz = std::nullopt; - basic_test(std::make_pair(rdf4cpp::util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50}), tz), "2042-05-01T00:50:00", std::partial_ordering::equivalent); - basic_test(std::make_pair(rdf4cpp::util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::hours{12} + std::chrono::minutes{34} + std::chrono::seconds{56} + std::chrono::milliseconds{789}), tz), "2042-05-01T12:34:56.789", std::partial_ordering::equivalent); - basic_test(std::make_pair(rdf4cpp::util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50} + std::chrono::milliseconds{100}), tz), "2042-05-01T00:50:00.1", std::partial_ordering::equivalent); - basic_test(std::make_pair(rdf4cpp::util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50} + std::chrono::nanoseconds{123456789}), tz), "2042-05-01T00:50:00.12345678910", std::partial_ordering::equivalent, true); - basic_test(std::make_pair(rdf4cpp::util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{42}), tz), "2042-05-01T00:50:00", std::partial_ordering::less); - basic_test(std::make_pair(rdf4cpp::util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50}), Timezone{std::chrono::hours{1}}), "2042-05-01T00:50:00+01:00", std::partial_ordering::equivalent); - basic_test(std::make_pair(rdf4cpp::util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50}), Timezone{std::chrono::minutes{-65}}), "2042-05-01T00:50:00-01:05", std::partial_ordering::equivalent); + basic_test(std::make_pair(*rdf4cpp::util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50}), tz), "2042-05-01T00:50:00", std::partial_ordering::equivalent); + basic_test(std::make_pair(*rdf4cpp::util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::hours{12} + std::chrono::minutes{34} + std::chrono::seconds{56} + std::chrono::milliseconds{789}), tz), "2042-05-01T12:34:56.789", std::partial_ordering::equivalent); + basic_test(std::make_pair(*rdf4cpp::util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50} + std::chrono::milliseconds{100}), tz), "2042-05-01T00:50:00.1", std::partial_ordering::equivalent); + basic_test(std::make_pair(*rdf4cpp::util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50} + std::chrono::nanoseconds{123456789}), tz), "2042-05-01T00:50:00.12345678910", std::partial_ordering::equivalent, true); + basic_test(std::make_pair(*rdf4cpp::util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{42}), tz), "2042-05-01T00:50:00", std::partial_ordering::less); + basic_test(std::make_pair(*rdf4cpp::util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50}), Timezone{std::chrono::hours{1}}), "2042-05-01T00:50:00+01:00", std::partial_ordering::equivalent); + basic_test(std::make_pair(*rdf4cpp::util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50}), Timezone{std::chrono::minutes{-65}}), "2042-05-01T00:50:00-01:05", std::partial_ordering::equivalent); basic_test("2042-05-05T13:40:08", "2042-05-05T13:40:08", std::partial_ordering::equivalent); basic_test("2041-05-05T13:40:08", "2042-05-05T13:40:08", std::partial_ordering::less); basic_test("2042-05-05T13:40:08", "2041-05-05T13:40:08", std::partial_ordering::greater); @@ -453,13 +458,13 @@ TEST_CASE("datatype dateTimeStamp") { CHECK(std::string(datatypes::xsd::DateTimeStamp::identifier) == "http://www.w3.org/2001/XMLSchema#dateTimeStamp"); Timezone const tz{std::chrono::hours{0}}; - basic_test(ZonedTime(tz, util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50})), "2042-05-01T00:50:00Z", std::partial_ordering::equivalent); - basic_test(ZonedTime(tz, util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::hours{12} + std::chrono::minutes{34} + std::chrono::seconds{56} + std::chrono::milliseconds{789})), "2042-05-01T12:34:56.789Z", std::partial_ordering::equivalent); - basic_test(ZonedTime(tz, util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50} + std::chrono::milliseconds{100})), "2042-05-01T00:50:00.1Z", std::partial_ordering::equivalent); - basic_test(ZonedTime(tz, util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50} + std::chrono::nanoseconds{123456789})), "2042-05-01T00:50:00.12345678910Z", std::partial_ordering::equivalent, true); - basic_test(ZonedTime(tz, util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{42})), "2042-05-01T00:50:00Z", std::partial_ordering::less); - basic_test(ZonedTime(Timezone{std::chrono::hours{1}}, util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50})), "2042-05-01T00:50:00+01:00", std::partial_ordering::equivalent); - basic_test(ZonedTime(Timezone{std::chrono::minutes{-65}}, util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50})), "2042-05-01T00:50:00-01:05", std::partial_ordering::equivalent); + basic_test(ZonedTime(tz, *util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50})), "2042-05-01T00:50:00Z", std::partial_ordering::equivalent); + basic_test(ZonedTime(tz, *util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::hours{12} + std::chrono::minutes{34} + std::chrono::seconds{56} + std::chrono::milliseconds{789})), "2042-05-01T12:34:56.789Z", std::partial_ordering::equivalent); + basic_test(ZonedTime(tz, *util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50} + std::chrono::milliseconds{100})), "2042-05-01T00:50:00.1Z", std::partial_ordering::equivalent); + basic_test(ZonedTime(tz, *util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50} + std::chrono::nanoseconds{123456789})), "2042-05-01T00:50:00.12345678910Z", std::partial_ordering::equivalent, true); + basic_test(ZonedTime(tz, *util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{42})), "2042-05-01T00:50:00Z", std::partial_ordering::less); + basic_test(ZonedTime(Timezone{std::chrono::hours{1}}, *util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50})), "2042-05-01T00:50:00+01:00", std::partial_ordering::equivalent); + basic_test(ZonedTime(Timezone{std::chrono::minutes{-65}}, *util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50})), "2042-05-01T00:50:00-01:05", std::partial_ordering::equivalent); basic_test("2042-05-05T13:40:08Z", "2042-05-05T13:40:08Z", std::partial_ordering::equivalent); basic_test("2041-05-05T13:40:08Z", "2042-05-05T13:40:08Z", std::partial_ordering::less); basic_test("2042-05-05T13:40:08Z", "2041-05-05T13:40:08Z", std::partial_ordering::greater); From 32cb9e186c11bcd961f3a73554ec43f13854a6a7 Mon Sep 17 00:00:00 2001 From: mcb Date: Thu, 3 Jul 2025 17:11:26 +0200 Subject: [PATCH 2/9] fix float->int checked conversion, reformat --- .../datatypes/registry/util/DateTimeUtils.hpp | 495 +++++++++--------- src/rdf4cpp/util/Int128.hpp | 33 +- tests/util/tests_BigDecimal.cpp | 12 + 3 files changed, 283 insertions(+), 257 deletions(-) diff --git a/src/rdf4cpp/datatypes/registry/util/DateTimeUtils.hpp b/src/rdf4cpp/datatypes/registry/util/DateTimeUtils.hpp index 10f75d1d1..c2cd8dc38 100644 --- a/src/rdf4cpp/datatypes/registry/util/DateTimeUtils.hpp +++ b/src/rdf4cpp/datatypes/registry/util/DateTimeUtils.hpp @@ -6,10 +6,10 @@ #include #include +#include +#include #include #include -#include -#include /** * @file @@ -18,291 +18,292 @@ */ namespace rdf4cpp::datatypes::registry::util { -template -ResultType parse_date_time_fragment(std::string_view &s) { - std::string_view res_s = s; - if constexpr (Separator != '\0') { - auto p = s.find(Separator, 1); + template + ResultType parse_date_time_fragment(std::string_view &s) { + std::string_view res_s = s; + if constexpr (Separator != '\0') { + auto p = s.find(Separator, 1); + if (p == std::string::npos) + throw InvalidNode(std::format("{} parse error: missing {}", datatype, Separator)); + res_s = s.substr(0, p); + s = s.substr(p + 1); + } + return ResultType{from_chars(res_s)}; + } + + template + std::optional parse_duration_fragment(std::string_view &s) { + if (s.empty()) + return std::nullopt; + std::string_view res_s = s; + auto p = s.find(Separator); if (p == std::string::npos) - throw InvalidNode(std::format("{} parse error: missing {}", datatype, Separator)); + return std::nullopt; res_s = s.substr(0, p); s = s.substr(p + 1); + return ResultType{from_chars(res_s)}; } - return ResultType{from_chars(res_s)}; -} - -template -std::optional parse_duration_fragment(std::string_view &s) { - if (s.empty()) - return std::nullopt; - std::string_view res_s = s; - auto p = s.find(Separator); - if (p == std::string::npos) - return std::nullopt; - res_s = s.substr(0, p); - s = s.substr(p + 1); - return ResultType{from_chars(res_s)}; -} - -template -inline std::chrono::nanoseconds parse_nanoseconds(std::string_view s) { - auto p = s.find('.'); - std::chrono::nanoseconds ms{}; - if (p != std::string::npos) { - auto milli_s = s.substr(p + 1, 9); - ms = std::chrono::nanoseconds{from_chars(milli_s)}; - for (size_t i = milli_s.length(); i < 9; ++i) { - ms *= 10; + + template + inline std::chrono::nanoseconds parse_nanoseconds(std::string_view s) { + auto p = s.find('.'); + std::chrono::nanoseconds ms{}; + if (p != std::string::npos) { + auto milli_s = s.substr(p + 1, 9); + ms = std::chrono::nanoseconds{from_chars(milli_s)}; + for (size_t i = milli_s.length(); i < 9; ++i) { + ms *= 10; + } + s = s.substr(0, p); } - s = s.substr(0, p); + std::chrono::seconds sec{from_chars(s)}; + return sec + ms; } - std::chrono::seconds sec{from_chars(s)}; - return sec + ms; -} - -template -inline std::optional parse_duration_nanoseconds(std::string_view &s) { - if (s.empty()) { - return std::nullopt; - } - std::string_view res_s = s; - auto p = s.find('S'); - if (p == std::string::npos) { - return std::nullopt; + + template + inline std::optional parse_duration_nanoseconds(std::string_view &s) { + if (s.empty()) { + return std::nullopt; + } + std::string_view res_s = s; + auto p = s.find('S'); + if (p == std::string::npos) { + return std::nullopt; + } + res_s = s.substr(0, p); + s = s.substr(p + 1); + return parse_nanoseconds(res_s); } - res_s = s.substr(0, p); - s = s.substr(p + 1); - return parse_nanoseconds(res_s); -} - -inline char *canonical_seconds_remove_empty_millis(char *it) { - for (size_t m = 0; m<9; ++m) { - if (*(it - 1) != '0') - return it; + + inline char *canonical_seconds_remove_empty_millis(char *it) { + for (size_t m = 0; m < 9; ++m) { + if (*(it - 1) != '0') + return it; + --it; + } + assert(*(it - 1) == '.'); --it; + return it; } - assert(*(it - 1) == '.'); - --it; - return it; -} - -template -inline nonstd::expected optional_to_overflow(std::optional const &o) { - if (o.has_value()) { - return *o; - } else { - return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); + + template + inline nonstd::expected optional_to_overflow(std::optional const &o) { + if (o.has_value()) { + return *o; + } else { + return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); + } } -} -/** + /** * returns nullopt on overflow * @param tp * @param d * @return */ -inline nonstd::expected add_duration_to_date_time(TimePoint const &tp, std::pair d) noexcept { - auto dec_tp = rdf4cpp::util::deconstruct_timepoint(tp); - if (!dec_tp.has_value()) { - return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); - } - auto [ymd, time] = *dec_tp; + inline nonstd::expected add_duration_to_date_time(TimePoint const &tp, std::pair d) noexcept { + auto dec_tp = rdf4cpp::util::deconstruct_timepoint(tp); + if (!dec_tp.has_value()) { + return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); + } + auto [ymd, time] = *dec_tp; - using Checked = rdf4cpp::util::CheckedIntegral; + using Checked = rdf4cpp::util::CheckedIntegral; - Checked checked_m = static_cast(ymd.month()); - checked_m += Checked{static_cast(ymd.year())} * 12; - checked_m += d.first.count(); + Checked checked_m = static_cast(ymd.month()); + checked_m += Checked{static_cast(ymd.year())} * 12; + checked_m += d.first.count(); - auto const checked_y = (checked_m -1 ) / 12; + auto const checked_y = (checked_m - 1) / 12; - checked_m = abs(checked_m - 1); + checked_m = abs(checked_m - 1); - auto const y_casted = checked_y.checked_cast(); - if (y_casted.is_invalid()) { - return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); - } + auto const y_casted = checked_y.checked_cast(); + if (y_casted.is_invalid()) { + return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); + } - auto const y = Year{y_casted.get_value()}; + auto const y = Year{y_casted.get_value()}; - auto const m_casted = (checked_m % 12 + 1).checked_cast(); - if (m_casted.is_invalid()) { - return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); - } - auto const m = std::chrono::month{m_casted.get_value()}; + auto const m_casted = (checked_m % 12 + 1).checked_cast(); + if (m_casted.is_invalid()) { + return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); + } + auto const m = std::chrono::month{m_casted.get_value()}; - ymd = YearMonthDay{y, m, ymd.day()}; - if (!ymd.ok()) { - ymd = YearMonthDay{ymd.year(), ymd.month(), std::chrono::last}; - } + ymd = YearMonthDay{y, m, ymd.day()}; + if (!ymd.ok()) { + ymd = YearMonthDay{ymd.year(), ymd.month(), std::chrono::last}; + } - auto date_opt = ymd.to_time_point_local(); - if (!date_opt.has_value()) { - return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); - } - rdf4cpp::util::TimePoint_Checked date = *date_opt; - date += time; - date += d.second; + auto date_opt = ymd.to_time_point_local(); + if (!date_opt.has_value()) { + return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); + } + rdf4cpp::util::TimePoint_Checked date = *date_opt; + date += time; + date += d.second; - auto date_uc = rdf4cpp::util::from_checked(date); - if (!date_uc.has_value()) { - return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); - } + auto date_uc = rdf4cpp::util::from_checked(date); + if (!date_uc.has_value()) { + return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); + } + + auto const deconstructed = rdf4cpp::util::deconstruct_timepoint(*date_uc); + if (!deconstructed.has_value()) { + return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); + } - auto const deconstructed = rdf4cpp::util::deconstruct_timepoint(*date_uc); - if (!deconstructed.has_value()) { - return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); + return *date_uc; } - return *date_uc; -} - -inline rdf4cpp::util::ZonedTime_Checked apply_tz_checked(TimePoint tp, OptionalTimezone tz) { - return {tz.has_value() ? *tz : rdf4cpp::util::time_point_replacement_timezone, rdf4cpp::util::to_checked(tp)}; -} -inline rdf4cpp::util::ZonedTime_Checked apply_tz_checked(std::pair const &tp) { - return apply_tz_checked(tp.first, tp.second); -} - -inline nonstd::expected timepoint_sub(std::pair const &lhs, std::pair const &rhs) noexcept { - rdf4cpp::util::ZonedTime_Checked const this_tp = apply_tz_checked(lhs); - rdf4cpp::util::ZonedTime_Checked const other_tp = apply_tz_checked(rhs); - - auto d = this_tp.get_sys_time() - other_tp.get_sys_time(); - return util::optional_to_overflow(rdf4cpp::util::from_checked(std::chrono::duration_cast, std::chrono::nanoseconds::period>>(d))); -} - -static inline std::partial_ordering compare_time_points(const rdf4cpp::TimePoint& a, std::optional atz, - const rdf4cpp::TimePoint& b, std::optional btz) noexcept { - auto const a_sys = apply_tz_checked(a, atz).get_sys_time(); - auto const b_sys = apply_tz_checked(b, btz).get_sys_time(); - if (a_sys.time_since_epoch().count().is_invalid() || b_sys.time_since_epoch().count().is_invalid()) { - return std::partial_ordering::unordered; + inline rdf4cpp::util::ZonedTime_Checked apply_tz_checked(TimePoint tp, OptionalTimezone tz) { + return {tz.has_value() ? *tz : rdf4cpp::util::time_point_replacement_timezone, rdf4cpp::util::to_checked(tp)}; } - return a_sys <=> b_sys; -} -template -requires std::same_as> || std::same_as> -inline static std::partial_ordering compare_time_points(T const &a, std::optional atz, - T const &b, std::optional btz) noexcept { - if (!a.has_value() || !b.has_value()) { - return std::partial_ordering::unordered; - } - return compare_time_points(*a, atz, *b, btz); -} -template -constexpr T number_of_bits(T x) noexcept { - return x < 2 ? x : 1 + number_of_bits(x >> 1); -} -template - requires(sizeof(TimeType) <= 2) -struct __attribute__((__packed__)) InliningHelper { - uint16_t tz_offset; - TimeType time_value; - - static constexpr int tz_shift = rdf4cpp::Timezone::max_value().offset.count() + 1; - static_assert(number_of_bits(static_cast(rdf4cpp::Timezone::max_value().offset.count() + tz_shift)) == 11); - - static constexpr uint16_t encode_tz(rdf4cpp::OptionalTimezone tz) noexcept { - if (tz.has_value()) - return static_cast(tz->offset.count() + tz_shift); - else - return 0; + inline rdf4cpp::util::ZonedTime_Checked apply_tz_checked(std::pair const &tp) { + return apply_tz_checked(tp.first, tp.second); } - constexpr InliningHelper(TimeType t, rdf4cpp::OptionalTimezone tz) noexcept : tz_offset(encode_tz(tz)), time_value(t) { + inline nonstd::expected timepoint_sub(std::pair const &lhs, std::pair const &rhs) noexcept { + rdf4cpp::util::ZonedTime_Checked const this_tp = apply_tz_checked(lhs); + rdf4cpp::util::ZonedTime_Checked const other_tp = apply_tz_checked(rhs); + + auto d = this_tp.get_sys_time() - other_tp.get_sys_time(); + return util::optional_to_overflow(rdf4cpp::util::from_checked(std::chrono::duration_cast, std::chrono::nanoseconds::period>>(d))); } - [[nodiscard]] constexpr rdf4cpp::OptionalTimezone decode_tz() const noexcept { - if (tz_offset == 0) - return std::nullopt; - else - return rdf4cpp::Timezone{std::chrono::minutes{static_cast(tz_offset) - tz_shift}}; + inline static std::partial_ordering compare_time_points(rdf4cpp::TimePoint const &a, std::optional atz, + rdf4cpp::TimePoint const &b, std::optional btz) noexcept { + auto const a_sys = apply_tz_checked(a, atz).get_sys_time(); + auto const b_sys = apply_tz_checked(b, btz).get_sys_time(); + if (a_sys.time_since_epoch().count().is_invalid() || b_sys.time_since_epoch().count().is_invalid()) { + return std::partial_ordering::unordered; + } + return a_sys <=> b_sys; } -}; -struct __attribute__((__packed__)) InliningHelperPacked { - static constexpr std::size_t width = storage::identifier::LiteralID::width; - static constexpr std::size_t tv_width = width - 11; - - uint16_t tz_offset : 11; - uint32_t time_value : tv_width; - -private: - [[maybe_unused]] uint32_t padding : 64 - width = 0; // to make sure the rest of the int64 is 0 - -public: - static constexpr int tz_shift = rdf4cpp::Timezone::max_value().offset.count() + 1; - static_assert(number_of_bits(static_cast(rdf4cpp::Timezone::max_value().offset.count() + tz_shift)) == 11); - - static constexpr uint16_t encode_tz(rdf4cpp::OptionalTimezone tz) noexcept { - if (tz.has_value()) - return static_cast(tz->offset.count() + tz_shift); - else - return 0; + template + requires std::same_as> || std::same_as> + inline static std::partial_ordering compare_time_points(T const &a, std::optional atz, + T const &b, std::optional btz) noexcept { + if (!a.has_value() || !b.has_value()) { + return std::partial_ordering::unordered; + } + return compare_time_points(*a, atz, *b, btz); } - - constexpr InliningHelperPacked(uint32_t t, rdf4cpp::OptionalTimezone tz) noexcept : tz_offset(encode_tz(tz)), time_value(t) { + template + constexpr T number_of_bits(T x) noexcept { + return x < 2 ? x : 1 + number_of_bits(x >> 1); } + template + requires(sizeof(TimeType) <= 2) + struct __attribute__((__packed__)) InliningHelper { + uint16_t tz_offset; + TimeType time_value; + + static constexpr int tz_shift = rdf4cpp::Timezone::max_value().offset.count() + 1; + static_assert(number_of_bits(static_cast(rdf4cpp::Timezone::max_value().offset.count() + tz_shift)) == 11); + + static constexpr uint16_t encode_tz(rdf4cpp::OptionalTimezone tz) noexcept { + if (tz.has_value()) + return static_cast(tz->offset.count() + tz_shift); + else + return 0; + } - [[nodiscard]] rdf4cpp::OptionalTimezone decode_tz() const noexcept { - if (tz_offset == 0) - return std::nullopt; - else - return rdf4cpp::Timezone{std::chrono::minutes{static_cast(tz_offset) - tz_shift}}; - } -}; - -inline nonstd::expected normalize(YearMonthDay const &i) { - // normalize - // see https://en.cppreference.com/w/cpp/chrono/year_month_day/operator_days - auto t = (i + std::chrono::months{0}).to_time_point(); - if (!t.has_value()) { - return nonstd::make_unexpected(DynamicError::OverOrUnderFlow); + constexpr InliningHelper(TimeType t, rdf4cpp::OptionalTimezone tz) noexcept : tz_offset(encode_tz(tz)), time_value(t) { + } + + [[nodiscard]] constexpr rdf4cpp::OptionalTimezone decode_tz() const noexcept { + if (tz_offset == 0) + return std::nullopt; + else + return rdf4cpp::Timezone{std::chrono::minutes{static_cast(tz_offset) - tz_shift}}; + } + }; + struct __attribute__((__packed__)) InliningHelperPacked { + static constexpr std::size_t width = storage::identifier::LiteralID::width; + static constexpr std::size_t tv_width = width - 11; + + uint16_t tz_offset : 11; + uint32_t time_value : tv_width; + + private: + [[maybe_unused]] uint32_t padding : 64 - width = 0; // to make sure the rest of the int64 is 0 + + public: + static constexpr int tz_shift = rdf4cpp::Timezone::max_value().offset.count() + 1; + static_assert(number_of_bits(static_cast(rdf4cpp::Timezone::max_value().offset.count() + tz_shift)) == 11); + + static constexpr uint16_t encode_tz(rdf4cpp::OptionalTimezone tz) noexcept { + if (tz.has_value()) + return static_cast(tz->offset.count() + tz_shift); + else + return 0; + } + + constexpr InliningHelperPacked(uint32_t t, rdf4cpp::OptionalTimezone tz) noexcept : tz_offset(encode_tz(tz)), time_value(t) { + } + + [[nodiscard]] rdf4cpp::OptionalTimezone decode_tz() const noexcept { + if (tz_offset == 0) + return std::nullopt; + else + return rdf4cpp::Timezone{std::chrono::minutes{static_cast(tz_offset) - tz_shift}}; + } + }; + + inline nonstd::expected normalize(YearMonthDay const &i) { + // normalize + // see https://en.cppreference.com/w/cpp/chrono/year_month_day/operator_days + auto t = (i + std::chrono::months{0}).to_time_point(); + if (!t.has_value()) { + return nonstd::make_unexpected(DynamicError::OverOrUnderFlow); + } + return util::optional_to_overflow(YearMonthDay::from_time_point_checked(*t)); } - return util::optional_to_overflow(YearMonthDay::from_time_point_checked(*t)); -} - -template -consteval I number_of_digits(I num) { - if (num < 0) { - return 1 + number_of_digits(-num); - } else if (num < base) { - return 1; - } else { - return 1 + number_of_digits(num / base); + + template + consteval I number_of_digits(I num) { + if (num < 0) { + return 1 + number_of_digits(-num); + } else if (num < base) { + return 1; + } else { + return 1 + number_of_digits(num / base); + } } -} -static_assert(number_of_digits(0)==1); -static_assert(number_of_digits(9)==1); -static_assert(number_of_digits(-1)==2); -static_assert(number_of_digits(10)==2); -static_assert(number_of_digits(std::numeric_limits::max())==std::numeric_limits::digits10+1); -namespace chrono_max_canonical_string_chars { - inline constexpr size_t year = std::numeric_limits::digits10 + 2; // +1 for the not fully representable digit, +1 for - sign - //std::chrono::day is in [0, 255] - inline constexpr size_t day = number_of_digits(255); - //std::chrono::month is in [0, 255] - inline constexpr size_t month = number_of_digits(255); - //[0, 59.999...] (includes nanoseconds) - inline constexpr size_t seconds = 2 + 1 + 9; - //[0,59] - inline constexpr size_t minutes = 2; - //[0,24] (used if more than 24 hours get added to days) - inline constexpr size_t hours = 2; - //used if no days are serialized - inline constexpr size_t hours_unbound = number_of_digits(std::chrono::floor(std::chrono::milliseconds::max()).count()); - static_assert(sizeof(std::chrono::hours::rep) <= sizeof(int64_t)); - static_assert(sizeof(std::chrono::milliseconds ::rep) <= sizeof(int64_t)); - //duration - static constexpr size_t years = number_of_digits(std::chrono::floor(std::chrono::months::max()).count()); - static_assert(sizeof(std::chrono::years::rep) <= sizeof(int64_t)); - static_assert(sizeof(std::chrono::months::rep) <= sizeof(int64_t)); - //duration - inline constexpr size_t months = 2; - //duration - inline constexpr size_t days = number_of_digits(std::chrono::floor(std::chrono::milliseconds::max()).count());; - static_assert(sizeof(std::chrono::days::rep) <= sizeof(int64_t)); -}; + static_assert(number_of_digits(0) == 1); + static_assert(number_of_digits(9) == 1); + static_assert(number_of_digits(-1) == 2); + static_assert(number_of_digits(10) == 2); + static_assert(number_of_digits(std::numeric_limits::max()) == std::numeric_limits::digits10 + 1); + namespace chrono_max_canonical_string_chars { + inline constexpr size_t year = std::numeric_limits::digits10 + 2; // +1 for the not fully representable digit, +1 for - sign + //std::chrono::day is in [0, 255] + inline constexpr size_t day = number_of_digits(255); + //std::chrono::month is in [0, 255] + inline constexpr size_t month = number_of_digits(255); + //[0, 59.999...] (includes nanoseconds) + inline constexpr size_t seconds = 2 + 1 + 9; + //[0,59] + inline constexpr size_t minutes = 2; + //[0,24] (used if more than 24 hours get added to days) + inline constexpr size_t hours = 2; + //used if no days are serialized + inline constexpr size_t hours_unbound = number_of_digits(std::chrono::floor(std::chrono::milliseconds::max()).count()); + static_assert(sizeof(std::chrono::hours::rep) <= sizeof(int64_t)); + static_assert(sizeof(std::chrono::milliseconds ::rep) <= sizeof(int64_t)); + //duration + static constexpr size_t years = number_of_digits(std::chrono::floor(std::chrono::months::max()).count()); + static_assert(sizeof(std::chrono::years::rep) <= sizeof(int64_t)); + static_assert(sizeof(std::chrono::months::rep) <= sizeof(int64_t)); + //duration + inline constexpr size_t months = 2; + //duration + inline constexpr size_t days = number_of_digits(std::chrono::floor(std::chrono::milliseconds::max()).count()); + ; + static_assert(sizeof(std::chrono::days::rep) <= sizeof(int64_t)); + }; // namespace chrono_max_canonical_string_chars } // namespace rdf4cpp::datatypes::registry::util diff --git a/src/rdf4cpp/util/Int128.hpp b/src/rdf4cpp/util/Int128.hpp index af254d3d3..f67b98e94 100644 --- a/src/rdf4cpp/util/Int128.hpp +++ b/src/rdf4cpp/util/Int128.hpp @@ -305,12 +305,27 @@ namespace rdf4cpp::util { struct MakeUnsigned>> { using t = boost::multiprecision::number>; }; - // TODO check double->int64 conversion template requires (IntegralExt || std::floating_point) && (IntegralExt || std::floating_point) static constexpr bool cast_checked(const From &f, To &result) noexcept { if constexpr (m == OverflowMode::Checked) { - if constexpr (std::numeric_limits::is_signed == std::numeric_limits::is_signed) { + if constexpr (std::floating_point && !std::floating_point) { + // from https://stackoverflow.com/questions/25857843/how-do-i-convert-an-arbitrary-double-to-an-integer-while-avoiding-undefined-beha + if (std::isnan(f) || std::isinf(f)) { + return true; + } + if constexpr (!std::numeric_limits::is_signed) { + if (f < 0) { + return true; + } + } + int exp; + std::frexp(f, &exp); + if (exp > std::numeric_limits::digits && f != std::numeric_limits::min()) { + return true; + } + } + else if constexpr (std::numeric_limits::is_signed == std::numeric_limits::is_signed) { if (std::numeric_limits::min() > f || f > std::numeric_limits::max()) { return true; } @@ -359,14 +374,13 @@ namespace rdf4cpp::util { result = static_cast(f); return false; } - template + template + requires IntegralExt || std::floating_point static constexpr bool cast_checked(From const &f, cpp_int_checked &result) noexcept { if constexpr (m == OverflowMode::Checked) { try { result = static_cast>(f); - } catch (std::overflow_error const &) { - return true; - } catch (std::range_error const &) { + } catch (std::runtime_error const &) { return true; } return false; @@ -374,14 +388,13 @@ namespace rdf4cpp::util { result = static_cast>(static_cast>(f)); return false; } - template + template + requires IntegralExt || std::floating_point static constexpr bool cast_checked(From const &f, cpp_int_unchecked &result) noexcept { if constexpr (m == OverflowMode::Checked) { try { result = static_cast>(static_cast>(f)); - } catch (std::overflow_error const &) { - return true; - } catch (std::range_error const &) { + } catch (std::runtime_error const &) { return true; } return false; diff --git a/tests/util/tests_BigDecimal.cpp b/tests/util/tests_BigDecimal.cpp index c7f921a06..8b8b44db2 100644 --- a/tests/util/tests_BigDecimal.cpp +++ b/tests/util/tests_BigDecimal.cpp @@ -165,6 +165,18 @@ TEST_CASE_TEMPLATE("checked casting", T, uint32_t, uint64_t, unsigned __int128, CHECK(cast_checked(std::numeric_limits::max(), i8) == true); CHECK(cast_checked(std::numeric_limits::max(), t) == false); CHECK(t == std::numeric_limits::max()); + + CHECK(cast_checked(0.0, t) == false); + CHECK(t == 0); + CHECK(cast_checked(static_cast(std::numeric_limits::max()) / 2, t) == false); + CHECK(t < std::numeric_limits::max()); + if constexpr (!rdf4cpp::util::detail::BoostNumber) { + CHECK(cast_checked(static_cast(std::numeric_limits::min()), t) == false); + CHECK(t == std::numeric_limits::min()); + } + CHECK(cast_checked(std::numeric_limits::quiet_NaN(), t) == true); + CHECK(cast_checked(std::numeric_limits::infinity(), t) == true); + CHECK(cast_checked(static_cast(std::numeric_limits::max()) * 2, t) == true); } TEST_CASE("int128 to_chars") { From 51129ee412b402efe3cf467544067b09db4d097a Mon Sep 17 00:00:00 2001 From: mcb Date: Mon, 7 Jul 2025 12:50:02 +0200 Subject: [PATCH 3/9] fix merge --- src/rdf4cpp/Timezone.hpp | 16 +-- .../datatypes/registry/util/DateTimeUtils.hpp | 2 +- src/rdf4cpp/util/Int128.hpp | 115 ++++++++---------- tests/datatype/tests_time_types.cpp | 2 +- 4 files changed, 64 insertions(+), 71 deletions(-) diff --git a/src/rdf4cpp/Timezone.hpp b/src/rdf4cpp/Timezone.hpp index 43970f81a..54724ae6b 100644 --- a/src/rdf4cpp/Timezone.hpp +++ b/src/rdf4cpp/Timezone.hpp @@ -429,25 +429,25 @@ namespace rdf4cpp { return day_; } - [[nodiscard]] constexpr std::optional> to_time_point() const { + [[nodiscard]] constexpr std::optional> to_time_point() const { static_assert(std::numeric_limits::digits >= 18, "This algorithm has not been ported to a 16 bit unsigned integer"); static_assert(std::numeric_limits::digits >= 20, "This algorithm has not been ported to a 16 bit signed integer"); static_assert(std::numeric_limits::digits >= 20, "This algorithm has not been ported to a 16 bit signed integer"); - util::CheckedIntegral y = static_cast(year_); + util::CheckedIntegral y = static_cast(year_); auto m = static_cast(month_); auto d = static_cast(day_); y -= m <= 2; - util::CheckedIntegral const era = (y >= 0 ? y : y - 399) / 400; + util::CheckedIntegral const era = (y >= 0 ? y : y - 399) / 400; auto const yoe = y - era * 400; // [0, 399] auto const doy = (153 * (m > 2 ? m - 3 : m + 9) + 2) / 5 + d - 1; // [0, 365] auto const doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096] // note that the epoch of system_clock is specified as 00:00:00 Coordinated Universal Time (UTC), Thursday, 1 January 1970 - time_point> const r{typename time_point>::duration{era * 146097 + static_cast>(doe) - 719468}}; + time_point> const r{typename time_point>::duration{era * 146097 + static_cast>(doe) - 719468}}; return util::from_checked(r); } - [[nodiscard]] constexpr std::optional> to_time_point_local() const { + [[nodiscard]] constexpr std::optional> to_time_point_local() const { auto const v = to_time_point(); - return time_point_local{v.value().time_since_epoch()}; + return time_point_local{v.value().time_since_epoch()}; } [[nodiscard]] constexpr bool ok() const noexcept { @@ -497,14 +497,14 @@ namespace rdf4cpp { } }; - using DurationNano = std::chrono::duration; + using DurationNano = std::chrono::duration; using TimePoint = std::chrono::time_point; // system_clock does not use leap seconds, as required by rdf (xsd) using TimePointSys = std::chrono::time_point; using ZonedTime = std::chrono::zoned_time; namespace util { - using DurationNano_Checked = std::chrono::duration, std::chrono::nanoseconds::period>; + using DurationNano_Checked = std::chrono::duration, std::chrono::nanoseconds::period>; using TimePoint_Checked = std::chrono::time_point; using TimePointSys_Checked = std::chrono::time_point; using ZonedTime_Checked = std::chrono::zoned_time; diff --git a/src/rdf4cpp/datatypes/registry/util/DateTimeUtils.hpp b/src/rdf4cpp/datatypes/registry/util/DateTimeUtils.hpp index c2cd8dc38..ffcda0c6a 100644 --- a/src/rdf4cpp/datatypes/registry/util/DateTimeUtils.hpp +++ b/src/rdf4cpp/datatypes/registry/util/DateTimeUtils.hpp @@ -108,7 +108,7 @@ namespace rdf4cpp::datatypes::registry::util { } auto [ymd, time] = *dec_tp; - using Checked = rdf4cpp::util::CheckedIntegral; + using Checked = rdf4cpp::util::CheckedIntegral; Checked checked_m = static_cast(ymd.month()); checked_m += Checked{static_cast(ymd.year())} * 12; diff --git a/src/rdf4cpp/util/Int128.hpp b/src/rdf4cpp/util/Int128.hpp index 36989040a..eefe49536 100644 --- a/src/rdf4cpp/util/Int128.hpp +++ b/src/rdf4cpp/util/Int128.hpp @@ -231,52 +231,47 @@ namespace rdf4cpp { using t = boost::multiprecision::number>; }; template - requires (IntegralExt || std::floating_point) && (IntegralExt || std::floating_point) + requires(IntegralExt || std::floating_point) && (IntegralExt || std::floating_point) static constexpr bool cast_checked(From const &f, To &result) noexcept { if constexpr (m == OverflowMode::Checked) { if constexpr (std::floating_point && !std::floating_point) { - // from https://stackoverflow.com/questions/25857843/how-do-i-convert-an-arbitrary-double-to-an-integer-while-avoiding-undefined-beha - if (std::isnan(f) || std::isinf(f)) { - return true; - } - if constexpr (!std::numeric_limits::is_signed) { - if (f < 0) { + // from https://stackoverflow.com/questions/25857843/how-do-i-convert-an-arbitrary-double-to-an-integer-while-avoiding-undefined-beha + if (std::isnan(f) || std::isinf(f)) { + return true; + } + if constexpr (!std::numeric_limits::is_signed) { + if (f < 0) { + return true; + } + } + int exp; + std::frexp(f, &exp); + if (exp > std::numeric_limits::digits && f != std::numeric_limits::min()) { + return true; + } + } else if constexpr (std::numeric_limits::is_signed == std::numeric_limits::is_signed) { + if (std::numeric_limits::min() > f || f > std::numeric_limits::max()) { + return true; + } + } else if constexpr (std::numeric_limits::is_signed) { + if (f < 0 || static_cast::t>(f) > std::numeric_limits::max()) { + return true; + } + } else { + if (f > std::numeric_limits::max()) { return true; } - } - int exp; - std::frexp(f, &exp); - if (exp > std::numeric_limits::digits && f != std::numeric_limits::min()) { - return true; - } - } - else if constexpr (std::numeric_limits::is_signed == std::numeric_limits::is_signed) { - if (std::numeric_limits::min() > f || f > std::numeric_limits::max()) { - return true; - } - } - else if constexpr (std::numeric_limits::is_signed) { - if (f < 0 || static_cast::t>(f) > std::numeric_limits::max()) { - return true; - } - } - else { - if (f > std::numeric_limits::max()) { - return true; } } + result = static_cast(f); + return false; } - result = static_cast(f); - return false; - } - template - static constexpr bool cast_checked(cpp_int_checked const &f, To &result) noexcept { + template + static constexpr bool cast_checked(cpp_int_checked const &f, To &result) noexcept { if constexpr (m == OverflowMode::Checked) { try { result = static_cast(f); - } catch (std::overflow_error const &) { - return true; - } catch (std::range_error const &) { + } catch (std::runtime_error const &) { return true; } return false; @@ -289,42 +284,40 @@ namespace rdf4cpp { if constexpr (m == OverflowMode::Checked) { try { result = static_cast>(f); - } catch (std::overflow_error const &) { - return true; - } catch (std::range_error const &) { + } catch (std::runtime_error const &) { return true; } return false; } result = static_cast>(static_cast>(f)); - result = static_cast(f); - return false; - } - template - requires IntegralExt || std::floating_point - static constexpr bool cast_checked(From const &f, cpp_int_checked &result) noexcept { - if constexpr (m == OverflowMode::Checked) { - try { - result = static_cast>(f); - } catch (std::runtime_error const &) { - return true; - } return false; } - result = static_cast>(static_cast>(f)); - return false; - } - template - requires IntegralExt || std::floating_point - static constexpr bool cast_checked(From const &f, cpp_int_unchecked &result) noexcept { - if constexpr (m == OverflowMode::Checked) { - try { - result = static_cast>(static_cast>(f)); - } catch (std::runtime_error const &) { - return true; + template + requires IntegralExt || std::floating_point + static constexpr bool cast_checked(From const &f, cpp_int_checked &result) noexcept { + if constexpr (m == OverflowMode::Checked) { + try { + result = static_cast>(f); + } catch (std::runtime_error const &) { + return true; + } + return false; } + result = static_cast>(static_cast>(f)); return false; } + template + requires IntegralExt || std::floating_point + static constexpr bool cast_checked(From const &f, cpp_int_unchecked &result) noexcept { + if constexpr (m == OverflowMode::Checked) { + try { + result = static_cast>(static_cast>(f)); + } catch (std::runtime_error const &) { + return true; + } + return false; + } + } } // namespace detail template diff --git a/tests/datatype/tests_time_types.cpp b/tests/datatype/tests_time_types.cpp index f5f4a6dda..1b05ac2ce 100644 --- a/tests/datatype/tests_time_types.cpp +++ b/tests/datatype/tests_time_types.cpp @@ -176,7 +176,7 @@ TEST_CASE("precision") { CHECK(ys > std::chrono::years{10000}); CHECK(ys > std::chrono::years{static_cast(std::chrono::year::max())}); - using I = rdf4cpp::util::Int128; + using I = rdf4cpp::Int128; using CI = rdf4cpp::util::CheckedIntegral; CI end_of_universe = rdf4cpp::datatypes::registry::util::from_chars("100000000000000"); CHECK(end_of_universe < std::numeric_limits::max()); From 54e66f4f7356b76dbd89af6f69754bbb922206e5 Mon Sep 17 00:00:00 2001 From: mcb Date: Mon, 7 Jul 2025 15:32:31 +0200 Subject: [PATCH 4/9] fix formatting --- .../datatypes/registry/util/DateTimeUtils.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/rdf4cpp/datatypes/registry/util/DateTimeUtils.hpp b/src/rdf4cpp/datatypes/registry/util/DateTimeUtils.hpp index ffcda0c6a..c3127c7bb 100644 --- a/src/rdf4cpp/datatypes/registry/util/DateTimeUtils.hpp +++ b/src/rdf4cpp/datatypes/registry/util/DateTimeUtils.hpp @@ -96,11 +96,11 @@ namespace rdf4cpp::datatypes::registry::util { } /** - * returns nullopt on overflow - * @param tp - * @param d - * @return - */ + * returns nullopt on overflow + * @param tp + * @param d + * @return + */ inline nonstd::expected add_duration_to_date_time(TimePoint const &tp, std::pair d) noexcept { auto dec_tp = rdf4cpp::util::deconstruct_timepoint(tp); if (!dec_tp.has_value()) { @@ -278,6 +278,7 @@ namespace rdf4cpp::datatypes::registry::util { static_assert(number_of_digits(10) == 2); static_assert(number_of_digits(std::numeric_limits::max()) == std::numeric_limits::digits10 + 1); namespace chrono_max_canonical_string_chars { + // TODO check limits inline constexpr size_t year = std::numeric_limits::digits10 + 2; // +1 for the not fully representable digit, +1 for - sign //std::chrono::day is in [0, 255] inline constexpr size_t day = number_of_digits(255); @@ -301,7 +302,6 @@ namespace rdf4cpp::datatypes::registry::util { inline constexpr size_t months = 2; //duration inline constexpr size_t days = number_of_digits(std::chrono::floor(std::chrono::milliseconds::max()).count()); - ; static_assert(sizeof(std::chrono::days::rep) <= sizeof(int64_t)); }; // namespace chrono_max_canonical_string_chars From 3136f7d2f9418b5783d1d690d560e78ac99cd15f Mon Sep 17 00:00:00 2001 From: mcb Date: Fri, 11 Jul 2025 13:49:17 +0200 Subject: [PATCH 5/9] checked ymd, limit checks --- src/rdf4cpp/Timezone.hpp | 144 ++++++++++++++---- .../datatypes/registry/util/DateTimeUtils.hpp | 22 +-- src/rdf4cpp/datatypes/xsd/time/Date.cpp | 4 +- src/rdf4cpp/datatypes/xsd/time/DateTime.cpp | 10 +- .../datatypes/xsd/time/DateTimeStamp.cpp | 10 +- src/rdf4cpp/datatypes/xsd/time/Day.cpp | 2 +- src/rdf4cpp/datatypes/xsd/time/Duration.cpp | 8 +- src/rdf4cpp/datatypes/xsd/time/Month.cpp | 2 +- src/rdf4cpp/datatypes/xsd/time/MonthDay.cpp | 2 +- src/rdf4cpp/datatypes/xsd/time/Time.cpp | 3 +- src/rdf4cpp/datatypes/xsd/time/Year.cpp | 2 +- src/rdf4cpp/datatypes/xsd/time/YearMonth.cpp | 2 +- tests/datatype/tests_time_types.cpp | 31 ++-- 13 files changed, 153 insertions(+), 89 deletions(-) diff --git a/src/rdf4cpp/Timezone.hpp b/src/rdf4cpp/Timezone.hpp index 54724ae6b..1d84d3e99 100644 --- a/src/rdf4cpp/Timezone.hpp +++ b/src/rdf4cpp/Timezone.hpp @@ -218,6 +218,13 @@ namespace rdf4cpp { friend constexpr Year operator+(std::chrono::years d, Year const &y) noexcept { return Year{y.value_ + d.count()}; } + [[nodiscard]] constexpr std::optional add_checked(std::chrono::years d) const noexcept { + int64_t r; + if (util::detail::add_checked(value_, d.count(), r)) { + return std::nullopt; + } + return Year{r}; + } constexpr Year operator+=(std::chrono::years d) noexcept { *this = *this + d; @@ -230,6 +237,20 @@ namespace rdf4cpp { friend constexpr std::chrono::years operator-(Year const &a, Year const &b) noexcept { return std::chrono::years{a.value_ - b.value_}; } + [[nodiscard]] constexpr std::optional sub_checked(std::chrono::years d) const noexcept { + int64_t r; + if (util::detail::sub_checked(value_, d.count(), r)) { + return std::nullopt; + } + return Year{r}; + } + [[nodiscard]] constexpr std::optional sub_checked(Year o) const noexcept { + int64_t r; + if (util::detail::sub_checked(this->value_, o.value_, r)) { + return std::nullopt; + } + return std::chrono::years{r}; + } constexpr Year operator-=(std::chrono::years d) noexcept { *this = *this - d; @@ -269,15 +290,21 @@ namespace rdf4cpp { Year year_ = Year{0}; Month month_ = Month{1}; - static constexpr YearMonth create_normalized(int64_t y, int64_t mo) noexcept { - --mo; + // returns nullopt iff overflow + static constexpr std::optional create_normalized(util::CheckedIntegral y, util::CheckedIntegral mo) noexcept { + mo -= 1; y += mo / 12; mo %= 12; if (mo < 0) { // fix result of % being in [-11,11] - --y; + y -= 1; mo += 12; } - return YearMonth{Year{y}, std::chrono::month{static_cast(mo + 1)}}; + mo += 1; + auto um = mo.checked_cast(); + if (y.is_invalid() || um.is_invalid()) { + return std::nullopt; + } + return YearMonth{Year{y.get_value()}, std::chrono::month{um.get_value()}}; } public: @@ -306,18 +333,27 @@ namespace rdf4cpp { friend constexpr YearMonth operator+(std::chrono::years d, YearMonth const &ym) noexcept { return YearMonth{ym.year_ + d, ym.month_}; } + [[nodiscard]] constexpr std::optional add_checked(std::chrono::years s) const noexcept { + auto y = year_.add_checked(s); + if (!y.has_value()) { + return std::nullopt; + } + return YearMonth{*y, month_}; + } constexpr YearMonth &operator+=(std::chrono::years d) noexcept { *this = *this + d; return *this; } - - // TODO overflow - friend constexpr YearMonth operator+(YearMonth const &ym, std::chrono::months d) noexcept { - return create_normalized(static_cast(ym.year_), static_cast(ym.month_) + d.count()); + + friend constexpr YearMonth operator+(YearMonth const &ym, std::chrono::months d) { + return create_normalized(static_cast(ym.year_), static_cast(ym.month_) + d.count()).value(); + } + friend constexpr YearMonth operator+(std::chrono::months d, YearMonth const &ym) { + return create_normalized(static_cast(ym.year_), static_cast(ym.month_) + d.count()).value(); } - friend constexpr YearMonth operator+(std::chrono::months d, YearMonth const &ym) noexcept { - return create_normalized(static_cast(ym.year_), static_cast(ym.month_) + d.count()); + [[nodiscard]] constexpr std::optional add_checked(std::chrono::months d) const noexcept { + return create_normalized(static_cast(year_), util::CheckedIntegral(static_cast(month_)) + d.count()); } constexpr YearMonth &operator+=(std::chrono::months d) noexcept { @@ -328,13 +364,35 @@ namespace rdf4cpp { friend constexpr YearMonth operator-(YearMonth const &ym, std::chrono::years d) noexcept { return {ym.year_ - d, ym.month_}; } - friend constexpr YearMonth operator-(YearMonth const &ym, std::chrono::months d) noexcept { - return create_normalized(static_cast(ym.year_), static_cast(ym.month_) - d.count()); + [[nodiscard]] constexpr std::optional sub_checked(std::chrono::years s) const noexcept { + auto y = year_.sub_checked(s); + if (!y.has_value()) { + return std::nullopt; + } + return YearMonth{*y, month_}; + } + friend constexpr YearMonth operator-(YearMonth const &ym, std::chrono::months d) { + return create_normalized(static_cast(ym.year_), static_cast(ym.month_) - d.count()).value(); + } + [[nodiscard]] constexpr std::optional sub_checked(std::chrono::months d) const noexcept { + return create_normalized(static_cast(year_), util::CheckedIntegral(static_cast(month_)) - d.count()); } friend constexpr std::chrono::months operator-(YearMonth const &a, YearMonth const &b) noexcept { return (a.year_ - b.year_) + (a.month_ - b.month_); } + [[nodiscard]] constexpr std::optional sub_checked(YearMonth const &o) const noexcept { + auto y = this->year_.sub_checked(o.year_); + if (!y.has_value()) { + return std::nullopt; + } + int64_t m; + if (util::detail::sub_checked(static_cast(this->month_), static_cast(o.month_), m)) { + return std::nullopt; + } + auto r = util::to_checked(*y) + std::chrono::months(m); + return util::from_checked(r); + } constexpr YearMonth &operator-=(std::chrono::years d) noexcept { *this = *this - d; @@ -429,25 +487,26 @@ namespace rdf4cpp { return day_; } - [[nodiscard]] constexpr std::optional> to_time_point() const { + // with year as int64 and timepoint as int128, guaranteed to not overflow + [[nodiscard]] constexpr time_point to_time_point() const { static_assert(std::numeric_limits::digits >= 18, "This algorithm has not been ported to a 16 bit unsigned integer"); static_assert(std::numeric_limits::digits >= 20, "This algorithm has not been ported to a 16 bit signed integer"); - static_assert(std::numeric_limits::digits >= 20, "This algorithm has not been ported to a 16 bit signed integer"); - util::CheckedIntegral y = static_cast(year_); + static_assert(std::numeric_limits::digits >= 20, "This algorithm has not been ported to a 16 bit signed integer"); + Int128 y = static_cast(year_); auto m = static_cast(month_); auto d = static_cast(day_); y -= m <= 2; - util::CheckedIntegral const era = (y >= 0 ? y : y - 399) / 400; + Int128 const era = (y >= 0 ? y : y - 399) / 400; auto const yoe = y - era * 400; // [0, 399] auto const doy = (153 * (m > 2 ? m - 3 : m + 9) + 2) / 5 + d - 1; // [0, 365] auto const doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096] // note that the epoch of system_clock is specified as 00:00:00 Coordinated Universal Time (UTC), Thursday, 1 January 1970 - time_point> const r{typename time_point>::duration{era * 146097 + static_cast>(doe) - 719468}}; - return util::from_checked(r); + time_point const r{typename time_point::duration{era * 146097 + doe - 719468}}; + return r; } - [[nodiscard]] constexpr std::optional> to_time_point_local() const { + [[nodiscard]] constexpr time_point_local to_time_point_local() const { auto const v = to_time_point(); - return time_point_local{v.value().time_since_epoch()}; + return time_point_local{v.time_since_epoch()}; } [[nodiscard]] constexpr bool ok() const noexcept { @@ -462,18 +521,32 @@ namespace rdf4cpp { friend constexpr YearMonthDay operator+(std::chrono::years d, YearMonthDay const &ym) noexcept { return {ym.year_ + d, ym.month_, ym.day_}; } + [[nodiscard]] constexpr std::optional add_checked(std::chrono::years d) const noexcept { + auto y = year_.add_checked(d); + if (!y.has_value()) { + return std::nullopt; + } + return YearMonthDay{*y, month_, day_}; + } constexpr YearMonthDay &operator+=(std::chrono::years d) noexcept { *this = *this + d; return *this; } - friend constexpr YearMonthDay operator+(YearMonthDay const &d, std::chrono::months m) noexcept { + friend constexpr YearMonthDay operator+(YearMonthDay const &d, std::chrono::months m) { return YearMonthDay{YearMonth{d.year_, d.month_} + m, d.day_}; } - friend constexpr YearMonthDay operator+(std::chrono::months m, YearMonthDay const &d) noexcept { + friend constexpr YearMonthDay operator+(std::chrono::months m, YearMonthDay const &d) { return YearMonthDay{YearMonth{d.year_, d.month_} + m, d.day_}; } + [[nodiscard]] constexpr std::optional add_checked(std::chrono::months d) const noexcept { + auto ym = YearMonth{year_, month_}.add_checked(d); + if (!ym.has_value()) { + return std::nullopt; + } + return YearMonthDay{*ym, day_}; + } constexpr YearMonthDay &operator+=(std::chrono::months d) noexcept { *this = *this + d; @@ -483,9 +556,23 @@ namespace rdf4cpp { friend constexpr YearMonthDay operator-(YearMonthDay const &ym, std::chrono::years d) noexcept { return {ym.year_ - d, ym.month_, ym.day_}; } - friend constexpr YearMonthDay operator-(YearMonthDay const &d, std::chrono::months m) noexcept { + friend constexpr YearMonthDay operator-(YearMonthDay const &d, std::chrono::months m) { return YearMonthDay{YearMonth{d.year_, d.month_} - m, d.day_}; } + [[nodiscard]] constexpr std::optional sub_checked(std::chrono::years d) const noexcept { + auto y = year_.sub_checked(d); + if (!y.has_value()) { + return std::nullopt; + } + return YearMonthDay{*y, month_, day_}; + } + [[nodiscard]] constexpr std::optional sub_checked(std::chrono::months d) const noexcept { + auto ym = YearMonth{year_, month_}.sub_checked(d); + if (!ym.has_value()) { + return std::nullopt; + } + return YearMonthDay{*ym, day_}; + } constexpr YearMonthDay &operator-=(std::chrono::years d) noexcept { *this = *this - d; @@ -515,15 +602,12 @@ namespace rdf4cpp { // implementation defined, not from standard inline constexpr Timezone time_point_replacement_timezone{std::chrono::minutes{0}}; - // TODO check return values - constexpr std::optional construct_timepoint(YearMonthDay const &date, DurationNano const &time_of_day) noexcept { + // with year as int64 and timepoint as int128, guaranteed to not overflow + constexpr TimePoint construct_timepoint(YearMonthDay const &date, DurationNano const &time_of_day) noexcept { auto sd = date.to_time_point_local(); - if (!sd.has_value()) { - return std::nullopt; - } - auto ms = static_cast(to_checked(*sd)); + auto ms = static_cast(sd); ms += time_of_day; - return from_checked(ms); + return ms; } // TODO check return values diff --git a/src/rdf4cpp/datatypes/registry/util/DateTimeUtils.hpp b/src/rdf4cpp/datatypes/registry/util/DateTimeUtils.hpp index c3127c7bb..c1f48b8bc 100644 --- a/src/rdf4cpp/datatypes/registry/util/DateTimeUtils.hpp +++ b/src/rdf4cpp/datatypes/registry/util/DateTimeUtils.hpp @@ -95,19 +95,13 @@ namespace rdf4cpp::datatypes::registry::util { } } - /** - * returns nullopt on overflow - * @param tp - * @param d - * @return - */ inline nonstd::expected add_duration_to_date_time(TimePoint const &tp, std::pair d) noexcept { auto dec_tp = rdf4cpp::util::deconstruct_timepoint(tp); if (!dec_tp.has_value()) { return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); } auto [ymd, time] = *dec_tp; - + // TODO use ymd.checked_add using Checked = rdf4cpp::util::CheckedIntegral; Checked checked_m = static_cast(ymd.month()); @@ -136,11 +130,7 @@ namespace rdf4cpp::datatypes::registry::util { ymd = YearMonthDay{ymd.year(), ymd.month(), std::chrono::last}; } - auto date_opt = ymd.to_time_point_local(); - if (!date_opt.has_value()) { - return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); - } - rdf4cpp::util::TimePoint_Checked date = *date_opt; + rdf4cpp::util::TimePoint_Checked date = ymd.to_time_point_local(); date += time; date += d.second; @@ -255,11 +245,11 @@ namespace rdf4cpp::datatypes::registry::util { inline nonstd::expected normalize(YearMonthDay const &i) { // normalize // see https://en.cppreference.com/w/cpp/chrono/year_month_day/operator_days - auto t = (i + std::chrono::months{0}).to_time_point(); - if (!t.has_value()) { - return nonstd::make_unexpected(DynamicError::OverOrUnderFlow); + auto ym = i.add_checked(std::chrono::months{0}); + if (!ym.has_value()) { + return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); } - return util::optional_to_overflow(YearMonthDay::from_time_point_checked(*t)); + return util::optional_to_overflow(YearMonthDay::from_time_point_checked(ym->to_time_point())); } template diff --git a/src/rdf4cpp/datatypes/xsd/time/Date.cpp b/src/rdf4cpp/datatypes/xsd/time/Date.cpp index 3946ff3de..a102b701b 100644 --- a/src/rdf4cpp/datatypes/xsd/time/Date.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/Date.cpp @@ -54,7 +54,7 @@ std::optional capabilities::Inlineable if (value.second.has_value()) { return std::nullopt; } - auto i = value.first.to_time_point()->time_since_epoch().count(); + auto i = value.first.to_time_point().time_since_epoch().count(); int64_t i64; if (rdf4cpp::util::detail::cast_checked(i, i64)) [[unlikely]] { return std::nullopt; @@ -69,7 +69,7 @@ capabilities::Inlineable::cpp_type capabilities::Inlineable: } rdf4cpp::TimePoint date_to_tp(YearMonthDay const &d) noexcept { - return *rdf4cpp::util::construct_timepoint(d, rdf4cpp::util::time_point_replacement_time_of_day); + return rdf4cpp::util::construct_timepoint(d, rdf4cpp::util::time_point_replacement_time_of_day); } template<> diff --git a/src/rdf4cpp/datatypes/xsd/time/DateTime.cpp b/src/rdf4cpp/datatypes/xsd/time/DateTime.cpp index a530448ab..5bf4ea8e7 100644 --- a/src/rdf4cpp/datatypes/xsd/time/DateTime.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/DateTime.cpp @@ -41,10 +41,7 @@ capabilities::Default::cpp_type capabilities::Default::cpp_type capabilities::Default diff --git a/src/rdf4cpp/datatypes/xsd/time/DateTimeStamp.cpp b/src/rdf4cpp/datatypes/xsd/time/DateTimeStamp.cpp index 5b563c1f3..8f39291f8 100644 --- a/src/rdf4cpp/datatypes/xsd/time/DateTimeStamp.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/DateTimeStamp.cpp @@ -48,10 +48,7 @@ capabilities::Default::cpp_type capabilities::Default::cpp_type capabilities::Default diff --git a/src/rdf4cpp/datatypes/xsd/time/Day.cpp b/src/rdf4cpp/datatypes/xsd/time/Day.cpp index 56b0e0509..fe8f7e799 100644 --- a/src/rdf4cpp/datatypes/xsd/time/Day.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/Day.cpp @@ -38,7 +38,7 @@ bool capabilities::Default::serialize_canonical_string(cpp_type const template<> std::partial_ordering capabilities::Comparable::compare(cpp_type const &lhs, cpp_type const &rhs) noexcept { - auto day_to_tp = [](std::chrono::day d) noexcept -> std::optional { + auto day_to_tp = [](std::chrono::day d) noexcept -> TimePoint { return rdf4cpp::util::construct_timepoint(YearMonthDay{rdf4cpp::util::time_point_replacement_date.year(), rdf4cpp::util::time_point_replacement_date.month(), d}, rdf4cpp::util::time_point_replacement_time_of_day); }; return registry::util::compare_time_points(day_to_tp(lhs.first), lhs.second, day_to_tp(rhs.first), rhs.second); diff --git a/src/rdf4cpp/datatypes/xsd/time/Duration.cpp b/src/rdf4cpp/datatypes/xsd/time/Duration.cpp index 49ac8dd85..b486761d9 100644 --- a/src/rdf4cpp/datatypes/xsd/time/Duration.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/Duration.cpp @@ -189,10 +189,10 @@ capabilities::Inlineable::cpp_type capabilities::Inlineable std::partial_ordering capabilities::Comparable::compare(cpp_type const &lhs, cpp_type const &rhs) noexcept { static constexpr std::array to_compare{ - *rdf4cpp::util::construct_timepoint(YearMonthDay{Year{1696}, std::chrono::month{9}, std::chrono::day{1}}, std::chrono::milliseconds{0}), - *rdf4cpp::util::construct_timepoint(YearMonthDay{Year{1697}, std::chrono::month{2}, std::chrono::day{1}}, std::chrono::milliseconds{0}), - *rdf4cpp::util::construct_timepoint(YearMonthDay{Year{1903}, std::chrono::month{3}, std::chrono::day{1}}, std::chrono::milliseconds{0}), - *rdf4cpp::util::construct_timepoint(YearMonthDay{Year{1903}, std::chrono::month{7}, std::chrono::day{1}}, std::chrono::milliseconds{0}), + rdf4cpp::util::construct_timepoint(YearMonthDay{Year{1696}, std::chrono::month{9}, std::chrono::day{1}}, std::chrono::milliseconds{0}), + rdf4cpp::util::construct_timepoint(YearMonthDay{Year{1697}, std::chrono::month{2}, std::chrono::day{1}}, std::chrono::milliseconds{0}), + rdf4cpp::util::construct_timepoint(YearMonthDay{Year{1903}, std::chrono::month{3}, std::chrono::day{1}}, std::chrono::milliseconds{0}), + rdf4cpp::util::construct_timepoint(YearMonthDay{Year{1903}, std::chrono::month{7}, std::chrono::day{1}}, std::chrono::milliseconds{0}), }; auto cmp = [lhs, rhs](const rdf4cpp::TimePoint& tp) noexcept { try { diff --git a/src/rdf4cpp/datatypes/xsd/time/Month.cpp b/src/rdf4cpp/datatypes/xsd/time/Month.cpp index bf22e77c9..5b38482d6 100644 --- a/src/rdf4cpp/datatypes/xsd/time/Month.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/Month.cpp @@ -38,7 +38,7 @@ bool capabilities::Default::serialize_canonical_string(cpp_type cons template<> std::partial_ordering capabilities::Comparable::compare(cpp_type const &lhs, cpp_type const &rhs) noexcept { - auto month_to_tp = [](std::chrono::month m) noexcept -> std::optional { + auto month_to_tp = [](std::chrono::month m) noexcept -> TimePoint { return rdf4cpp::util::construct_timepoint(YearMonthDay{rdf4cpp::util::time_point_replacement_date.year(), m, std::chrono::last}, rdf4cpp::util::time_point_replacement_time_of_day); }; return registry::util::compare_time_points(month_to_tp(lhs.first), lhs.second, month_to_tp(rhs.first), rhs.second); diff --git a/src/rdf4cpp/datatypes/xsd/time/MonthDay.cpp b/src/rdf4cpp/datatypes/xsd/time/MonthDay.cpp index c04f76669..2967c71ba 100644 --- a/src/rdf4cpp/datatypes/xsd/time/MonthDay.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/MonthDay.cpp @@ -61,7 +61,7 @@ capabilities::Inlineable::cpp_type capabilities::Inlineable std::partial_ordering capabilities::Comparable::compare(cpp_type const &lhs, cpp_type const &rhs) noexcept { - auto md_to_tp = [](std::chrono::month_day md) noexcept -> std::optional { + auto md_to_tp = [](std::chrono::month_day md) noexcept -> TimePoint { return rdf4cpp::util::construct_timepoint(YearMonthDay{rdf4cpp::util::time_point_replacement_date.year(), md.month(), md.day()}, rdf4cpp::util::time_point_replacement_time_of_day); }; return registry::util::compare_time_points(md_to_tp(lhs.first), lhs.second, md_to_tp(rhs.first), rhs.second); diff --git a/src/rdf4cpp/datatypes/xsd/time/Time.cpp b/src/rdf4cpp/datatypes/xsd/time/Time.cpp index 3f411a008..b86ab4940 100644 --- a/src/rdf4cpp/datatypes/xsd/time/Time.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/Time.cpp @@ -77,8 +77,7 @@ std::partial_ordering capabilities::Comparable::compare(cpp_type const template<> template<> capabilities::Promotable::promoted_cpp_type<0> capabilities::Promotable::promote<0>(cpp_type const &value) noexcept { - // time_point_replacement_date can not cause an overflow - return std::make_pair(*rdf4cpp::util::construct_timepoint(rdf4cpp::util::time_point_replacement_date, value.first), value.second); + return std::make_pair(rdf4cpp::util::construct_timepoint(rdf4cpp::util::time_point_replacement_date, value.first), value.second); } template<> diff --git a/src/rdf4cpp/datatypes/xsd/time/Year.cpp b/src/rdf4cpp/datatypes/xsd/time/Year.cpp index 4bfacc981..e0a4d19be 100644 --- a/src/rdf4cpp/datatypes/xsd/time/Year.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/Year.cpp @@ -26,7 +26,7 @@ bool capabilities::Default::serialize_canonical_string(cpp_type const template<> std::partial_ordering capabilities::Comparable::compare(cpp_type const &lhs, cpp_type const &rhs) noexcept { - auto year_to_tp = [](Year const &t) -> std::optional { + auto year_to_tp = [](Year const &t) -> TimePoint { return rdf4cpp::util::construct_timepoint(YearMonthDay{t, rdf4cpp::util::time_point_replacement_date.month(), rdf4cpp::util::time_point_replacement_date.day()}, rdf4cpp::util::time_point_replacement_time_of_day); }; return registry::util::compare_time_points(year_to_tp(lhs.first), lhs.second, year_to_tp(rhs.first), rhs.second); diff --git a/src/rdf4cpp/datatypes/xsd/time/YearMonth.cpp b/src/rdf4cpp/datatypes/xsd/time/YearMonth.cpp index edc4fb75c..d0cb023d1 100644 --- a/src/rdf4cpp/datatypes/xsd/time/YearMonth.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/YearMonth.cpp @@ -62,7 +62,7 @@ capabilities::Inlineable::cpp_type capabilities::Inlineable std::partial_ordering capabilities::Comparable::compare(cpp_type const &lhs, cpp_type const &rhs) noexcept { - auto ym_to_tp = [](YearMonth const &t) -> std::optional { + auto ym_to_tp = [](YearMonth const &t) -> TimePoint { return rdf4cpp::util::construct_timepoint(YearMonthDay{t.year(), t.month(), std::chrono::last}, rdf4cpp::util::time_point_replacement_time_of_day); }; return registry::util::compare_time_points(ym_to_tp(lhs.first), lhs.second, ym_to_tp(rhs.first), rhs.second); diff --git a/tests/datatype/tests_time_types.cpp b/tests/datatype/tests_time_types.cpp index 1b05ac2ce..7463dac6b 100644 --- a/tests/datatype/tests_time_types.cpp +++ b/tests/datatype/tests_time_types.cpp @@ -187,6 +187,9 @@ TEST_CASE("precision") { CHECK(nanos.count() > end_of_universe); auto r = CI(std::numeric_limits::max()) / nanos.count(); CHECK(r > 0); + + CHECK(!rdf4cpp::util::deconstruct_timepoint(rdf4cpp::TimePoint::max()).has_value()); + CHECK(!rdf4cpp::util::deconstruct_timepoint(rdf4cpp::TimePoint::min()).has_value()); } TEST_CASE("datatype gYear") { @@ -394,13 +397,13 @@ TEST_CASE("datatype dateTime") { CHECK(std::string(datatypes::xsd::DateTime::identifier) == "http://www.w3.org/2001/XMLSchema#dateTime"); rdf4cpp::OptionalTimezone const tz = std::nullopt; - basic_test(std::make_pair(*rdf4cpp::util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50}), tz), "2042-05-01T00:50:00", std::partial_ordering::equivalent); - basic_test(std::make_pair(*rdf4cpp::util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::hours{12} + std::chrono::minutes{34} + std::chrono::seconds{56} + std::chrono::milliseconds{789}), tz), "2042-05-01T12:34:56.789", std::partial_ordering::equivalent); - basic_test(std::make_pair(*rdf4cpp::util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50} + std::chrono::milliseconds{100}), tz), "2042-05-01T00:50:00.1", std::partial_ordering::equivalent); - basic_test(std::make_pair(*rdf4cpp::util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50} + std::chrono::nanoseconds{123456789}), tz), "2042-05-01T00:50:00.12345678910", std::partial_ordering::equivalent, true); - basic_test(std::make_pair(*rdf4cpp::util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{42}), tz), "2042-05-01T00:50:00", std::partial_ordering::less); - basic_test(std::make_pair(*rdf4cpp::util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50}), Timezone{std::chrono::hours{1}}), "2042-05-01T00:50:00+01:00", std::partial_ordering::equivalent); - basic_test(std::make_pair(*rdf4cpp::util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50}), Timezone{std::chrono::minutes{-65}}), "2042-05-01T00:50:00-01:05", std::partial_ordering::equivalent); + basic_test(std::make_pair(rdf4cpp::util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50}), tz), "2042-05-01T00:50:00", std::partial_ordering::equivalent); + basic_test(std::make_pair(rdf4cpp::util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::hours{12} + std::chrono::minutes{34} + std::chrono::seconds{56} + std::chrono::milliseconds{789}), tz), "2042-05-01T12:34:56.789", std::partial_ordering::equivalent); + basic_test(std::make_pair(rdf4cpp::util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50} + std::chrono::milliseconds{100}), tz), "2042-05-01T00:50:00.1", std::partial_ordering::equivalent); + basic_test(std::make_pair(rdf4cpp::util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50} + std::chrono::nanoseconds{123456789}), tz), "2042-05-01T00:50:00.12345678910", std::partial_ordering::equivalent, true); + basic_test(std::make_pair(rdf4cpp::util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{42}), tz), "2042-05-01T00:50:00", std::partial_ordering::less); + basic_test(std::make_pair(rdf4cpp::util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50}), Timezone{std::chrono::hours{1}}), "2042-05-01T00:50:00+01:00", std::partial_ordering::equivalent); + basic_test(std::make_pair(rdf4cpp::util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50}), Timezone{std::chrono::minutes{-65}}), "2042-05-01T00:50:00-01:05", std::partial_ordering::equivalent); basic_test("2042-05-05T13:40:08", "2042-05-05T13:40:08", std::partial_ordering::equivalent); basic_test("2041-05-05T13:40:08", "2042-05-05T13:40:08", std::partial_ordering::less); basic_test("2042-05-05T13:40:08", "2041-05-05T13:40:08", std::partial_ordering::greater); @@ -458,13 +461,13 @@ TEST_CASE("datatype dateTimeStamp") { CHECK(std::string(datatypes::xsd::DateTimeStamp::identifier) == "http://www.w3.org/2001/XMLSchema#dateTimeStamp"); Timezone const tz{std::chrono::hours{0}}; - basic_test(ZonedTime(tz, *util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50})), "2042-05-01T00:50:00Z", std::partial_ordering::equivalent); - basic_test(ZonedTime(tz, *util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::hours{12} + std::chrono::minutes{34} + std::chrono::seconds{56} + std::chrono::milliseconds{789})), "2042-05-01T12:34:56.789Z", std::partial_ordering::equivalent); - basic_test(ZonedTime(tz, *util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50} + std::chrono::milliseconds{100})), "2042-05-01T00:50:00.1Z", std::partial_ordering::equivalent); - basic_test(ZonedTime(tz, *util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50} + std::chrono::nanoseconds{123456789})), "2042-05-01T00:50:00.12345678910Z", std::partial_ordering::equivalent, true); - basic_test(ZonedTime(tz, *util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{42})), "2042-05-01T00:50:00Z", std::partial_ordering::less); - basic_test(ZonedTime(Timezone{std::chrono::hours{1}}, *util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50})), "2042-05-01T00:50:00+01:00", std::partial_ordering::equivalent); - basic_test(ZonedTime(Timezone{std::chrono::minutes{-65}}, *util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50})), "2042-05-01T00:50:00-01:05", std::partial_ordering::equivalent); + basic_test(ZonedTime(tz, util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50})), "2042-05-01T00:50:00Z", std::partial_ordering::equivalent); + basic_test(ZonedTime(tz, util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::hours{12} + std::chrono::minutes{34} + std::chrono::seconds{56} + std::chrono::milliseconds{789})), "2042-05-01T12:34:56.789Z", std::partial_ordering::equivalent); + basic_test(ZonedTime(tz, util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50} + std::chrono::milliseconds{100})), "2042-05-01T00:50:00.1Z", std::partial_ordering::equivalent); + basic_test(ZonedTime(tz, util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50} + std::chrono::nanoseconds{123456789})), "2042-05-01T00:50:00.12345678910Z", std::partial_ordering::equivalent, true); + basic_test(ZonedTime(tz, util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{42})), "2042-05-01T00:50:00Z", std::partial_ordering::less); + basic_test(ZonedTime(Timezone{std::chrono::hours{1}}, util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50})), "2042-05-01T00:50:00+01:00", std::partial_ordering::equivalent); + basic_test(ZonedTime(Timezone{std::chrono::minutes{-65}}, util::construct_timepoint(YearMonthDay{Year{2042}, std::chrono::month{5}, std::chrono::day{1}}, std::chrono::minutes{50})), "2042-05-01T00:50:00-01:05", std::partial_ordering::equivalent); basic_test("2042-05-05T13:40:08Z", "2042-05-05T13:40:08Z", std::partial_ordering::equivalent); basic_test("2041-05-05T13:40:08Z", "2042-05-05T13:40:08Z", std::partial_ordering::less); basic_test("2042-05-05T13:40:08Z", "2041-05-05T13:40:08Z", std::partial_ordering::greater); From 867cd20a4384a496a9abc17ae0913f9f152b74bc Mon Sep 17 00:00:00 2001 From: mcb Date: Fri, 11 Jul 2025 14:07:14 +0200 Subject: [PATCH 6/9] simplify timepoint+dur --- src/rdf4cpp/Timezone.hpp | 11 ++++---- .../datatypes/registry/util/DateTimeUtils.hpp | 25 +++---------------- 2 files changed, 9 insertions(+), 27 deletions(-) diff --git a/src/rdf4cpp/Timezone.hpp b/src/rdf4cpp/Timezone.hpp index 1d84d3e99..d9331b114 100644 --- a/src/rdf4cpp/Timezone.hpp +++ b/src/rdf4cpp/Timezone.hpp @@ -291,7 +291,7 @@ namespace rdf4cpp { Month month_ = Month{1}; // returns nullopt iff overflow - static constexpr std::optional create_normalized(util::CheckedIntegral y, util::CheckedIntegral mo) noexcept { + static constexpr std::optional create_normalized(util::CheckedIntegral y, util::CheckedIntegral mo) noexcept { mo -= 1; y += mo / 12; mo %= 12; @@ -301,10 +301,11 @@ namespace rdf4cpp { } mo += 1; auto um = mo.checked_cast(); - if (y.is_invalid() || um.is_invalid()) { + auto uy = y.checked_cast(); + if (uy.is_invalid() || um.is_invalid()) { return std::nullopt; } - return YearMonth{Year{y.get_value()}, std::chrono::month{um.get_value()}}; + return YearMonth{Year{uy.get_value()}, std::chrono::month{um.get_value()}}; } public: @@ -353,7 +354,7 @@ namespace rdf4cpp { return create_normalized(static_cast(ym.year_), static_cast(ym.month_) + d.count()).value(); } [[nodiscard]] constexpr std::optional add_checked(std::chrono::months d) const noexcept { - return create_normalized(static_cast(year_), util::CheckedIntegral(static_cast(month_)) + d.count()); + return create_normalized(static_cast(year_), util::CheckedIntegral(static_cast(month_)) + d.count()); } constexpr YearMonth &operator+=(std::chrono::months d) noexcept { @@ -375,7 +376,7 @@ namespace rdf4cpp { return create_normalized(static_cast(ym.year_), static_cast(ym.month_) - d.count()).value(); } [[nodiscard]] constexpr std::optional sub_checked(std::chrono::months d) const noexcept { - return create_normalized(static_cast(year_), util::CheckedIntegral(static_cast(month_)) - d.count()); + return create_normalized(static_cast(year_), util::CheckedIntegral(static_cast(month_)) - d.count()); } friend constexpr std::chrono::months operator-(YearMonth const &a, YearMonth const &b) noexcept { diff --git a/src/rdf4cpp/datatypes/registry/util/DateTimeUtils.hpp b/src/rdf4cpp/datatypes/registry/util/DateTimeUtils.hpp index c1f48b8bc..095888940 100644 --- a/src/rdf4cpp/datatypes/registry/util/DateTimeUtils.hpp +++ b/src/rdf4cpp/datatypes/registry/util/DateTimeUtils.hpp @@ -101,31 +101,12 @@ namespace rdf4cpp::datatypes::registry::util { return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); } auto [ymd, time] = *dec_tp; - // TODO use ymd.checked_add - using Checked = rdf4cpp::util::CheckedIntegral; - Checked checked_m = static_cast(ymd.month()); - checked_m += Checked{static_cast(ymd.year())} * 12; - checked_m += d.first.count(); - - auto const checked_y = (checked_m - 1) / 12; - - checked_m = abs(checked_m - 1); - - auto const y_casted = checked_y.checked_cast(); - if (y_casted.is_invalid()) { - return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); - } - - auto const y = Year{y_casted.get_value()}; - - auto const m_casted = (checked_m % 12 + 1).checked_cast(); - if (m_casted.is_invalid()) { + auto ymd_c = ymd.add_checked(d.first); + if (!ymd_c.has_value()) { return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); } - auto const m = std::chrono::month{m_casted.get_value()}; - - ymd = YearMonthDay{y, m, ymd.day()}; + ymd = *ymd_c; if (!ymd.ok()) { ymd = YearMonthDay{ymd.year(), ymd.month(), std::chrono::last}; } From 456ef07dd37f9a947be2d54f03a4761c71d79c15 Mon Sep 17 00:00:00 2001 From: mcb Date: Fri, 11 Jul 2025 15:25:18 +0200 Subject: [PATCH 7/9] num chars, literal api --- src/rdf4cpp/Literal.cpp | 73 +++++++++++++------ src/rdf4cpp/Timezone.hpp | 54 +++++++------- .../datatypes/registry/util/DateTimeUtils.hpp | 26 ++++--- 3 files changed, 90 insertions(+), 63 deletions(-) diff --git a/src/rdf4cpp/Literal.cpp b/src/rdf4cpp/Literal.cpp index ecb05bde1..3ff7731ee 100644 --- a/src/rdf4cpp/Literal.cpp +++ b/src/rdf4cpp/Literal.cpp @@ -2416,73 +2416,84 @@ Literal Literal::now(storage::DynNodeStoragePtr node_storage) { } std::optional Literal::year() const noexcept { - if (!datatype_eq() && !datatype_eq() && !datatype_eq() - && !datatype_eq() && !datatype_eq()) + if (!datatype_eq() && !datatype_eq() && !datatype_eq() && !datatype_eq() && !datatype_eq()) { return std::nullopt; + } auto casted = this->cast_to_value(); - if (!casted.has_value()) + if (!casted.has_value()) { return std::nullopt; + } auto [date, _] = *util::deconstruct_timepoint(casted->first); return date.year(); } Literal Literal::as_year(storage::DynNodeStoragePtr node_storage) const { auto r = this->year(); - if (!r.has_value()) + if (!r.has_value()) { return Literal{}; + } return Literal::make_typed_from_value(static_cast(*r), select_node_storage(node_storage)); } std::optional Literal::month() const noexcept { if (!datatype_eq() && !datatype_eq() && !datatype_eq() - && !datatype_eq() && !datatype_eq() && !datatype_eq()) + && !datatype_eq() && !datatype_eq() && !datatype_eq()) { return std::nullopt; + } auto casted = this->cast_to_value(); - if (!casted.has_value()) + if (!casted.has_value()) { return std::nullopt; + } auto [date, _] = *util::deconstruct_timepoint(casted->first); return date.month(); } Literal Literal::as_month(storage::DynNodeStoragePtr node_storage) const { auto r = this->month(); - if (!r.has_value()) + if (!r.has_value()) { return Literal{}; + } return Literal::make_typed_from_value(static_cast(*r), select_node_storage(node_storage)); } std::optional Literal::day() const noexcept { if (!datatype_eq() && !datatype_eq() && !datatype_eq() - && !datatype_eq() && !datatype_eq()) + && !datatype_eq() && !datatype_eq()) { return std::nullopt; + } auto casted = this->cast_to_value(); - if (!casted.has_value()) + if (!casted.has_value()) { return std::nullopt; + } auto [date, _] = *util::deconstruct_timepoint(casted->first); return date.day(); } Literal Literal::as_day(storage::DynNodeStoragePtr node_storage) const { auto r = this->day(); - if (!r.has_value()) + if (!r.has_value()) { return Literal{}; + } return Literal::make_typed_from_value(static_cast(*r), select_node_storage(node_storage)); } std::optional Literal::hours() const noexcept { - if (!datatype_eq() && !datatype_eq() && !datatype_eq()) + if (!datatype_eq() && !datatype_eq() && !datatype_eq()) { return std::nullopt; + } auto casted = this->cast_to_value(); - if (!casted.has_value()) + if (!casted.has_value()) { return std::nullopt; + } auto [_, time] = *util::deconstruct_timepoint(casted->first); return std::chrono::hh_mm_ss{std::chrono::duration_cast(time)}.hours(); } Literal Literal::as_hours(storage::DynNodeStoragePtr node_storage) const { auto r = this->hours(); - if (!r.has_value()) + if (!r.has_value()) { return Literal{}; + } return Literal::make_typed_from_value(r->count(), select_node_storage(node_storage)); } @@ -2490,25 +2501,29 @@ std::optional Literal::minutes() const noexcept { if (!datatype_eq() && !datatype_eq() && !datatype_eq()) return std::nullopt; auto casted = this->cast_to_value(); - if (!casted.has_value()) + if (!casted.has_value()) { return std::nullopt; + } auto [_, time] = *util::deconstruct_timepoint(casted->first); return std::chrono::hh_mm_ss{std::chrono::duration_cast(time)}.minutes(); } Literal Literal::as_minutes(storage::DynNodeStoragePtr node_storage) const { auto r = this->minutes(); - if (!r.has_value()) + if (!r.has_value()) { return Literal{}; + } return Literal::make_typed_from_value(r->count(), select_node_storage(node_storage)); } std::optional Literal::seconds() const noexcept { - if (!datatype_eq() && !datatype_eq() && !datatype_eq()) + if (!datatype_eq() && !datatype_eq() && !datatype_eq()) { return std::nullopt; + } auto casted = this->cast_to_value(); - if (!casted.has_value()) + if (!casted.has_value()) { return std::nullopt; + } auto [_, t] = *util::deconstruct_timepoint(casted->first); std::chrono::hh_mm_ss const time{std::chrono::duration_cast(t)}; return time.seconds() + time.subseconds(); @@ -2516,39 +2531,49 @@ std::optional Literal::seconds() const noexcept { Literal Literal::as_seconds(storage::DynNodeStoragePtr node_storage) const { auto r = this->seconds(); - if (!r.has_value()) + if (!r.has_value()) { return Literal{}; - return Literal::make_typed_from_value(Decimal128{r->count(), 9}, select_node_storage(node_storage)); + } + using dur = std::remove_cvref_t; + static_assert(dur::period::num == 1); + static constexpr auto ex = rdf4cpp::datatypes::registry::util::chrono_max_canonical_string_chars::sub_seconds; + static_assert(Decimal128{dur{std::chrono::seconds{1}}.count(), ex} == Decimal128{1, 0}); + return Literal::make_typed_from_value(Decimal128{r->count(), ex}, select_node_storage(node_storage)); } std::optional Literal::timezone() const noexcept { auto casted = this->cast_to_value(); - if (!casted.has_value()) + if (!casted.has_value()) { return std::nullopt; + } auto tz = casted->second; return tz; } Literal Literal::as_timezone(storage::DynNodeStoragePtr node_storage) const { auto r = this->timezone(); - if (!r.has_value()) + if (!r.has_value()) { return Literal{}; + } return Literal::make_typed_from_value(r->offset, select_node_storage(node_storage)); } std::optional Literal::tz() const noexcept { auto casted = this->cast_to_value(); - if (!casted.has_value()) + if (!casted.has_value()) { return std::nullopt; + } auto tz = casted->second; - if (!tz.has_value()) + if (!tz.has_value()) { return ""; + } return tz->to_canonical_string(); } Literal Literal::as_tz(storage::DynNodeStoragePtr node_storage) const { auto r = this->tz(); - if (!r.has_value()) + if (!r.has_value()) { return Literal{}; + } return Literal::make_simple_unchecked(*r, false, select_node_storage(node_storage)); } diff --git a/src/rdf4cpp/Timezone.hpp b/src/rdf4cpp/Timezone.hpp index d9331b114..3e8e69cdd 100644 --- a/src/rdf4cpp/Timezone.hpp +++ b/src/rdf4cpp/Timezone.hpp @@ -133,22 +133,22 @@ namespace rdf4cpp { namespace util { /** - * turns any duration to its CheckedIntegral counterpart. - * @tparam R - * @param v - * @return - */ + * turns any duration to its CheckedIntegral counterpart. + * @tparam R + * @param v + * @return + */ template constexpr std::chrono::duration, R> to_checked(std::chrono::duration v) noexcept { return std::chrono::duration, R>{v.count()}; } /** - * turns any CheckedIntegral duration back to its integer based duration. - * @note undefined behavior, if v is invalid - * @tparam R - * @param v - * @return - */ + * turns any CheckedIntegral duration back to its integer based duration. + * @note undefined behavior, if v is invalid + * @tparam R + * @param v + * @return + */ template constexpr std::optional> from_checked(std::chrono::duration, R> v) noexcept { if (v.count().is_invalid()) { @@ -157,24 +157,24 @@ namespace rdf4cpp { return std::chrono::duration{v.count().get_value()}; } /** - * turns any time_point to its CheckedIntegral counterpart. - * @tparam C - * @tparam R - * @param v - * @return - */ + * turns any time_point to its CheckedIntegral counterpart. + * @tparam C + * @tparam R + * @param v + * @return + */ template constexpr std::chrono::time_point, R>> to_checked(std::chrono::time_point> v) noexcept { return std::chrono::time_point, R>>{to_checked(v.time_since_epoch())}; } /** - * turns any CheckedIntegral time_point back to its integer based time_point. - * @note undefined behavior, if v is invalid - * @tparam C - * @tparam R - * @param v - * @return - */ + * turns any CheckedIntegral time_point back to its integer based time_point. + * @note undefined behavior, if v is invalid + * @tparam C + * @tparam R + * @param v + * @return + */ template constexpr std::optional>> from_checked(std::chrono::time_point, R>> v) noexcept { if (v.time_since_epoch().count().is_invalid()) { @@ -191,9 +191,9 @@ namespace rdf4cpp { using Day = std::chrono::day; /** - * Like std::chrono::year, except it has a greater range. - * adapted from https://howardhinnant.github.io/date_algorithms.html - */ + * Like std::chrono::year, except it has a greater range. + * adapted from https://howardhinnant.github.io/date_algorithms.html + */ struct Year { private: int64_t value_; diff --git a/src/rdf4cpp/datatypes/registry/util/DateTimeUtils.hpp b/src/rdf4cpp/datatypes/registry/util/DateTimeUtils.hpp index 095888940..e93e16696 100644 --- a/src/rdf4cpp/datatypes/registry/util/DateTimeUtils.hpp +++ b/src/rdf4cpp/datatypes/registry/util/DateTimeUtils.hpp @@ -247,32 +247,34 @@ namespace rdf4cpp::datatypes::registry::util { static_assert(number_of_digits(9) == 1); static_assert(number_of_digits(-1) == 2); static_assert(number_of_digits(10) == 2); - static_assert(number_of_digits(std::numeric_limits::max()) == std::numeric_limits::digits10 + 1); + static_assert(number_of_digits(std::numeric_limits::max()) == std::numeric_limits::digits10 + 1); namespace chrono_max_canonical_string_chars { - // TODO check limits - inline constexpr size_t year = std::numeric_limits::digits10 + 2; // +1 for the not fully representable digit, +1 for - sign + inline constexpr size_t year = std::numeric_limits::digits10 + 2; // +1 for the not fully representable digit, +1 for - sign //std::chrono::day is in [0, 255] inline constexpr size_t day = number_of_digits(255); //std::chrono::month is in [0, 255] inline constexpr size_t month = number_of_digits(255); + // not including leading 0. + inline constexpr size_t sub_seconds = number_of_digits(std::chrono::nanoseconds::period::den) - 1; + static_assert(std::chrono::nanoseconds::period::num == 1); //[0, 59.999...] (includes nanoseconds) - inline constexpr size_t seconds = 2 + 1 + 9; + inline constexpr size_t seconds = number_of_digits(59) + 1 + sub_seconds; //[0,59] - inline constexpr size_t minutes = 2; - //[0,24] (used if more than 24 hours get added to days) - inline constexpr size_t hours = 2; + inline constexpr size_t minutes = number_of_digits(59); + //[0,24] + inline constexpr size_t hours = number_of_digits(24); //used if no days are serialized - inline constexpr size_t hours_unbound = number_of_digits(std::chrono::floor(std::chrono::milliseconds::max()).count()); + inline constexpr size_t hours_unbound = number_of_digits(std::chrono::floor(std::chrono::nanoseconds::max()).count()); static_assert(sizeof(std::chrono::hours::rep) <= sizeof(int64_t)); - static_assert(sizeof(std::chrono::milliseconds ::rep) <= sizeof(int64_t)); + static_assert(sizeof(std::chrono::nanoseconds::rep) <= sizeof(int64_t)); //duration static constexpr size_t years = number_of_digits(std::chrono::floor(std::chrono::months::max()).count()); static_assert(sizeof(std::chrono::years::rep) <= sizeof(int64_t)); static_assert(sizeof(std::chrono::months::rep) <= sizeof(int64_t)); + //duration [1,12] + inline constexpr size_t months = number_of_digits(12); //duration - inline constexpr size_t months = 2; - //duration - inline constexpr size_t days = number_of_digits(std::chrono::floor(std::chrono::milliseconds::max()).count()); + inline constexpr size_t days = number_of_digits(std::chrono::floor(std::chrono::nanoseconds::max()).count()); static_assert(sizeof(std::chrono::days::rep) <= sizeof(int64_t)); }; // namespace chrono_max_canonical_string_chars From 4f84279b3a26b1e2969e86b9d8a7adceaa601936 Mon Sep 17 00:00:00 2001 From: mcb Date: Mon, 14 Jul 2025 14:22:15 +0200 Subject: [PATCH 8/9] last overflow checks --- src/rdf4cpp/Literal.cpp | 36 +++++++++++++++---- src/rdf4cpp/Timezone.hpp | 1 - src/rdf4cpp/datatypes/xsd/time/DateTime.cpp | 6 +++- .../datatypes/xsd/time/DateTimeStamp.cpp | 6 +++- tests/datatype/tests_time_types.cpp | 15 ++++++++ 5 files changed, 55 insertions(+), 9 deletions(-) diff --git a/src/rdf4cpp/Literal.cpp b/src/rdf4cpp/Literal.cpp index 3ff7731ee..00948730e 100644 --- a/src/rdf4cpp/Literal.cpp +++ b/src/rdf4cpp/Literal.cpp @@ -2423,7 +2423,11 @@ std::optional Literal::year() const noexcept { if (!casted.has_value()) { return std::nullopt; } - auto [date, _] = *util::deconstruct_timepoint(casted->first); + auto dec = util::deconstruct_timepoint(casted->first); + if (!dec.has_value()) [[unlikely]] { + return std::nullopt; + } + auto [date, _] = *dec; return date.year(); } @@ -2444,7 +2448,11 @@ std::optional Literal::month() const noexcept { if (!casted.has_value()) { return std::nullopt; } - auto [date, _] = *util::deconstruct_timepoint(casted->first); + auto dec = util::deconstruct_timepoint(casted->first); + if (!dec.has_value()) [[unlikely]] { + return std::nullopt; + } + auto [date, _] = *dec; return date.month(); } @@ -2465,7 +2473,11 @@ std::optional Literal::day() const noexcept { if (!casted.has_value()) { return std::nullopt; } - auto [date, _] = *util::deconstruct_timepoint(casted->first); + auto dec = util::deconstruct_timepoint(casted->first); + if (!dec.has_value()) [[unlikely]] { + return std::nullopt; + } + auto [date, _] = *dec; return date.day(); } @@ -2485,7 +2497,11 @@ std::optional Literal::hours() const noexcept { if (!casted.has_value()) { return std::nullopt; } - auto [_, time] = *util::deconstruct_timepoint(casted->first); + auto dec = util::deconstruct_timepoint(casted->first); + if (!dec.has_value()) [[unlikely]] { + return std::nullopt; + } + auto [_, time] = *dec; return std::chrono::hh_mm_ss{std::chrono::duration_cast(time)}.hours(); } @@ -2504,7 +2520,11 @@ std::optional Literal::minutes() const noexcept { if (!casted.has_value()) { return std::nullopt; } - auto [_, time] = *util::deconstruct_timepoint(casted->first); + auto dec = util::deconstruct_timepoint(casted->first); + if (!dec.has_value()) [[unlikely]] { + return std::nullopt; + } + auto [_, time] = *dec; return std::chrono::hh_mm_ss{std::chrono::duration_cast(time)}.minutes(); } @@ -2524,7 +2544,11 @@ std::optional Literal::seconds() const noexcept { if (!casted.has_value()) { return std::nullopt; } - auto [_, t] = *util::deconstruct_timepoint(casted->first); + auto dec = util::deconstruct_timepoint(casted->first); + if (!dec.has_value()) [[unlikely]] { + return std::nullopt; + } + auto [_, t] = *dec; std::chrono::hh_mm_ss const time{std::chrono::duration_cast(t)}; return time.seconds() + time.subseconds(); } diff --git a/src/rdf4cpp/Timezone.hpp b/src/rdf4cpp/Timezone.hpp index 3e8e69cdd..ddcfdd924 100644 --- a/src/rdf4cpp/Timezone.hpp +++ b/src/rdf4cpp/Timezone.hpp @@ -611,7 +611,6 @@ namespace rdf4cpp { return ms; } - // TODO check return values constexpr std::optional> deconstruct_timepoint(TimePoint const &tp) noexcept { auto const days = std::chrono::floor>(to_checked(tp)); if (days.time_since_epoch().count().is_invalid()) { diff --git a/src/rdf4cpp/datatypes/xsd/time/DateTime.cpp b/src/rdf4cpp/datatypes/xsd/time/DateTime.cpp index 5bf4ea8e7..15efccdf8 100644 --- a/src/rdf4cpp/datatypes/xsd/time/DateTime.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/DateTime.cpp @@ -65,7 +65,11 @@ bool capabilities::Default::serialize_canonical_string(cpp_type co registry::util::chrono_max_canonical_string_chars::minutes + 1 + registry::util::chrono_max_canonical_string_chars::seconds + Timezone::max_canonical_string_chars> buff; - auto [date, time] = *rdf4cpp::util::deconstruct_timepoint(value.first); + auto dec = rdf4cpp::util::deconstruct_timepoint(value.first); + if (!dec.has_value()) [[unlikely]] { + return writer::write_str("invalid DateTime", writer); + } + auto [date, time] = *dec; char *it = std::format_to(buff.data(), "{}T{:%H:%M:%S}", date, std::chrono::hh_mm_ss{std::chrono::duration_cast(time)}); it = util::canonical_seconds_remove_empty_millis(it); if (value.second.has_value()) { diff --git a/src/rdf4cpp/datatypes/xsd/time/DateTimeStamp.cpp b/src/rdf4cpp/datatypes/xsd/time/DateTimeStamp.cpp index 8f39291f8..c6e08f137 100644 --- a/src/rdf4cpp/datatypes/xsd/time/DateTimeStamp.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/DateTimeStamp.cpp @@ -71,7 +71,11 @@ bool capabilities::Default::serialize_canonical_string(cpp_ty registry::util::chrono_max_canonical_string_chars::minutes + 1 + registry::util::chrono_max_canonical_string_chars::seconds + Timezone::max_canonical_string_chars> buff; - auto [date, time] = *rdf4cpp::util::deconstruct_timepoint(value.get_local_time()); + auto dec = rdf4cpp::util::deconstruct_timepoint(value.get_local_time()); + if (!dec.has_value()) [[unlikely]] { + return writer::write_str("invalid DateTimeStamp", writer); + } + auto [date, time] = *dec; char *it = std::format_to(buff.data(), "{}T{:%H:%M:%S}", date, std::chrono::hh_mm_ss{std::chrono::duration_cast(time)}); it = util::canonical_seconds_remove_empty_millis(it); it = value.get_time_zone().to_canonical_string(it); diff --git a/tests/datatype/tests_time_types.cpp b/tests/datatype/tests_time_types.cpp index 7463dac6b..beda1797c 100644 --- a/tests/datatype/tests_time_types.cpp +++ b/tests/datatype/tests_time_types.cpp @@ -453,6 +453,9 @@ TEST_CASE("datatype dateTime") { CHECK(Literal::make_typed("-13798000000-01-01T00:00:00Z").lexical_form() == "-13798000000-01-01T00:00:00Z"); // check that age of universe is in range CHECK(Literal::make_typed("100000000000000-01-01T00:00:00Z").lexical_form() == "100000000000000-01-01T00:00:00Z"); + + auto too_big = Literal::make_typed_from_value(datatypes::xsd::DateTime::cpp_type{TimePoint::max(), Timezone{}}); + CHECK(too_big.lexical_form() == "invalid DateTime"); } TEST_CASE("datatype dateTimeStamp") { @@ -509,6 +512,9 @@ TEST_CASE("datatype dateTimeStamp") { CHECK(a.null()); // turn off unused and nodiscard ignored warnings CHECK(Literal::make_typed("2042-05-06T00:00:00.000Z") == Literal::make_typed("2042-05-05T24:00:00Z")); CHECK(Literal::make_typed("2042-05-05T24:00:00.000Z").lexical_form() == "2042-05-06T00:00:00Z"); + + auto too_big = Literal::make_typed_from_value(datatypes::xsd::DateTimeStamp::cpp_type{Timezone{}, TimePoint::max()}); + CHECK(too_big.lexical_form() == "invalid DateTimeStamp"); } TEST_CASE("datatype duration") { @@ -679,6 +685,15 @@ TEST_CASE("Literal API") { CHECK(Literal::make_typed("---3+1:30").as_tz() == Literal::make_simple("+01:30")); CHECK(Literal::make_typed("---3").as_tz() == Literal::make_simple("")); CHECK(Literal::make_simple("5").as_tz().null()); + + auto too_big = Literal::make_typed_from_value(datatypes::xsd::DateTime::cpp_type{TimePoint::max(), Timezone{}}); + CHECK(!too_big.year().has_value()); + CHECK(!too_big.month().has_value()); + CHECK(!too_big.day().has_value()); + CHECK(!too_big.hours().has_value()); + CHECK(!too_big.minutes().has_value()); + CHECK(!too_big.seconds().has_value()); + CHECK(too_big.timezone().has_value()); } TEST_CASE("arithmetic") { From 217eb9fec73ff3dadfcd7b5b71b66db15718cca2 Mon Sep 17 00:00:00 2001 From: mcb Date: Mon, 14 Jul 2025 14:41:12 +0200 Subject: [PATCH 9/9] fix doc --- src/rdf4cpp/Timezone.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/rdf4cpp/Timezone.hpp b/src/rdf4cpp/Timezone.hpp index ddcfdd924..74928e3a3 100644 --- a/src/rdf4cpp/Timezone.hpp +++ b/src/rdf4cpp/Timezone.hpp @@ -144,7 +144,6 @@ namespace rdf4cpp { } /** * turns any CheckedIntegral duration back to its integer based duration. - * @note undefined behavior, if v is invalid * @tparam R * @param v * @return @@ -169,7 +168,6 @@ namespace rdf4cpp { } /** * turns any CheckedIntegral time_point back to its integer based time_point. - * @note undefined behavior, if v is invalid * @tparam C * @tparam R * @param v