.. _program_listing_file_src_rdf4cpp_Timezone.hpp: Program Listing for File Timezone.hpp ===================================== |exhale_lsh| :ref:`Return to documentation for file ` (``src/rdf4cpp/Timezone.hpp``) .. |exhale_lsh| unicode:: U+021B0 .. UPWARDS ARROW WITH TIP LEFTWARDS .. code-block:: cpp #ifndef RDF4CPP_TIMEZONE_HPP #define RDF4CPP_TIMEZONE_HPP #include #include #include #include #include #include #include #include namespace rdf4cpp { struct Timezone { // heavily inspired by https://howardhinnant.github.io/date/tz.html#Examples std::chrono::minutes offset = std::chrono::minutes{0}; static constexpr const char *begin_tokens = "Z+-"; 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)) { } constexpr explicit Timezone(std::chrono::hours h) noexcept : offset(h) {} constexpr explicit Timezone(std::chrono::minutes h) noexcept : offset(h) {} constexpr auto operator<=>(const Timezone &) const noexcept = default; 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; } 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; } } 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_local(const std::chrono::sys_time &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()}; } const Timezone *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}}; }; }; using OptionalTimezone = std::optional; using Month = std::chrono::month; using Day = std::chrono::day; struct Year { private: int64_t value_; public: explicit constexpr Year(int64_t y = 0) noexcept : value_{y} { } constexpr explicit operator int64_t() const noexcept { return value_; } [[nodiscard]] constexpr bool is_leap() const noexcept(noexcept(value_ % 100)) { return value_ % 4 == 0 && (value_ % 100 != 0 || value_ % 400 == 0); } constexpr auto operator<=>(Year const &) const noexcept = default; friend constexpr Year operator+(Year const &y, std::chrono::years d) noexcept { return Year{y.value_ + d.count()}; } friend constexpr Year operator+(std::chrono::years d, Year const &y) noexcept { return Year{y.value_ + d.count()}; } constexpr Year operator+=(std::chrono::years d) noexcept { *this = *this + d; return *this; } 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_}; } constexpr Year operator-=(std::chrono::years d) noexcept { *this = *this - d; return *this; } 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--() noexcept { *this -= std::chrono::years{1}; return *this; } constexpr Year operator--(int) noexcept { Year r = *this; --(*this); return r; } static constexpr Year max() noexcept { return Year{std::numeric_limits::max()}; } static constexpr Year min() noexcept { return Year{std::numeric_limits::min()}; } }; struct YearMonth { private: Year year_ = Year{0}; Month month_ = Month{1}; static constexpr YearMonth create_normalized(int64_t y, int64_t mo) noexcept { --mo; y += mo / 12; mo %= 12; if (mo < 0) { // fix result of % being in [-11,11] --y; mo += 12; } return YearMonth{Year{y}, std::chrono::month{static_cast(mo+1)}}; } public: constexpr YearMonth() noexcept = default; constexpr YearMonth(Year y, std::chrono::month m) noexcept : year_{y}, month_{m} { } [[nodiscard]] constexpr Year year() const noexcept { return year_; } [[nodiscard]] constexpr Month month() const noexcept { return month_; } constexpr auto operator<=>(YearMonth const &) const noexcept = default; [[nodiscard]] constexpr bool ok() const noexcept { return month_.ok(); } 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_}; } constexpr YearMonth& operator+=(std::chrono::years d) noexcept { *this = *this + d; return *this; } friend constexpr YearMonth operator+(YearMonth const &ym, std::chrono::months d) noexcept { return create_normalized(static_cast(ym.year_), static_cast(ym.month_) + d.count()); } friend constexpr YearMonth operator+(std::chrono::months d, YearMonth const &ym) noexcept { return create_normalized(static_cast(ym.year_), static_cast(ym.month_) + d.count()); } constexpr YearMonth& operator+=(std::chrono::months d) noexcept { *this = *this + d; return *this; } 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()); } friend constexpr std::chrono::months operator-(YearMonth const &a, YearMonth const &b) noexcept { return (a.year_ - b.year_) + (a.month_ - b.month_); } 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; } }; 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}; } public: constexpr YearMonthDay() noexcept = default; 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())) { } [[nodiscard]] constexpr Year year() const noexcept { return year_; } [[nodiscard]] constexpr Month month() const noexcept { return month_; } [[nodiscard]] constexpr Day day() const noexcept { return day_; } [[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 bool ok() const noexcept { return month_.ok() && day_.ok() && day_ <= last_day_in_month(year_, month_); } constexpr auto operator<=>(YearMonthDay const &) const noexcept = default; 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_}; } constexpr YearMonthDay & operator+=(std::chrono::years d) noexcept { *this = *this + d; return *this; } friend constexpr YearMonthDay operator+(YearMonthDay const &d, std::chrono::months m) noexcept { 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 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-(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::months d) noexcept { *this = *this - d; return *this; } }; 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 { // 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 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; } constexpr std::pair deconstruct_timepoint(TimePoint const &tp) { auto days = std::chrono::floor>(tp); return {YearMonthDay{days}, tp - days}; } } // namespace util } // namespace rdf4cpp namespace std::chrono { template<> struct zoned_traits<::rdf4cpp::Timezone> { static ::rdf4cpp::Timezone default_zone() noexcept { return ::rdf4cpp::Timezone{}; } }; } // namespace std::chrono #ifndef DOXYGEN_PARSER template struct dice::hash::dice_hash_overload { static size_t dice_hash(rdf4cpp::Timezone const &x) noexcept { auto off = x.offset.count(); return dice::hash::dice_hash_templates::dice_hash(off); } }; template struct dice::hash::dice_hash_overload { static size_t dice_hash(rdf4cpp::OptionalTimezone const &x) noexcept { auto off = x.has_value() ? x->offset.count() : std::chrono::minutes{std::chrono::hours{15}}.count(); return dice::hash::dice_hash_templates::dice_hash(off); } }; template struct dice::hash::dice_hash_overload { static size_t dice_hash(::boost::multiprecision::cpp_int const &x) noexcept { return dice::hash::dice_hash_templates::dice_hash(::boost::multiprecision::hash_value(x)); } }; template struct dice::hash::dice_hash_overload { static size_t dice_hash(rdf4cpp::Year const &x) noexcept { return dice::hash::dice_hash_templates::dice_hash(static_cast(x)); } }; template struct dice::hash::dice_hash_overload { static size_t dice_hash(rdf4cpp::YearMonthDay const &x) noexcept { return dice::hash::dice_hash_templates::dice_hash(std::make_tuple(x.year(), x.month(), x.day())); } }; template struct dice::hash::dice_hash_overload { static size_t dice_hash(rdf4cpp::YearMonth const &x) noexcept { return dice::hash::dice_hash_templates::dice_hash(std::make_tuple(x.year(), x.month())); } }; template<> struct std::formatter : std::formatter { inline auto format(rdf4cpp::Year const &p, format_context &ctx) const { return std::format_to(ctx.out(), "{:0{}}", static_cast(p), static_cast(p) < 0 ? 5 : 4); } }; template<> struct std::formatter : std::formatter { inline auto format(rdf4cpp::YearMonthDay const &p, format_context &ctx) const { return std::format_to(ctx.out(), "{}-{:%m}-{:%d}", p.year(), p.month(), p.day()); } }; template<> struct std::formatter : std::formatter { inline auto format(rdf4cpp::YearMonth const &p, format_context &ctx) const { return std::format_to(ctx.out(), "{}-{:%m}", p.year(), p.month()); } }; 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); return std::format_to(ctx.out(), "{}T{:%H:%M:%S}", date, std::chrono::hh_mm_ss{std::chrono::duration_cast(time)}); } }; template<> struct std::formatter : std::formatter { inline auto format(rdf4cpp::ZonedTime const &p, format_context &ctx) const { return std::format_to(ctx.out(), "{}{}", p.get_local_time(), p.get_time_zone().to_canonical_string()); } }; namespace rdf4cpp { inline std::ostream &operator<<(std::ostream &os, rdf4cpp::Year const &value) { std::format_to(std::ostream_iterator{os}, "{}", value); return os; } inline std::ostream &operator<<(std::ostream &os, rdf4cpp::YearMonthDay const &value) { std::format_to(std::ostream_iterator{os}, "{}", value); return os; } inline std::ostream &operator<<(std::ostream &os, rdf4cpp::YearMonth const &value) { std::format_to(std::ostream_iterator{os}, "{}", value); return os; } } #endif #endif //RDF4CPP_TIMEZONE_HPP