jiff::fmt::serde

Module span

Source
Expand description

Convenience routines for serializing Span values.

These convenience routines exist because the Serialize implementation for Span always uses the ISO 8601 duration format. These routines provide a way to use the “friendly” format.

Only serialization routines are provided because a Span’s Deserialize implementation automatically handles both the ISO 8601 duration format and the “friendly” format.

§Advice

The Serialize implementation uses ISO 8601 because it is a widely accepted interchange format for communicating durations. If you need to inter-operate with other systems, it is almost certainly the correct choice.

The “friendly” format does not adhere to any universal specified format. However, it is perhaps easier to read, and crucially, unambiguously represents all components of a Span faithfully. (In contrast, the ISO 8601 format always normalizes sub-second durations into fractional seconds, which means durations like 1100ms and 1s100ms are alwasys considered equivalent.)

§Available routines

A SpanPrinter has a lot of different configuration options. The convenience routines provided by this module only cover a small space of those options since it isn’t feasible to provide a convenience routine for every possible set of configuration options.

While more convenience routines could be added (please file an issue), only the most common or popular such routines can be feasibly added. So in the case where a convenience routine isn’t available for the configuration you want, you can very easily define your own serialize_with routine.

The recommended approach is to define a function and a type that implements the std::fmt::Display trait. This way, if a serializer can efficiently support Display implementations, then an allocation can be avoided.

use jiff::{Span, ToSpan};

#[derive(Debug, serde::Deserialize, serde::Serialize)]
struct Data {
    #[serde(serialize_with = "custom_friendly")]
    duration: Span,
}

let json = r#"{"duration": "1 year 2 months 36 hours 1100ms"}"#;
let got: Data = serde_json::from_str(&json).unwrap();
assert_eq!(
    got.duration,
    1.year().months(2).hours(36).milliseconds(1100).fieldwise(),
);

let expected = r#"{"duration":"1 year, 2 months, 36:00:01.100"}"#;
assert_eq!(serde_json::to_string(&got).unwrap(), expected);

fn custom_friendly<S: serde::Serializer>(
    span: &Span,
    se: S,
) -> Result<S::Ok, S::Error> {
    struct Custom<'a>(&'a Span);

    impl<'a> std::fmt::Display for Custom<'a> {
        fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
            use jiff::fmt::{
                friendly::{Designator, Spacing, SpanPrinter},
                StdFmtWrite,
            };

            static PRINTER: SpanPrinter = SpanPrinter::new()
                .designator(Designator::Verbose)
                .comma_after_designator(true)
                .spacing(Spacing::BetweenUnitsAndDesignators)
                .hours_minutes_seconds(true)
                .precision(Some(3));

            PRINTER
                .print_span(self.0, StdFmtWrite(f))
                .map_err(|_| core::fmt::Error)
        }
    }

    se.collect_str(&Custom(span))
}

Recall from above that you only need a custom serialization routine for this. Namely, deserialization automatically supports parsing all configuration options for serialization unconditionally.

Modules§