Module temporal

A hybrid format derived from RFC 3339, RFC 9557 and ISO 8601.

This module provides an implementation of the Temporal ISO 8601 grammar. The API is spread out over parsers and printers for datetimes and spans.

Note that for most use cases, you should be using the corresponding Display or FromStr trait implementations for printing and parsing respectively. This module provides a "lower level" API for configuring the behavior of printing and parsing, including the ability to parse from byte strings (i.e., &[u8]).

Date and time format

The specific format supported depends on what kind of type you're trying to parse into. Here are some examples to give a general idea:

Smaller types can generally be parsed from strings representing a bigger type. For example, a civil::Date can be parsed from 2020-08-21T02:21:58.

As mentioned above, the datetime format supported by Jiff is a hybrid of the "best" parts of RFC 3339, RFC 9557 and ISO 8601. Generally speaking, RFC 3339 and RFC 9557 are supported in their entirety, but not all of ISO 8601 is. For example, 2024-06-16T10.5 is a valid ISO 8601 datetime, but isn't supported by Jiff. (Only fractional seconds are supported.)

Some additional details worth noting:

The complete datetime format supported is described by the Temporal ISO 8601 grammar.

Span format

To a first approximation, the span format supported roughly corresponds to this regular expression:

P(\d+y)?(\d+m)?(\d+w)?(\d+d)?(T(\d+h)?(\d+m)?(\d+s)?)?

But there are some details not easily captured by a simple regular expression:

This is, roughly speaking, a subset of what ISO 8601 specifies. It isn't strictly speaking a subset since Jiff (like Temporal) permits week units to be mixed with other units.

Here are some examples:

use jiff::{Span, ToSpan};

let spans = [
    ("P40D", 40.days()),
    ("P1y1d", 1.year().days(1)),
    ("P3dT4h59m", 3.days().hours(4).minutes(59)),
    ("PT2H30M", 2.hours().minutes(30)),
    ("P1m", 1.month()),
    ("P1w", 1.week()),
    ("P1w4d", 1.week().days(4)),
    ("PT1m", 1.minute()),
    ("PT0.0021s", 2.milliseconds().microseconds(100)),
    ("PT0s", 0.seconds()),
    ("P0d", 0.seconds()),
    (
        "P1y1m1dT1h1m1.1s",
        1.year().months(1).days(1).hours(1).minutes(1).seconds(1).milliseconds(100),
    ),
];
for (string, span) in spans {
    let parsed: Span = string.parse()?;
    assert_eq!(
        span.fieldwise(),
        parsed.fieldwise(),
        "result of parsing {string:?}",
    );
}

# Ok::<(), Box<dyn std::error::Error>>(())

One can also parse ISO 8601 durations into a SignedDuration, but units are limited to hours or smaller:

use jiff::SignedDuration;

let durations = [
    ("PT2H30M", SignedDuration::from_secs(2 * 60 * 60 + 30 * 60)),
    ("PT2.5h", SignedDuration::from_secs(2 * 60 * 60 + 30 * 60)),
    ("PT1m", SignedDuration::from_mins(1)),
    ("PT1.5m", SignedDuration::from_secs(90)),
    ("PT0.0021s", SignedDuration::new(0, 2_100_000)),
    ("PT0s", SignedDuration::ZERO),
    ("PT0.000000001s", SignedDuration::from_nanos(1)),
];
for (string, duration) in durations {
    let parsed: SignedDuration = string.parse()?;
    assert_eq!(duration, parsed, "result of parsing {string:?}");
}

# Ok::<(), Box<dyn std::error::Error>>(())

The complete span format supported is described by the Temporal ISO 8601 grammar.

Differences with Temporal

Jiff implements Temporal's grammar pretty closely, but there are a few differences at the time of writing. It is a specific goal that all differences should be rooted in what Jiff itself supports, and not in the grammar itself.

There is some more background on Temporal's format available.

Structs