diff --git a/src/rdf4cpp/Literal.cpp b/src/rdf4cpp/Literal.cpp index 30e61c8a..41418faf 100644 --- a/src/rdf4cpp/Literal.cpp +++ b/src/rdf4cpp/Literal.cpp @@ -2420,73 +2420,100 @@ 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); + } + auto dec = util::deconstruct_timepoint(casted->first); + if (!dec.has_value()) [[unlikely]] { + return std::nullopt; + } + auto [date, _] = *dec; 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); + } + auto dec = util::deconstruct_timepoint(casted->first); + if (!dec.has_value()) [[unlikely]] { + return std::nullopt; + } + auto [date, _] = *dec; 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); + } + auto dec = util::deconstruct_timepoint(casted->first); + if (!dec.has_value()) [[unlikely]] { + return std::nullopt; + } + auto [date, _] = *dec; 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); + } + 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(); } 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)); } @@ -2494,65 +2521,87 @@ 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 dec = util::deconstruct_timepoint(casted->first); + if (!dec.has_value()) [[unlikely]] { return std::nullopt; - auto [_, time] = util::deconstruct_timepoint(casted->first); + } + auto [_, time] = *dec; 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); + } + 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(); } 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 6e786f5e..5ed5845c 100644 --- a/src/rdf4cpp/Timezone.hpp +++ b/src/rdf4cpp/Timezone.hpp @@ -11,452 +11,623 @@ #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; - -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_; + 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. + * @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. + * @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 -public: - explicit constexpr Year(int64_t y = 0) noexcept : value_{y} { - } - constexpr explicit operator int64_t() const noexcept { - return value_; - } + using OptionalTimezone = std::optional; - [[nodiscard]] constexpr bool is_leap() const noexcept(noexcept(value_ % 100)) { - return value_ % 4 == 0 && (value_ % 100 != 0 || value_ % 400 == 0); - } + using Month = std::chrono::month; + using Day = std::chrono::day; - constexpr auto operator<=>(Year const &) const noexcept = default; + /** + * 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_; - 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()}; + } + [[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--() 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_}; + } + [[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}; + } -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}; + + // 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 -= 1; + mo += 12; + } + mo += 1; + auto um = mo.checked_cast(); + auto uy = y.checked_cast(); + if (uy.is_invalid() || um.is_invalid()) { + return std::nullopt; + } + return YearMonth{Year{uy.get_value()}, std::chrono::month{um.get_value()}}; + } - [[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_}; + } + [[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_}; + } - 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_); - } + 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(); + } + [[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::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 { - RDF4CPP_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_}; + } + [[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()); + } -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_); + } + [[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 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 { + RDF4CPP_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_}; - } + // 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"); + Int128 y = static_cast(year_); + auto m = static_cast(month_); + auto d = static_cast(day_); + y -= m <= 2; + 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 + doe - 719468}}; + return r; + } + [[nodiscard]] constexpr time_point_local to_time_point_local() const { + auto const v = to_time_point(); + return time_point_local{v.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_}; + } + [[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_}; + } - 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) { + return YearMonthDay{YearMonth{d.year_, d.month_} + m, d.day_}; + } + 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_}; + } -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) { + 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_}; + } -// 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}}; + + // 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(); + auto ms = static_cast(sd); + ms += time_of_day; + return ms; + } -constexpr std::pair deconstruct_timepoint(TimePoint const &tp) { - auto days = std::chrono::floor>(tp); - return {YearMonthDay{days}, tp - days}; -} + 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 @@ -467,7 +638,7 @@ namespace std::chrono { return ::rdf4cpp::Timezone{}; } }; -} // namespace std::chrono +} // namespace std::chrono #ifndef DOXYGEN_PARSER template @@ -524,7 +695,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)}); } }; @@ -548,7 +723,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 2ce7a453..daec769e 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 #include /** @@ -19,350 +19,265 @@ */ 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 { - RDF4CPP_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; + 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)}; } - 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; - if constexpr (Separator != '\0') { - auto p = s.find(Separator, 1); + 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; + } + RDF4CPP_ASSERT(*(it - 1) == '.'); --it; + return it; } - RDF4CPP_ASSERT(*(it - 1) == '.'); - --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); + } + } - boost::multiprecision::checked_int128_t checked_m = static_cast(ymd.month()); - checked_m += boost::multiprecision::checked_int128_t{static_cast(ymd.year())} * 12; - checked_m += d.first.count(); + 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; + + auto ymd_c = ymd.add_checked(d.first); + if (!ymd_c.has_value()) { + return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); + } + ymd = *ymd_c; + if (!ymd.ok()) { + ymd = YearMonthDay{ymd.year(), ymd.month(), std::chrono::last}; + } - auto const checked_y = (checked_m -1 ) / 12; + rdf4cpp::util::TimePoint_Checked date = ymd.to_time_point_local(); + date += time; + date += d.second; - checked_m = abs(checked_m - 1); + auto date_uc = rdf4cpp::util::from_checked(date); + if (!date_uc.has_value()) { + return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); + } - auto const y = Year{static_cast(checked_y)}; - auto const m = std::chrono::month{static_cast(static_cast(checked_m % 12 + 1))}; + auto const deconstructed = rdf4cpp::util::deconstruct_timepoint(*date_uc); + if (!deconstructed.has_value()) { + return nonstd::make_unexpected(datatypes::DynamicError::OverOrUnderFlow); + } - ymd = YearMonthDay{y, m, ymd.day()}; - if (!ymd.ok()) { - ymd = YearMonthDay{ymd.year(), ymd.month(), std::chrono::last}; + return *date_uc; } - TimePoint date = ymd.to_time_point_local(); - date += time; - date += d.second; + 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); + } - rdf4cpp::util::deconstruct_timepoint(date); // check if it still fits into year, throws if not + 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); - return date; -} + 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))); + } -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 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; + } + 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; + } - ZonedTime const other_tp{rhs.second.has_value() ? *rhs.second : Timezone{}, - rhs.first}; + constexpr InliningHelper(TimeType t, rdf4cpp::OptionalTimezone tz) noexcept : tz_offset(encode_tz(tz)), time_value(t) { + } - 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); - } -} - -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; + [[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; - 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; - 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); - }; + uint16_t tz_offset : 11; + uint32_t time_value : tv_width; - if (!atz.has_value()) { - atz = rdf4cpp::util::time_point_replacement_timezone; - } - if (!btz.has_value()) { - btz = rdf4cpp::util::time_point_replacement_timezone; - } + private: + [[maybe_unused]] uint32_t padding : 64 - width = 0; // to make sure the rest of the int64 is 0 - auto a_sys = apply_timezone(a, *atz); - return cmp_opt(a_sys, apply_timezone(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; - } + 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); - constexpr InliningHelper(TimeType t, rdf4cpp::OptionalTimezone tz) noexcept : tz_offset(encode_tz(tz)), time_value(t) { - } + 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]] 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) { + } - 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}}; + } + }; - [[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 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(ym->to_time_point())); } -}; - -inline YearMonthDay 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()}; -} - -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); + // 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 = number_of_digits(59) + 1 + sub_seconds; + //[0,59] + 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::nanoseconds::max()).count()); + static_assert(sizeof(std::chrono::hours::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 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 } // namespace rdf4cpp::datatypes::registry::util diff --git a/src/rdf4cpp/datatypes/xsd/time/Date.cpp b/src/rdf4cpp/datatypes/xsd/time/Date.cpp index cd899f93..b5d6217b 100644 --- a/src/rdf4cpp/datatypes/xsd/time/Date.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/Date.cpp @@ -15,7 +15,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)); @@ -52,10 +56,11 @@ std::optional capabilities::Inlineable return std::nullopt; } auto i = value.first.to_time_point().time_since_epoch().count(); - if (!util::fits_into(i)) [[unlikely]] { + 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<> @@ -98,8 +103,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); } @@ -107,9 +119,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 c3a614df..76f55759 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,8 @@ capabilities::Default::cpp_type capabilities::Default::cpp_type capabilities::Default @@ -59,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()) { @@ -74,14 +84,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 +113,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 a7ee098d..0d1f8622 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,8 @@ capabilities::Default::cpp_type capabilities::Default::cpp_type capabilities::Default @@ -66,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); @@ -82,10 +91,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 +143,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 d0815137..bc32ff8a 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 -> 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/DayTimeDuration.cpp b/src/rdf4cpp/datatypes/xsd/time/DayTimeDuration.cpp index 3bf02648..2e062bd0 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 4488565f..885c362e 100644 --- a/src/rdf4cpp/datatypes/xsd/time/Duration.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/Duration.cpp @@ -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 3888bb7c..da983578 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 -> 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 f36476c9..01b86a3e 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 -> 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 d9516a73..1ef7f973 100644 --- a/src/rdf4cpp/datatypes/xsd/time/Time.cpp +++ b/src/rdf4cpp/datatypes/xsd/time/Time.cpp @@ -101,8 +101,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 +119,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 7958de75..1f78d5fb 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) -> 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); } template<> diff --git a/src/rdf4cpp/datatypes/xsd/time/YearMonth.cpp b/src/rdf4cpp/datatypes/xsd/time/YearMonth.cpp index f12200bc..55c95c21 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) -> 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/src/rdf4cpp/datatypes/xsd/time/YearMonthDuration.cpp b/src/rdf4cpp/datatypes/xsd/time/YearMonthDuration.cpp index 40d9b59b..b2a78752 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 cfc69f00..59895d2b 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 c2c67a9e..78cd04da 100644 --- a/src/rdf4cpp/util/Int128.hpp +++ b/src/rdf4cpp/util/Int128.hpp @@ -252,10 +252,25 @@ namespace rdf4cpp { using t = boost::multiprecision::number>; }; template - requires IntegralExt && IntegralExt + 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::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; } @@ -277,9 +292,7 @@ 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; @@ -292,9 +305,21 @@ namespace rdf4cpp { if constexpr (m == OverflowMode::Checked) { try { result = static_cast>(f); - } catch (std::overflow_error const &) { + } catch (std::runtime_error const &) { return true; - } catch (std::range_error const &) { + } + 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_checked &result) noexcept { + if constexpr (m == OverflowMode::Checked) { + try { + result = static_cast>(f); + } catch (std::runtime_error const &) { return true; } return false; @@ -302,6 +327,18 @@ namespace rdf4cpp { 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 @@ -333,6 +370,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 df722d7e..beda1797 100644 --- a/tests/datatype/tests_time_types.cpp +++ b/tests/datatype/tests_time_types.cpp @@ -176,12 +176,20 @@ 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::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); + + CHECK(!rdf4cpp::util::deconstruct_timepoint(rdf4cpp::TimePoint::max()).has_value()); + CHECK(!rdf4cpp::util::deconstruct_timepoint(rdf4cpp::TimePoint::min()).has_value()); } TEST_CASE("datatype gYear") { @@ -445,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") { @@ -501,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") { @@ -671,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") { diff --git a/tests/util/tests_BigDecimal.cpp b/tests/util/tests_BigDecimal.cpp index d9d89ad0..82c33c38 100644 --- a/tests/util/tests_BigDecimal.cpp +++ b/tests/util/tests_BigDecimal.cpp @@ -167,6 +167,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); } constexpr __int128 Make128(int64_t h, int64_t l) {