// Glaze Library // For the license information refer to glaze.hpp #pragma once #include #include #include #include #include "glaze/core/context.hpp" #include "glaze/core/traits.hpp" namespace glz { // Concept for std::chrono::duration types template concept is_duration = requires { typename T::rep; typename T::period; requires std::is_same_v, std::chrono::duration>; }; // Concept for std::chrono::time_point types template concept is_time_point = requires { typename T::clock; typename T::duration; requires std::is_same_v, std::chrono::time_point>; }; // Concept for system_clock time_points (serialize as ISO 8601 string) template concept is_system_time_point = is_time_point && std::is_same_v::clock, std::chrono::system_clock>; // Concept for steady_clock time_points (serialize as numeric count) template concept is_steady_time_point = is_time_point && std::is_same_v::clock, std::chrono::steady_clock>; // Detect if high_resolution_clock is a true alias or a distinct type inline constexpr bool hrc_is_system = std::is_same_v; inline constexpr bool hrc_is_steady = std::is_same_v; // Concept for high_resolution_clock when it's a distinct type (rare) template concept is_high_res_time_point = is_time_point && std::is_same_v::clock, std::chrono::high_resolution_clock> && !hrc_is_system && !hrc_is_steady; // ============================================ // epoch_time wrapper for Unix timestamp format // ============================================ // Wrapper that controls serialization format, not storage // Internally stores system_clock::time_point with native precision // Template parameter specifies the OUTPUT format precision template struct epoch_time { std::chrono::system_clock::time_point value{}; // Implicit conversions for ergonomic use epoch_time() = default; epoch_time(std::chrono::system_clock::time_point tp) : value(tp) {} operator std::chrono::system_clock::time_point() const { return value; } // Comparison operators bool operator==(const epoch_time&) const = default; auto operator<=>(const epoch_time&) const = default; }; // Convenience aliases - name indicates OUTPUT format using epoch_seconds = epoch_time; using epoch_millis = epoch_time; using epoch_micros = epoch_time; using epoch_nanos = epoch_time; // Concept to detect epoch_time wrapper template concept is_epoch_time = requires(T t) { { t.value } -> std::convertible_to; }; // ============================================ // TOML Local Date/Time support // ============================================ // Concept for std::chrono::year_month_day (TOML Local Date) template concept is_year_month_day = std::is_same_v, std::chrono::year_month_day>; // Concept for std::chrono::hh_mm_ss (TOML Local Time) template concept is_hh_mm_ss = requires { typename std::remove_cvref_t::precision; requires requires(T t) { { t.hours() } -> std::convertible_to; { t.minutes() } -> std::convertible_to; { t.seconds() } -> std::convertible_to; { t.subseconds() }; { t.is_negative() } -> std::convertible_to; }; }; // Register chrono types as having specified Glaze serialization // This prevents P2996 automatic reflection from creating ambiguous specializations template struct specified> : std::true_type {}; template struct specified> : std::true_type {}; template <> struct specified : std::true_type {}; template struct specified> : std::true_type {}; template struct specified> : std::true_type {}; namespace chrono_detail { // Parse `count` decimal digits starting at s[start]. Returns -1 if any character // is not a digit. Precondition: `count > 0` and `count` digits are readable at // s[start..start+count). `count == 0` returns 0 rather than an error. inline int parse_digits(const char* s, size_t start, size_t count) noexcept { int val = 0; for (size_t i = 0; i < count; ++i) { const char c = s[start + i]; if (c < '0' || c > '9') return -1; val = val * 10 + (c - '0'); } return val; } // Write `val` as exactly N zero-padded decimal digits to b starting at ix, advancing ix. // Caller must ensure b has at least N bytes of capacity at ix. template inline void write_digits(B& b, auto& ix, uint64_t val) noexcept { for (size_t i = N; i > 0; --i) { b[ix + i - 1] = static_cast('0' + val % 10); val /= 10; } ix += N; } // Parse exactly "YYYY-MM-DD" (10 chars) into a year_month_day. // On failure, sets ec to parse_error and leaves ymd unchanged. // The 10-char length check implicitly caps the year at 9999, which keeps the // writer's [0000, 9999] range symmetric on read; loosening the size check would // break that symmetry, so it's worth holding fixed. inline void parse_ymd(std::string_view str, std::chrono::year_month_day& ymd, error_code& ec) noexcept { if (str.size() != 10) { ec = error_code::parse_error; return; } const char* s = str.data(); const int yr = parse_digits(s, 0, 4); const int mo = parse_digits(s, 5, 2); const int dy = parse_digits(s, 8, 2); if (yr < 0 || mo < 0 || dy < 0 || s[4] != '-' || s[7] != '-') { ec = error_code::parse_error; return; } // Fast-fail on obviously out-of-range components before constructing year_month_day. // year_month_day::ok() below catches the remaining cases (e.g. Feb 30, leap years). if (mo < 1 || mo > 12 || dy < 1 || dy > 31) { ec = error_code::parse_error; return; } using namespace std::chrono; // yr is in [0, 9999] (4-digit parse, validated non-negative above), which fits // inside std::chrono::year's [-32767, 32767] range, so the int -> year conversion // is in range. const auto candidate = year_month_day{year{yr}, month{static_cast(mo)}, day{static_cast(dy)}}; if (!candidate.ok()) { ec = error_code::parse_error; return; } ymd = candidate; } // Parse an RFC 3339 / ISO 8601 date-time string into a system_clock time_point. // On failure, sets ec to parse_error and leaves value unchanged. template inline void parse_iso8601(std::string_view str, TP& value, error_code& ec) noexcept { // Minimum: YYYY-MM-DDTHH:MM:SS = 19 chars (timezone optional, defaults to UTC) if (str.size() < 19) { ec = error_code::parse_error; return; } const char* s = str.data(); const auto n = str.size(); const int yr = parse_digits(s, 0, 4); const int mo = parse_digits(s, 5, 2); const int dy = parse_digits(s, 8, 2); const int hr = parse_digits(s, 11, 2); const int mi = parse_digits(s, 14, 2); const int sc = parse_digits(s, 17, 2); if (yr < 0 || mo < 0 || dy < 0 || hr < 0 || mi < 0 || sc < 0 || s[4] != '-' || s[7] != '-' || s[10] != 'T' || s[13] != ':' || s[16] != ':') { ec = error_code::parse_error; return; } if (mo < 1 || mo > 12 || dy < 1 || dy > 31 || hr > 23 || mi > 59 || sc > 59) { ec = error_code::parse_error; return; } size_t pos = 19; int64_t subsec_nanos = 0; if (pos < n && s[pos] == '.') { ++pos; int64_t frac = 0; int digits = 0; while (pos < n && s[pos] >= '0' && s[pos] <= '9') { if (digits < 9) { frac = frac * 10 + (s[pos] - '0'); ++digits; } ++pos; } if (digits == 0) { ec = error_code::parse_error; return; } static constexpr int64_t scale[] = {1000000000, 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1}; subsec_nanos = frac * scale[digits]; } int tz_offset_seconds = 0; if (pos < n) { if (s[pos] == 'Z') { ++pos; } else if (s[pos] == '+' || s[pos] == '-') { // UTC = local - offset. "+05:30" means local is 5h30 ahead of UTC, so we // subtract 5h30 (multiplier -1); "-08:00" means local is behind UTC, so we add // 8h (multiplier +1). const int utc_adjustment = (s[pos] == '+') ? -1 : 1; ++pos; if (pos + 2 > n) { ec = error_code::parse_error; return; } const int tz_hour = parse_digits(s, pos, 2); if (tz_hour < 0 || tz_hour > 23) { ec = error_code::parse_error; return; } pos += 2; int tz_min = 0; bool has_tz_colon = false; if (pos < n && s[pos] == ':') { ++pos; has_tz_colon = true; } if (pos + 2 <= n) { const int m = parse_digits(s, pos, 2); if (m < 0 || m > 59) { ec = error_code::parse_error; return; } tz_min = m; pos += 2; } else if (has_tz_colon) { ec = error_code::parse_error; return; } tz_offset_seconds = utc_adjustment * (tz_hour * 3600 + tz_min * 60); } } if (pos != n) { ec = error_code::parse_error; return; } using namespace std::chrono; const auto ymd = year_month_day{year{yr}, month{static_cast(mo)}, day{static_cast(dy)}}; if (!ymd.ok()) { ec = error_code::parse_error; return; } const auto tp = sys_days{ymd} + hours{hr} + minutes{mi} + seconds{sc} + seconds{tz_offset_seconds} + nanoseconds{subsec_nanos}; using Duration = typename std::remove_cvref_t::duration; value = time_point_cast(tp); } } }