# std::chrono Support

Glaze provides first-class support for `std::chrono` types, enabling seamless serialization and deserialization of durations and time points.

## Supported Types

### Durations

All `std::chrono::duration` types are supported and serialize as their numeric count value (the `rep`, expressed in the duration's own period). This representation is shared by every Glaze format (JSON, BEVE, CBOR, MsgPack, BSON, TOML, YAML, CSV, JSONB, EETF), so the same type round-trips between any of them interchangeably:

```cpp
std::chrono::milliseconds ms{12345};
std::string json = glz::write_json(ms).value();  // "12345"

std::chrono::milliseconds parsed{};
glz::read_json(parsed, json);  // parsed.count() == 12345

// The same value over a binary format: BEVE encodes the duration byte-for-byte
// like its underlying integer rep.
std::string beve{};
glz::write_beve(ms, beve);
std::chrono::milliseconds from_beve{};
glz::read_beve(from_beve, beve);  // from_beve == ms
```

> In BSON, whose document root cannot be a bare scalar, a duration is supported as a struct/map field (encoded as the rep's native BSON number type) rather than as a top-level value.

This works with any duration type, including custom periods:

```cpp
std::chrono::seconds s{3600};           // "3600"
std::chrono::nanoseconds ns{123456789}; // "123456789"
std::chrono::hours h{24};               // "24"

// Custom period (e.g., 60fps frames)
using frames = std::chrono::duration<int64_t, std::ratio<1, 60>>;
frames f{120};  // "120"

// Floating-point rep
std::chrono::duration<double, std::milli> ms{123.456};  // "123.456"
```

### System Clock Time Points

`std::chrono::system_clock::time_point` serializes as an ISO 8601 string in UTC:

```cpp
auto now = std::chrono::system_clock::now();
std::string json = glz::write_json(now).value();
// "2024-12-13T15:30:45.123456789Z"
```

The precision of fractional seconds matches the time point's duration type:

```cpp
using namespace std::chrono;

sys_time<seconds> tp_sec{...};      // "2024-12-13T15:30:45Z"
sys_time<milliseconds> tp_ms{...};  // "2024-12-13T15:30:45.123Z"
sys_time<microseconds> tp_us{...};  // "2024-12-13T15:30:45.123456Z"
sys_time<nanoseconds> tp_ns{...};   // "2024-12-13T15:30:45.123456789Z"
```

Parsing supports multiple ISO 8601 formats:

```cpp
std::chrono::system_clock::time_point tp;

// UTC with Z suffix
glz::read_json(tp, "\"2024-12-13T15:30:45Z\"");

// With timezone offset
glz::read_json(tp, "\"2024-12-13T15:30:45+05:00\"");
glz::read_json(tp, "\"2024-12-13T15:30:45-08:00\"");

// With fractional seconds
glz::read_json(tp, "\"2024-12-13T15:30:45.123456789Z\"");
```

### Date-Only Time Points (`sys_days`)

> **Breaking change:** `std::chrono::sys_days` previously serialized as the full ISO 8601 datetime `"YYYY-MM-DDT00:00:00Z"`. It now serializes as the date-only string `"YYYY-MM-DD"`. The reader accepts both forms, so consumers upgrading Glaze on the read side stay compatible; producers that emit the new shape break older readers that strictly require a time component.

Time points whose duration period is exactly `days` (`std::chrono::sys_days`, i.e. `std::chrono::sys_time<std::chrono::days>`) serialize as date-only strings, since the time-of-day at that precision is always zero:

```cpp
using namespace std::chrono;

sys_days d = year{2024} / month{6} / day{15};
std::string json = glz::write_json(d).value();  // "2024-06-15"

sys_days parsed{};
glz::read_json(parsed, "\"2024-06-15\"");
```

On read, both the bare date form and a full ISO 8601 datetime are accepted; a full datetime is floored to days precision **after** any timezone offset is applied to UTC:

```cpp
sys_days parsed{};
glz::read_json(parsed, "\"2024-06-15T15:30:45Z\"");      // parsed == 2024-06-15
glz::read_json(parsed, "\"2024-06-14T20:00:00-08:00\""); // parsed == 2024-06-15 (UTC)
```

Footgun: a sender that thinks "June 14 in California" and emits `"2024-06-14T23:00:00-08:00"` (= `2024-06-15 07:00:00Z`) gets `2024-06-15` back, not `2024-06-14`. The date is the UTC date of the instant, not the local date in the offset. If your protocol means "the local civil date," send a bare `"YYYY-MM-DD"` instead of a datetime with offset.

The reader rejects negative-year strings (`"-0001-01-01"`) and 5+ digit years for symmetry with the writer's `[0000, 9999]` range.

### `std::chrono::year_month_day`

`std::chrono::year_month_day` serializes as a `"YYYY-MM-DD"` JSON string:

```cpp
using namespace std::chrono;

year_month_day ymd{year{2024}, month{6}, day{15}};
std::string json = glz::write_json(ymd).value();  // "2024-06-15"

year_month_day parsed{};
glz::read_json(parsed, json);
```

Unlike `sys_days`, `year_month_day` accepts only the bare date form on read; embed a `sys_days` field if you also need to accept full datetimes.

Years must lie within RFC 3339's four-digit range `[0000, 9999]` on write; values outside that range fail to serialize with `error_code::constraint_violated` rather than emit wrap-around digits.

### Steady Clock Time Points

`std::chrono::steady_clock::time_point` serializes as a numeric count (time since epoch), since steady clock's epoch is implementation-defined and not meaningful as a calendar time:

```cpp
auto start = std::chrono::steady_clock::now();
std::string json = glz::write_json(start).value();  // numeric count

std::chrono::steady_clock::time_point parsed;
glz::read_json(parsed, json);
// Exact roundtrip - no precision loss
```

### High Resolution Clock Time Points

`std::chrono::high_resolution_clock` is not a distinct clock in any mainstream standard library. It is an alias, and **which clock it aliases differs by implementation**:

| Standard library | `high_resolution_clock` is | `high_resolution_clock::time_point` behaves as |
|------------------|----------------------------|------------------------------------------------|
| libc++ (Clang), MSVC STL | `steady_clock` | numeric count, supported by every format |
| libstdc++ (GCC) | `system_clock` | ISO 8601 string, only in the formats that support `system_clock::time_point` |

Because the alias makes the two types *identical*, Glaze cannot give them different behavior. The practical consequence is that a type containing an `hrc::time_point` can compile on one platform and fail on another: under libstdc++ it is a `system_clock::time_point`, which BEVE, CSV, and JSONB do not support (see [Format Support](#format-support)).

Prefer naming the clock you actually mean:

```cpp
std::chrono::steady_clock::time_point t;  // portable: numeric count in every format
std::chrono::system_clock::time_point w;  // portable: wall clock, format support varies
```

## Epoch Time Wrappers

For APIs that expect Unix timestamps (numeric epoch time) instead of ISO 8601 strings, Glaze provides `epoch_time<Duration>` wrappers:

```cpp
#include "glaze/glaze.hpp"

// Convenience aliases
glz::epoch_seconds  // Unix timestamp in seconds
glz::epoch_millis   // Unix timestamp in milliseconds
glz::epoch_micros   // Unix timestamp in microseconds
glz::epoch_nanos    // Unix timestamp in nanoseconds
```

### Usage

```cpp
glz::epoch_seconds ts;
ts.value = std::chrono::system_clock::now();

std::string json = glz::write_json(ts).value();  // "1702481400"

glz::epoch_seconds parsed;
glz::read_json(parsed, json);
```

### Implicit Conversion

`epoch_time` wrappers implicitly convert to/from `system_clock::time_point`:

```cpp
glz::epoch_millis ts = std::chrono::system_clock::now();
std::chrono::system_clock::time_point tp = ts;
```

### In Structs

Use epoch wrappers when your API requires numeric timestamps:

```cpp
struct ApiResponse {
    std::string data;
    glz::epoch_millis created_at;  // Serializes as: 1702481400123
    glz::epoch_millis updated_at;
};

struct Event {
    std::string name;
    std::chrono::system_clock::time_point timestamp;  // ISO 8601 string
    std::chrono::milliseconds duration;               // Numeric count
};
```

## Per-Field Format Customization

The clock type alone decides the default representation (ISO 8601 for `system_clock`, numeric count for `steady_clock`, and so on). When a single field needs a different shape, the per-field wrappers below decouple the *format* from the *type*. They are applied inside `glz::object(...)` within a `glz::meta<T>` specialization and require `#include "glaze/chrono.hpp"`.

### `glz::date_format` — custom strftime-subset pattern

`glz::date_format(&T::member, "pattern")` serializes a `system_clock` time point (or a `year_month_day`) using a `strftime`-style pattern instead of ISO 8601:

```cpp
#include "glaze/chrono.hpp"

struct Event {
    std::chrono::system_clock::time_point start{};
    std::chrono::sys_days day{};
};

template <>
struct glz::meta<Event> {
    using T = Event;
    static constexpr auto value = glz::object(
        "start", glz::date_format(&T::start, "%Y-%m-%d %H:%M:%S"),  // "2026-06-18 12:34:56"
        "day",   glz::date_format(&T::day,   "%Y/%m/%d"));          // "2026/06/18"
};
```

The wrapper is bidirectional: the same pattern parses the value back on read.

```cpp
Event e{};
e.start = std::chrono::sys_days{std::chrono::year{2026} / 6 / 18} + std::chrono::hours{12} +
          std::chrono::minutes{34} + std::chrono::seconds{56};

std::string json = glz::write_json(e).value();   // {"start":"2026-06-18 12:34:56","day":"..."}

Event parsed{};
auto ec = glz::read_json(parsed, json);           // parsed.start == e.start
```

The member pointer and pattern are passed as ordinary arguments (not template parameters), so fields that share a member type but differ in format do not each spin up a fresh set of template instantiations. The pattern is still a compile-time literal and is validated at compile time.

Supported conversion specifiers (locale-independent by design):

| Token | Meaning              | Token | Meaning                |
|-------|----------------------|-------|------------------------|
| `%Y`  | year (4 digits)      | `%H`  | hour, 24-hour (2)      |
| `%m`  | month (2 digits)     | `%M`  | minute (2 digits)      |
| `%d`  | day (2 digits)       | `%S`  | second (2 digits)      |
| `%F`  | `%Y-%m-%d`           | `%T`  | `%H:%M:%S`             |
| `%%`  | literal `%`          |       |                        |

The pattern is validated at compile time:

- An unsupported token (e.g. `%A`, `%j`, `%z`) is a hard compile error.
- The pattern must contain a full calendar date (`%Y %m %d`, or `%F`); a time-only pattern cannot reconstruct an absolute time point and is rejected.
- A `year_month_day` field rejects time tokens (`%H %M %S %T`).

Notes and limitations (MVP scope):

- **Times are treated as UTC wall-clock.** There is no timezone token; the decomposed fields are UTC, matching the ISO 8601 writer.
- **The four-digit-year range still applies.** As with the ISO 8601 writers, a year outside `[0000, 9999]` (or a non-`ok()` `year_month_day`) fails to serialize with `error_code::constraint_violated` rather than emitting wrap-around digits.
- **`%S` writes integer seconds only.** Sub-second precision is truncated on write (the format you typed has nowhere to put it), so round-tripping a finer-than-seconds value through a `%S` pattern is lossy by design. Use the default ISO 8601 representation when you need sub-second fidelity. Explicit-width fraction tokens (`%3S`/`%6S`/`%9S`) are a planned extension.
- **Text/JSON-family backends only.** `date_format` is a textual wrapper that routes through the JSON serializer, so it also works for the JSON-family text outputs (NDJSON, stencil/mustache). Serializing a field that uses it to a backend with its own native encoding (BEVE, CBOR, MsgPack, BSON, or TOML) is a compile error (an undefined `glz::to<Format, …>`) rather than a silent miscoding.

### `glz::epoch_count` — per-field Unix timestamp

`glz::epoch_count<Duration>(&T::member)` serializes a `system_clock` time point as a numeric Unix timestamp in units of `Duration`. It is the per-field counterpart to the `glz::epoch_time` storage wrapper, letting one field be an epoch count while others stay ISO 8601:

```cpp
#include "glaze/chrono.hpp"

struct Reading {
    std::chrono::system_clock::time_point observed{};   // ISO 8601 (default)
    std::chrono::sys_time<std::chrono::milliseconds> logged{};
};

template <>
struct glz::meta<Reading> {
    using T = Reading;
    static constexpr auto value = glz::object(
        "observed", &T::observed,                                       // "2026-06-18T12:34:56Z"
        "logged",   glz::epoch_count<std::chrono::milliseconds>(&T::logged));  // 1781786096789
};
```

Unlike `glz::epoch_time<Duration>` (a storage type you declare your member as), `glz::epoch_count` wraps an ordinary `system_clock` time-point member in place, so you can keep the field's native type. Like `date_format`, it routes through the JSON serializer (so it also works for NDJSON and stencil output) and is a compile error under the native-encoding backends (BEVE, CBOR, MsgPack, BSON, TOML).

The count is read as a JSON integer: exponent form (`1e3` → `1000`) is accepted, while fractional values (`1.5`) and quoted numbers (`"1500"`) are rejected with `error_code::parse_error`.

## Complete Example

```cpp
#include "glaze/glaze.hpp"
#include <chrono>

struct LogEntry {
    std::string message;
    std::chrono::system_clock::time_point timestamp;
    std::chrono::microseconds processing_time;
};

struct MetricsReport {
    glz::epoch_millis generated_at;
    std::chrono::steady_clock::time_point uptime_start;
    std::vector<LogEntry> entries;
};

int main() {
    MetricsReport report;
    report.generated_at = std::chrono::system_clock::now();
    report.uptime_start = std::chrono::steady_clock::now();
    report.entries.push_back({
        "Request processed",
        std::chrono::system_clock::now(),
        std::chrono::microseconds{1234}
    });

    // Serialize
    std::string json = glz::write_json(report).value();

    // Deserialize
    MetricsReport parsed;
    glz::read_json(parsed, json);
}
```

Output:

```json
{
    "generated_at": 1702481400123,
    "uptime_start": 123456789012345,
    "entries": [
        {
            "message": "Request processed",
            "timestamp": "2024-12-13T15:30:45.123456Z",
            "processing_time": 1234
        }
    ]
}
```

## Summary Table

| Type | JSON Format | Example |
|------|-------------|---------|
| `std::chrono::duration<Rep, Period>` | Numeric count | `12345` |
| `std::chrono::system_clock::time_point` | ISO 8601 string | `"2024-12-13T15:30:45Z"` |
| `std::chrono::sys_days` (`sys_time<days>`) | Date string | `"2024-12-13"` |
| `std::chrono::year_month_day` | Date string | `"2024-12-13"` |
| `std::chrono::steady_clock::time_point` | Numeric count | `123456789012345` |
| `glz::epoch_seconds` | Unix seconds | `1702481400` |
| `glz::epoch_millis` | Unix milliseconds | `1702481400123` |
| `glz::epoch_micros` | Unix microseconds | `1702481400123456` |
| `glz::epoch_nanos` | Unix nanoseconds | `1702481400123456789` |

## Format Support

Durations and count-based time points use one representation shared by every format, so they interchange freely: the same meta schema can target JSON and BEVE. Calendar types are format-specific, because a wall-clock instant has no single natural encoding across text and binary formats.

| Type | JSON | BEVE | CBOR | MsgPack | BSON | TOML | YAML | CSV | JSONB |
|------|:----:|:----:|:----:|:-------:|:----:|:----:|:----:|:---:|:-----:|
| `std::chrono::duration<Rep, Period>` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `steady_clock::time_point` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `system_clock::time_point` | ✅ | — | ✅ | ✅ | ✅ | ✅ | ✅ | — | — |
| `sys_days` (`sys_time<days>`) | ✅ | — | ✅ | — | — | ✅ | ✅ | — | — |
| `year_month_day` | ✅ | — | — | — | — | ✅ | ✅ | — | — |
| `glz::epoch_time<D>` | ✅ | — | ✅ | — | — | — | — | — | — |

`high_resolution_clock::time_point` is not listed because it is an alias: it follows the `steady_clock` row under libc++ and MSVC, and the `system_clock` row under libstdc++. See [High Resolution Clock Time Points](#high-resolution-clock-time-points).

An unsupported combination is a compile-time error (`write_supported` / `read_supported` report `false`), never a silent fallback. To carry a wall-clock instant through a format that lacks calendar support, store it as a duration since a known epoch (for example a `std::chrono::milliseconds` field), which every format encodes identically.

## Notes

- **Thread Safety**: All chrono serialization is fully thread-safe with no static buffers
- **Precision**: Roundtrip serialization preserves full precision for all supported types
- **Validation**: Invalid ISO 8601 strings return `glz::error_code::parse_error`
- **Timezone Handling**: Time points are always converted to/from UTC; timezone offsets in input are properly applied
- **Leap Seconds**: Not supported (`23:59:60` will return a parse error). `std::chrono::system_clock` uses Unix time which does not account for leap seconds

## TOML Datetime Support

TOML has native datetime types (not quoted strings). When using `glz::write_toml` / `glz::read_toml`, chrono types use TOML's native datetime format:

- `system_clock::time_point` → TOML Offset Date-Time (`2024-06-15T10:30:45Z`)
- `year_month_day` → TOML Local Date (`2024-06-15`)
- `hh_mm_ss<Duration>` → TOML Local Time (`10:30:45.123`)
- Durations and other time points → Numeric values

> The per-field `glz::date_format` / `glz::epoch_count` wrappers are JSON-text-only and do **not** compile under `glz::write_toml`. Native (unwrapped) chrono members serialize as TOML datetimes as shown above; only the per-field format wrappers are restricted.

See [TOML Documentation](./toml.md#datetime-support) for full details.

## CBOR Datetime Support

CBOR has standard semantic tags for dates and times defined in RFC 8949. When using `glz::write_cbor` / `glz::read_cbor`, chrono types use these tags:

- `system_clock::time_point` → tag 0 + RFC 3339 date/time text string (`2024-06-15T10:30:45Z`)
- `epoch_seconds` → tag 1 + integer seconds since Unix epoch
- `epoch_millis` / `epoch_micros` / `epoch_nanos` → tag 1 + float64 seconds since epoch
- `std::chrono::duration` → bare integer count in the duration's native units
- `std::chrono::steady_clock::time_point` → bare integer count (no tag)

Sub-second `epoch_time` precision is carried via float64 because RFC 8949 §3.4.2 forbids nested tags (including tag 4 decimal fractions) as tag 1 content. `epoch_millis` and `epoch_micros` round-trip losslessly for realistic epochs; `epoch_nanos` loses ~100 ns of precision on modern timestamps once the nanosecond count exceeds 2^53.

See [CBOR Documentation](./cbor.md#date-and-time-stdchrono) for wire-format tables, decoder leniency rules, and safety guarantees.
