Module unsigned_duration

Source
Expand description

Convenience routines for serializing std::time::Duration values.

The principal helpers in this module are the required and optional sub-modules. Either may be used with Serde’s with attribute. Each sub-module provides both a serialization and a deserialization routine for std::time::Duration. Deserialization supports either ISO 8601 or the “friendly” format. Serialization always uses ISO 8601 for reasons of increased interoperability. These helpers are meant to approximate the Deserialize and Serialize trait implementations for Jiff’s own SignedDuration.

If you want to serialize a std::time::Duration using the friendly, then you can make use of the helpers in friendly::compact, also via Serde’s with attribute. These helpers change their serialization to the “friendly” format using compact unit designators. Their deserialization remains the same as the top-level helpers (that is, both ISO 8601 and friendly formatted duration strings are parsed).

Unlike Jiff’s own SignedDuration, deserializing a std::time::Duration does not support negative durations. If a negative duration is found, then deserialization will fail. Moreover, as an unsigned type, a std::time::Duration can represent larger durations than a SignedDuration. This means that a SignedDuration cannot deserialize all valid values of a std::time::Duration. In other words, be careful not to mix them.

§Example: maximally interoperable serialization

This example shows how to achieve Serde integration for std::time::Duration in a way that mirrors SignedDuration. In particular, this supports deserializing ISO 8601 or “friendly” format duration strings. In order to be maximally interoperable, this serializes only in the ISO 8601 format.

use std::time::Duration;

use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Task {
    name: String,
    #[serde(with = "jiff::fmt::serde::unsigned_duration::required")]
    timeout: Duration,
    #[serde(with = "jiff::fmt::serde::unsigned_duration::optional")]
    retry_delay: Option<Duration>,
}

let task = Task {
    name: "Task 1".to_string(),
    // 1 hour 30 minutes
    timeout: Duration::from_secs(60 * 60 + 30 * 60),
    // 2 seconds 500 milliseconds
    retry_delay: Some(Duration::from_millis(2500)),
};

let expected_json = r#"{"name":"Task 1","timeout":"PT1H30M","retry_delay":"PT2.5S"}"#;
let actual_json = serde_json::to_string(&task)?;
assert_eq!(actual_json, expected_json);

let deserialized_task: Task = serde_json::from_str(&actual_json)?;
assert_eq!(deserialized_task, task);

// Example with None for optional field
let task_no_retry = Task {
    name: "Task 2".to_string(),
    timeout: Duration::from_secs(5),
    retry_delay: None,
};
let expected_json_no_retry = r#"{"name":"Task 2","timeout":"PT5S","retry_delay":null}"#;
let actual_json_no_retry = serde_json::to_string(&task_no_retry)?;
assert_eq!(actual_json_no_retry, expected_json_no_retry);

let deserialized_task_no_retry: Task = serde_json::from_str(&actual_json_no_retry)?;
assert_eq!(deserialized_task_no_retry, task_no_retry);

§Example: Round-tripping std::time::Duration

This example demonstrates how to serialize and deserialize a std::time::Duration field using the helpers from this module. In particular, this serializes durations in the more human readable “friendly” format, but can still deserialize ISO 8601 duration strings.

use std::time::Duration;

use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Task {
    name: String,
    #[serde(with = "jiff::fmt::serde::unsigned_duration::friendly::compact::required")]
    timeout: Duration,
    #[serde(with = "jiff::fmt::serde::unsigned_duration::friendly::compact::optional")]
    retry_delay: Option<Duration>,
}

let task = Task {
    name: "Task 1".to_string(),
    // 1 hour 30 minutes
    timeout: Duration::from_secs(60 * 60 + 30 * 60),
    // 2 seconds 500 milliseconds
    retry_delay: Some(Duration::from_millis(2500)),
};

let expected_json = r#"{"name":"Task 1","timeout":"1h 30m","retry_delay":"2s 500ms"}"#;
let actual_json = serde_json::to_string(&task)?;
assert_eq!(actual_json, expected_json);

let deserialized_task: Task = serde_json::from_str(&actual_json)?;
assert_eq!(deserialized_task, task);

// Example with None for optional field
let task_no_retry = Task {
    name: "Task 2".to_string(),
    timeout: Duration::from_secs(5),
    retry_delay: None,
};
let expected_json_no_retry = r#"{"name":"Task 2","timeout":"5s","retry_delay":null}"#;
let actual_json_no_retry = serde_json::to_string(&task_no_retry)?;
assert_eq!(actual_json_no_retry, expected_json_no_retry);

let deserialized_task_no_retry: Task = serde_json::from_str(&actual_json_no_retry)?;
assert_eq!(deserialized_task_no_retry, task_no_retry);

§Example: custom “friendly” format options

When using friendly::compact, the serialization implementation uses a fixed friendly format configuration. To use your own configuration, you’ll need to write your own serialization function:

use std::time::Duration;

#[derive(Debug, serde::Deserialize, serde::Serialize)]
struct Data {
    #[serde(serialize_with = "custom_friendly")]
    // We can reuse an existing deserialization helper so that you
    // don't have to write your own.
    #[serde(deserialize_with = "jiff::fmt::serde::unsigned_duration::required::deserialize")]
    duration: Duration,
}

let json = r#"{"duration": "36 hours 1100ms"}"#;
let got: Data = serde_json::from_str(&json).unwrap();
assert_eq!(got.duration, Duration::new(36 * 60 * 60 + 1, 100_000_000));

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

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

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

            static PRINTER: SpanPrinter = SpanPrinter::new()
                .hours_minutes_seconds(true)
                .precision(Some(3));

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

    se.collect_str(&Custom(duration))
}

Modules§

friendly
(De)serialize a std::time::Duration in the friendly duration format.
optional
(De)serialize an optional ISO 8601 or friendly duration from a std::time::Duration.
required
(De)serialize a required ISO 8601 or friendly duration from a std::time::Duration.