iso8601_duration_serde/
lib.rs

1use iso8601_duration::Duration as IsoDuration;
2use serde::{Deserialize, Deserializer, Serialize, Serializer};
3use time::Duration;
4use time_core::convert::*;
5
6/// Serialize an [`time::Duration`] using the well-known ISO 8601 format.
7#[inline]
8pub fn serialize<S: Serializer>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error> {
9    let mut seconds = duration.whole_seconds();
10    let nanoseconds = duration.subsec_nanoseconds();
11
12    let days = seconds / Second::per_t::<i64>(Day);
13    seconds = seconds % Second::per_t::<i64>(Day);
14
15    let hours = seconds / Second::per_t::<i64>(Hour);
16    seconds = seconds % Second::per_t::<i64>(Hour);
17
18    let minutes = seconds / Second::per_t::<i64>(Minute);
19    seconds = seconds % Second::per_t::<i64>(Minute);
20
21    let seconds_f32 =
22        seconds as f32 + (nanoseconds as f64 / Nanosecond::per_t::<f64>(Second)) as f32;
23
24    let iso_duration = IsoDuration::new(
25        0f32,
26        0f32,
27        days as f32,
28        hours as f32,
29        minutes as f32,
30        seconds_f32,
31    );
32
33    iso_duration.serialize(serializer)
34}
35
36/// Deserialize an [`time::Duration`] from its ISO 8601 representation.
37#[inline]
38pub fn deserialize<'a, D: Deserializer<'a>>(deserializer: D) -> Result<Duration, D::Error> {
39    let duration = IsoDuration::deserialize(deserializer)?;
40
41    if duration.year > 0.0 || duration.month > 0.0 {
42        return Err(serde::de::Error::custom(
43            "Duration::year and Duration::month must be zero",
44        ));
45    }
46
47    let seconds_fract = duration.day.fract() * Second::per_t::<f32>(Day)
48        + duration.hour.fract() * Second::per_t::<f32>(Hour)
49        + duration.minute.fract() * Second::per_t::<f32>(Minute)
50        + duration.second.fract();
51
52    let seconds = duration.day as i64 * Second::per_t::<i64>(Day)
53        + duration.hour as i64 * Second::per_t::<i64>(Hour)
54        + duration.minute as i64 * Second::per_t::<i64>(Minute)
55        + duration.second as i64
56        + seconds_fract as i64;
57
58    let nanoseconds = (seconds_fract.fract() * Nanosecond::per_t::<f32>(Second)) as i32;
59
60    Ok(Duration::new(seconds, nanoseconds))
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    use serde::{Serialize, Deserialize};
67
68    #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
69    struct TestStruct {
70        #[serde(with = "super")]
71        duration: Duration,
72    }
73
74    #[test]
75    fn test_positive_duration_with_days() {
76        let test_struct = TestStruct {
77            duration: Duration::days(5),
78        };
79
80        let json = serde_json::to_string(&test_struct).unwrap();
81        assert_eq!(json, r#"{"duration":"P5D"}"#);
82
83        let deserialized: TestStruct = serde_json::from_str(&json).unwrap();
84        assert_eq!(test_struct, deserialized);
85    }
86
87    #[test]
88    fn test_positive_duration_with_hours() {
89        let test_struct = TestStruct {
90            duration: Duration::hours(3),
91        };
92
93        let json = serde_json::to_string(&test_struct).unwrap();
94        assert_eq!(json, r#"{"duration":"PT3H"}"#);
95
96        let deserialized: TestStruct = serde_json::from_str(&json).unwrap();
97        assert_eq!(test_struct, deserialized);
98    }
99
100    #[test]
101    fn test_positive_duration_with_minutes() {
102        let test_struct = TestStruct {
103            duration: Duration::minutes(30),
104        };
105
106        let json = serde_json::to_string(&test_struct).unwrap();
107        assert_eq!(json, r#"{"duration":"PT30M"}"#);
108
109        let deserialized: TestStruct = serde_json::from_str(&json).unwrap();
110        assert_eq!(test_struct, deserialized);
111    }
112
113    #[test]
114    fn test_positive_duration_with_seconds() {
115        let test_struct = TestStruct {
116            duration: Duration::seconds(45),
117        };
118
119        let json = serde_json::to_string(&test_struct).unwrap();
120        assert_eq!(json, r#"{"duration":"PT45S"}"#);
121
122        let deserialized: TestStruct = serde_json::from_str(&json).unwrap();
123        assert_eq!(test_struct, deserialized);
124    }
125
126    #[test]
127    fn test_complex_positive_duration() {
128        let test_struct = TestStruct {
129            duration: Duration::days(2) + Duration::hours(3) + Duration::minutes(30) + Duration::seconds(15),
130        };
131
132        let json = serde_json::to_string(&test_struct).unwrap();
133        assert_eq!(json, r#"{"duration":"P2DT3H30M15S"}"#);
134
135        let deserialized: TestStruct = serde_json::from_str(&json).unwrap();
136        assert_eq!(test_struct, deserialized);
137    }
138
139    #[test]
140    fn test_duration_with_fractional_seconds() {
141        let test_struct = TestStruct {
142            duration: Duration::seconds(10) + Duration::milliseconds(500),
143        };
144
145        let json = serde_json::to_string(&test_struct).unwrap();
146        // Depending on serialization implementation, fractional seconds might be formatted differently
147        
148        let deserialized: TestStruct = serde_json::from_str(&json).unwrap();
149        // Allow small differences in precision due to floating point arithmetic
150        assert_eq!(deserialized.duration.whole_seconds(), test_struct.duration.whole_seconds());
151        // Check that the difference is less than 1 second
152        assert!(deserialized.duration - test_struct.duration < Duration::seconds(1));
153        assert!(test_struct.duration - deserialized.duration < Duration::seconds(1));
154    }
155
156    #[test]
157    fn test_deserialize_iso8601_variants() {
158        // Test various ISO 8601 formats
159        
160        // Days only
161        let json = r#"{"duration":"P7D"}"#;
162        let deserialized: TestStruct = serde_json::from_str(json).unwrap();
163        assert_eq!(deserialized.duration, Duration::days(7));
164        
165        // Hours only
166        let json = r#"{"duration":"PT5H"}"#;
167        let deserialized: TestStruct = serde_json::from_str(json).unwrap();
168        assert_eq!(deserialized.duration, Duration::hours(5));
169        
170        // Minutes only
171        let json = r#"{"duration":"PT30M"}"#;
172        let deserialized: TestStruct = serde_json::from_str(json).unwrap();
173        assert_eq!(deserialized.duration, Duration::minutes(30));
174        
175        // Seconds only
176        let json = r#"{"duration":"PT45S"}"#;
177        let deserialized: TestStruct = serde_json::from_str(json).unwrap();
178        assert_eq!(deserialized.duration, Duration::seconds(45));
179        
180        // Complex format
181        let json = r#"{"duration":"P1DT12H30M45S"}"#;
182        let deserialized: TestStruct = serde_json::from_str(json).unwrap();
183        assert_eq!(deserialized.duration, Duration::days(1) + Duration::hours(12) + Duration::minutes(30) + Duration::seconds(45));
184        
185        // Fractional seconds
186        let json = r#"{"duration":"PT10.5S"}"#;
187        let deserialized: TestStruct = serde_json::from_str(json).unwrap();
188        // Check that deserialization works (exact value might vary due to float precision)
189        assert!(deserialized.duration > Duration::seconds(10));
190        assert!(deserialized.duration < Duration::seconds(11));
191    }
192}