nt_time/serde_with/
iso_8601.rs

1// SPDX-FileCopyrightText: 2023 Shun Sakai
2//
3// SPDX-License-Identifier: Apache-2.0 OR MIT
4
5//! Use the well-known [ISO 8601 format] when serializing and deserializing a
6//! [`FileTime`].
7//!
8//! Use this module in combination with Serde's [`with`] attribute.
9//!
10//! <div class="warning">
11//!
12//! If the `large-dates` feature is not enabled, the largest date and time is
13//! "9999-12-31 23:59:59.999999999 UTC".
14//!
15//! </div>
16//!
17//! # Examples
18//!
19//! ```
20//! use nt_time::{
21//!     FileTime,
22//!     serde::{Deserialize, Serialize},
23//!     serde_with::iso_8601,
24//! };
25//!
26//! #[derive(Deserialize, Serialize)]
27//! struct Time {
28//!     #[serde(with = "iso_8601")]
29//!     time: FileTime,
30//! }
31//!
32//! let ft = Time {
33//!     time: FileTime::UNIX_EPOCH,
34//! };
35//! let json = serde_json::to_string(&ft).unwrap();
36//! assert_eq!(json, r#"{"time":"+001970-01-01T00:00:00.000000000Z"}"#);
37//!
38//! let ft: Time = serde_json::from_str(&json).unwrap();
39//! assert_eq!(ft.time, FileTime::UNIX_EPOCH);
40//! ```
41//!
42//! [ISO 8601 format]: https://www.iso.org/iso-8601-date-and-time-format.html
43//! [`with`]: https://serde.rs/field-attrs.html#with
44
45pub mod option;
46
47use serde::{Deserializer, Serializer, de::Error as _, ser::Error as _};
48use time::serde::iso8601;
49
50use crate::FileTime;
51
52#[allow(clippy::missing_errors_doc)]
53/// Serializes a [`FileTime`] into the given Serde serializer.
54///
55/// This serializes using the well-known [ISO 8601 format].
56///
57/// [ISO 8601 format]: https://www.iso.org/iso-8601-date-and-time-format.html
58#[inline]
59pub fn serialize<S: Serializer>(ft: &FileTime, serializer: S) -> Result<S::Ok, S::Error> {
60    iso8601::serialize(&(*ft).try_into().map_err(S::Error::custom)?, serializer)
61}
62
63#[allow(clippy::missing_errors_doc)]
64/// Deserializes a [`FileTime`] from the given Serde deserializer.
65///
66/// This deserializes from its [ISO 8601 representation].
67///
68/// [ISO 8601 representation]: https://www.iso.org/iso-8601-date-and-time-format.html
69#[inline]
70pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<FileTime, D::Error> {
71    FileTime::try_from(iso8601::deserialize(deserializer)?).map_err(D::Error::custom)
72}
73
74#[cfg(test)]
75mod tests {
76    use serde::{Deserialize, Serialize};
77    use serde_test::{Token, assert_de_tokens_error, assert_tokens};
78
79    use super::*;
80
81    #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
82    struct Test {
83        #[serde(with = "crate::serde_with::iso_8601")]
84        time: FileTime,
85    }
86
87    #[test]
88    fn serde() {
89        assert_tokens(
90            &Test {
91                time: FileTime::NT_TIME_EPOCH,
92            },
93            &[
94                Token::Struct {
95                    name: "Test",
96                    len: 1,
97                },
98                Token::Str("time"),
99                Token::BorrowedStr("+001601-01-01T00:00:00.000000000Z"),
100                Token::StructEnd,
101            ],
102        );
103        assert_tokens(
104            &Test {
105                time: FileTime::UNIX_EPOCH,
106            },
107            &[
108                Token::Struct {
109                    name: "Test",
110                    len: 1,
111                },
112                Token::Str("time"),
113                Token::BorrowedStr("+001970-01-01T00:00:00.000000000Z"),
114                Token::StructEnd,
115            ],
116        );
117    }
118
119    #[cfg(feature = "large-dates")]
120    #[test]
121    fn serde_with_large_dates() {
122        assert_tokens(
123            &Test {
124                time: FileTime::MAX,
125            },
126            &[
127                Token::Struct {
128                    name: "Test",
129                    len: 1,
130                },
131                Token::Str("time"),
132                Token::BorrowedStr("+060056-05-28T05:36:10.955161500Z"),
133                Token::StructEnd,
134            ],
135        );
136    }
137
138    #[test]
139    fn deserialize_error() {
140        assert_de_tokens_error::<Test>(
141            &[
142                Token::Struct {
143                    name: "Test",
144                    len: 1,
145                },
146                Token::Str("time"),
147                Token::BorrowedStr("+001600-12-31T23:59:59.999999900Z"),
148                Token::StructEnd,
149            ],
150            "date and time is before `1601-01-01 00:00:00 UTC`",
151        );
152    }
153
154    #[cfg(not(feature = "large-dates"))]
155    #[test]
156    fn deserialize_error_without_large_dates() {
157        assert_de_tokens_error::<Test>(
158            &[
159                Token::Struct {
160                    name: "Test",
161                    len: 1,
162                },
163                Token::Str("time"),
164                Token::BorrowedStr("+010000-01-01T00:00:00.000000000Z"),
165                Token::StructEnd,
166            ],
167            "unexpected trailing characters; the end of input was expected",
168        );
169    }
170
171    #[cfg(feature = "large-dates")]
172    #[test]
173    fn deserialize_error_with_large_dates() {
174        assert_de_tokens_error::<Test>(
175            &[
176                Token::Struct {
177                    name: "Test",
178                    len: 1,
179                },
180                Token::Str("time"),
181                Token::BorrowedStr("+060056-05-28T05:36:10.955161600Z"),
182                Token::StructEnd,
183            ],
184            "date and time is after `+60056-05-28 05:36:10.955161500 UTC`",
185        );
186    }
187
188    #[test]
189    fn serialize_json() {
190        assert_eq!(
191            serde_json::to_string(&Test {
192                time: FileTime::NT_TIME_EPOCH
193            })
194            .unwrap(),
195            r#"{"time":"+001601-01-01T00:00:00.000000000Z"}"#
196        );
197        assert_eq!(
198            serde_json::to_string(&Test {
199                time: FileTime::UNIX_EPOCH
200            })
201            .unwrap(),
202            r#"{"time":"+001970-01-01T00:00:00.000000000Z"}"#
203        );
204    }
205
206    #[cfg(feature = "large-dates")]
207    #[test]
208    fn serialize_json_with_large_dates() {
209        assert_eq!(
210            serde_json::to_string(&Test {
211                time: FileTime::MAX
212            })
213            .unwrap(),
214            r#"{"time":"+060056-05-28T05:36:10.955161500Z"}"#
215        );
216    }
217
218    #[test]
219    fn deserialize_json() {
220        assert_eq!(
221            serde_json::from_str::<Test>(r#"{"time":"1601-01-01T00:00:00Z"}"#).unwrap(),
222            Test {
223                time: FileTime::NT_TIME_EPOCH
224            }
225        );
226        assert_eq!(
227            serde_json::from_str::<Test>(r#"{"time":"1970-01-01T00:00:00Z"}"#).unwrap(),
228            Test {
229                time: FileTime::UNIX_EPOCH
230            }
231        );
232    }
233
234    #[cfg(feature = "large-dates")]
235    #[test]
236    fn deserialize_json_with_large_dates() {
237        assert_eq!(
238            serde_json::from_str::<Test>(r#"{"time":"+060056-05-28T05:36:10.955161500Z"}"#)
239                .unwrap(),
240            Test {
241                time: FileTime::MAX
242            }
243        );
244    }
245}