time/serde/
mod.rs

1//! Differential formats for serde.
2// This also includes the serde implementations for all types. This doesn't need to be externally
3// documented, though.
4
5// Types with guaranteed stable serde representations. Strings are avoided to allow for optimal
6// representations in various binary forms.
7
8/// Consume the next item in a sequence.
9macro_rules! item {
10    ($seq:expr, $name:literal) => {
11        $seq.next_element()?
12            .ok_or_else(|| <A::Error as serde::de::Error>::custom(concat!("expected ", $name)))
13    };
14}
15
16#[cfg(any(feature = "formatting", feature = "parsing"))]
17pub mod iso8601;
18#[cfg(any(feature = "formatting", feature = "parsing"))]
19pub mod rfc2822;
20#[cfg(any(feature = "formatting", feature = "parsing"))]
21pub mod rfc3339;
22pub mod timestamp;
23mod visitor;
24
25#[cfg(feature = "serde-human-readable")]
26use alloc::string::ToString;
27use core::marker::PhantomData;
28
29#[cfg(feature = "serde-human-readable")]
30use serde::ser::Error as _;
31use serde::{Deserialize, Deserializer, Serialize, Serializer};
32/// Generate a custom serializer and deserializer from a format string or an existing format.
33///
34/// The syntax accepted by this macro is the same as [`format_description::parse()`], which can
35/// be found in [the book](https://time-rs.github.io/book/api/format-description.html).
36///
37/// # Usage
38///
39/// Invoked as `serde::format_description!(mod_name, Date, FORMAT)` where `FORMAT` is either a
40/// `"<format string>"` or something that implements
41#[cfg_attr(
42    all(feature = "formatting", feature = "parsing"),
43    doc = "[`Formattable`](crate::formatting::Formattable) and \
44           [`Parsable`](crate::parsing::Parsable)."
45)]
46#[cfg_attr(
47    all(feature = "formatting", not(feature = "parsing")),
48    doc = "[`Formattable`](crate::formatting::Formattable)."
49)]
50#[cfg_attr(
51    all(not(feature = "formatting"), feature = "parsing"),
52    doc = "[`Parsable`](crate::parsing::Parsable)."
53)]
54/// This puts a module named `mod_name` in the current scope that can be used to format `Date`
55/// structs. A submodule (`mod_name::option`) is also generated for `Option<Date>`. Both
56/// modules are only visible in the current scope.
57///
58/// The returned `Option` will contain a deserialized value if present and `None` if the field
59/// is present but the value is `null` (or the equivalent in other formats). To return `None`
60/// when the field is not present, you should use `#[serde(default)]` on the field.
61///
62/// # Examples
63///
64/// Using a format string:
65///
66/// ```rust,no_run
67/// # use time::OffsetDateTime;
68#[cfg_attr(
69    all(feature = "formatting", feature = "parsing"),
70    doc = "use ::serde::{Serialize, Deserialize};"
71)]
72#[cfg_attr(
73    all(feature = "formatting", not(feature = "parsing")),
74    doc = "use ::serde::Serialize;"
75)]
76#[cfg_attr(
77    all(not(feature = "formatting"), feature = "parsing"),
78    doc = "use ::serde::Deserialize;"
79)]
80/// use time::serde;
81///
82/// // Makes a module `mod my_format { ... }`.
83/// serde::format_description!(my_format, OffsetDateTime, "hour=[hour], minute=[minute]");
84///
85/// # #[allow(dead_code)]
86#[cfg_attr(
87    all(feature = "formatting", feature = "parsing"),
88    doc = "#[derive(Serialize, Deserialize)]"
89)]
90#[cfg_attr(
91    all(feature = "formatting", not(feature = "parsing")),
92    doc = "#[derive(Serialize)]"
93)]
94#[cfg_attr(
95    all(not(feature = "formatting"), feature = "parsing"),
96    doc = "#[derive(Deserialize)]"
97)]
98/// struct SerializesWithCustom {
99///     #[serde(with = "my_format")]
100///     dt: OffsetDateTime,
101///     #[serde(with = "my_format::option")]
102///     maybe_dt: Option<OffsetDateTime>,
103/// }
104/// ```
105/// 
106/// Define the format separately to be used in multiple places:
107/// ```rust,no_run
108/// # use time::OffsetDateTime;
109#[cfg_attr(
110    all(feature = "formatting", feature = "parsing"),
111    doc = "use ::serde::{Serialize, Deserialize};"
112)]
113#[cfg_attr(
114    all(feature = "formatting", not(feature = "parsing")),
115    doc = "use ::serde::Serialize;"
116)]
117#[cfg_attr(
118    all(not(feature = "formatting"), feature = "parsing"),
119    doc = "use ::serde::Deserialize;"
120)]
121/// use time::serde;
122/// use time::format_description::BorrowedFormatItem;
123///
124/// const DATE_TIME_FORMAT: &[BorrowedFormatItem<'_>] = time::macros::format_description!(
125///     "hour=[hour], minute=[minute]"
126/// );
127///
128/// // Makes a module `mod my_format { ... }`.
129/// serde::format_description!(my_format, OffsetDateTime, DATE_TIME_FORMAT);
130///
131/// # #[allow(dead_code)]
132#[cfg_attr(
133    all(feature = "formatting", feature = "parsing"),
134    doc = "#[derive(Serialize, Deserialize)]"
135)]
136#[cfg_attr(
137    all(feature = "formatting", not(feature = "parsing")),
138    doc = "#[derive(Serialize)]"
139)]
140#[cfg_attr(
141    all(not(feature = "formatting"), feature = "parsing"),
142    doc = "#[derive(Deserialize)]"
143)]
144/// struct SerializesWithCustom {
145///     #[serde(with = "my_format")]
146///     dt: OffsetDateTime,
147///     #[serde(with = "my_format::option")]
148///     maybe_dt: Option<OffsetDateTime>,
149/// }
150///
151/// fn main() {
152///     # #[allow(unused_variables)]
153///     let str_ts = OffsetDateTime::now_utc().format(DATE_TIME_FORMAT).unwrap();
154/// }
155/// ```
156/// 
157/// Customize the configuration of ISO 8601 formatting/parsing:
158/// ```rust,no_run
159/// # use time::OffsetDateTime;
160#[cfg_attr(
161    all(feature = "formatting", feature = "parsing"),
162    doc = "use ::serde::{Serialize, Deserialize};"
163)]
164#[cfg_attr(
165    all(feature = "formatting", not(feature = "parsing")),
166    doc = "use ::serde::Serialize;"
167)]
168#[cfg_attr(
169    all(not(feature = "formatting"), feature = "parsing"),
170    doc = "use ::serde::Deserialize;"
171)]
172/// use time::serde;
173/// use time::format_description::well_known::{iso8601, Iso8601};
174///
175/// # #[allow(dead_code)]
176/// const CONFIG: iso8601::EncodedConfig = iso8601::Config::DEFAULT
177///     .set_year_is_six_digits(false)
178///     .encode();
179/// # #[allow(dead_code)]
180/// const FORMAT: Iso8601<CONFIG> = Iso8601::<CONFIG>;
181///
182/// // Makes a module `mod my_format { ... }`.
183/// serde::format_description!(my_format, OffsetDateTime, FORMAT);
184///
185/// # #[allow(dead_code)]
186#[cfg_attr(
187    all(feature = "formatting", feature = "parsing"),
188    doc = "#[derive(Serialize, Deserialize)]"
189)]
190#[cfg_attr(
191    all(feature = "formatting", not(feature = "parsing")),
192    doc = "#[derive(Serialize)]"
193)]
194#[cfg_attr(
195    all(not(feature = "formatting"), feature = "parsing"),
196    doc = "#[derive(Deserialize)]"
197)]
198/// struct SerializesWithCustom {
199///     #[serde(with = "my_format")]
200///     dt: OffsetDateTime,
201///     #[serde(with = "my_format::option")]
202///     maybe_dt: Option<OffsetDateTime>,
203/// }
204/// # fn main() {}
205/// ```
206/// 
207/// [`format_description::parse()`]: crate::format_description::parse()
208#[cfg(all(feature = "macros", any(feature = "formatting", feature = "parsing")))]
209pub use time_macros::serde_format_description as format_description;
210
211use self::visitor::Visitor;
212#[cfg(feature = "parsing")]
213use crate::format_description::{modifier, BorrowedFormatItem, Component};
214use crate::{
215    Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcDateTime, UtcOffset, Weekday,
216};
217
218// region: Date
219/// The format used when serializing and deserializing a human-readable `Date`.
220#[cfg(feature = "parsing")]
221const DATE_FORMAT: &[BorrowedFormatItem<'_>] = &[
222    BorrowedFormatItem::Component(Component::Year(modifier::Year::default())),
223    BorrowedFormatItem::Literal(b"-"),
224    BorrowedFormatItem::Component(Component::Month(modifier::Month::default())),
225    BorrowedFormatItem::Literal(b"-"),
226    BorrowedFormatItem::Component(Component::Day(modifier::Day::default())),
227];
228
229impl Serialize for Date {
230    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
231        #[cfg(feature = "serde-human-readable")]
232        if serializer.is_human_readable() {
233            let Ok(s) = self.format(&DATE_FORMAT) else {
234                return Err(S::Error::custom("failed formatting `Date`"));
235            };
236            return serializer.serialize_str(&s);
237        }
238
239        (self.year(), self.ordinal()).serialize(serializer)
240    }
241}
242
243impl<'a> Deserialize<'a> for Date {
244    fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
245        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
246            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
247        } else {
248            deserializer.deserialize_tuple(2, Visitor::<Self>(PhantomData))
249        }
250    }
251}
252// endregion date
253
254// region: Duration
255impl Serialize for Duration {
256    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
257        #[cfg(feature = "serde-human-readable")]
258        if serializer.is_human_readable() {
259            return serializer.collect_str(&format_args!(
260                "{}{}.{:>09}",
261                if self.is_negative() { "-" } else { "" },
262                self.whole_seconds().unsigned_abs(),
263                self.subsec_nanoseconds().abs(),
264            ));
265        }
266
267        (self.whole_seconds(), self.subsec_nanoseconds()).serialize(serializer)
268    }
269}
270
271impl<'a> Deserialize<'a> for Duration {
272    fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
273        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
274            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
275        } else {
276            deserializer.deserialize_tuple(2, Visitor::<Self>(PhantomData))
277        }
278    }
279}
280// endregion Duration
281
282// region: OffsetDateTime
283/// The format used when serializing and deserializing a human-readable `OffsetDateTime`.
284#[cfg(feature = "parsing")]
285const OFFSET_DATE_TIME_FORMAT: &[BorrowedFormatItem<'_>] = &[
286    BorrowedFormatItem::Compound(DATE_FORMAT),
287    BorrowedFormatItem::Literal(b" "),
288    BorrowedFormatItem::Compound(TIME_FORMAT),
289    BorrowedFormatItem::Literal(b" "),
290    BorrowedFormatItem::Compound(UTC_OFFSET_FORMAT),
291];
292
293impl Serialize for OffsetDateTime {
294    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
295        #[cfg(feature = "serde-human-readable")]
296        if serializer.is_human_readable() {
297            let Ok(s) = self.format(&OFFSET_DATE_TIME_FORMAT) else {
298                return Err(S::Error::custom("failed formatting `OffsetDateTime`"));
299            };
300            return serializer.serialize_str(&s);
301        }
302
303        (
304            self.year(),
305            self.ordinal(),
306            self.hour(),
307            self.minute(),
308            self.second(),
309            self.nanosecond(),
310            self.offset().whole_hours(),
311            self.offset().minutes_past_hour(),
312            self.offset().seconds_past_minute(),
313        )
314            .serialize(serializer)
315    }
316}
317
318impl<'a> Deserialize<'a> for OffsetDateTime {
319    fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
320        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
321            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
322        } else {
323            deserializer.deserialize_tuple(9, Visitor::<Self>(PhantomData))
324        }
325    }
326}
327// endregion OffsetDateTime
328
329// region: PrimitiveDateTime
330/// The format used when serializing and deserializing a human-readable `PrimitiveDateTime`.
331#[cfg(feature = "parsing")]
332const PRIMITIVE_DATE_TIME_FORMAT: &[BorrowedFormatItem<'_>] = &[
333    BorrowedFormatItem::Compound(DATE_FORMAT),
334    BorrowedFormatItem::Literal(b" "),
335    BorrowedFormatItem::Compound(TIME_FORMAT),
336];
337
338impl Serialize for PrimitiveDateTime {
339    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
340        #[cfg(feature = "serde-human-readable")]
341        if serializer.is_human_readable() {
342            let Ok(s) = self.format(&PRIMITIVE_DATE_TIME_FORMAT) else {
343                return Err(S::Error::custom("failed formatting `PrimitiveDateTime`"));
344            };
345            return serializer.serialize_str(&s);
346        }
347
348        (
349            self.year(),
350            self.ordinal(),
351            self.hour(),
352            self.minute(),
353            self.second(),
354            self.nanosecond(),
355        )
356            .serialize(serializer)
357    }
358}
359
360impl<'a> Deserialize<'a> for PrimitiveDateTime {
361    fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
362        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
363            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
364        } else {
365            deserializer.deserialize_tuple(6, Visitor::<Self>(PhantomData))
366        }
367    }
368}
369// endregion PrimitiveDateTime
370
371// region: UtcDateTime
372/// The format used when serializing and deserializing a human-readable `UtcDateTime`.
373#[cfg(feature = "parsing")]
374const UTC_DATE_TIME_FORMAT: &[BorrowedFormatItem<'_>] = PRIMITIVE_DATE_TIME_FORMAT;
375
376impl Serialize for UtcDateTime {
377    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
378        #[cfg(feature = "serde-human-readable")]
379        if serializer.is_human_readable() {
380            let Ok(s) = self.format(&PRIMITIVE_DATE_TIME_FORMAT) else {
381                return Err(S::Error::custom("failed formatting `UtcDateTime`"));
382            };
383            return serializer.serialize_str(&s);
384        }
385
386        (
387            self.year(),
388            self.ordinal(),
389            self.hour(),
390            self.minute(),
391            self.second(),
392            self.nanosecond(),
393        )
394            .serialize(serializer)
395    }
396}
397
398impl<'a> Deserialize<'a> for UtcDateTime {
399    fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
400        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
401            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
402        } else {
403            deserializer.deserialize_tuple(6, Visitor::<Self>(PhantomData))
404        }
405    }
406}
407// endregion UtcDateTime
408
409// region: Time
410/// The format used when serializing and deserializing a human-readable `Time`.
411#[cfg(feature = "parsing")]
412const TIME_FORMAT: &[BorrowedFormatItem<'_>] = &[
413    BorrowedFormatItem::Component(Component::Hour(modifier::Hour::default())),
414    BorrowedFormatItem::Literal(b":"),
415    BorrowedFormatItem::Component(Component::Minute(modifier::Minute::default())),
416    BorrowedFormatItem::Literal(b":"),
417    BorrowedFormatItem::Component(Component::Second(modifier::Second::default())),
418    BorrowedFormatItem::Literal(b"."),
419    BorrowedFormatItem::Component(Component::Subsecond(modifier::Subsecond::default())),
420];
421
422impl Serialize for Time {
423    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
424        #[cfg(feature = "serde-human-readable")]
425        if serializer.is_human_readable() {
426            let Ok(s) = self.format(&TIME_FORMAT) else {
427                return Err(S::Error::custom("failed formatting `Time`"));
428            };
429            return serializer.serialize_str(&s);
430        }
431
432        (self.hour(), self.minute(), self.second(), self.nanosecond()).serialize(serializer)
433    }
434}
435
436impl<'a> Deserialize<'a> for Time {
437    fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
438        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
439            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
440        } else {
441            deserializer.deserialize_tuple(4, Visitor::<Self>(PhantomData))
442        }
443    }
444}
445// endregion Time
446
447// region: UtcOffset
448// FIXME: turn these constants into `const { ... }` blocks once we can depend on Rust 1.79.
449#[cfg(feature = "parsing")]
450const UTC_OFFSET_HOUR: modifier::OffsetHour = {
451    let mut m = modifier::OffsetHour::default();
452    m.sign_is_mandatory = true;
453    m
454};
455#[cfg(feature = "parsing")]
456const UTC_OFFSET_MINUTE: modifier::OffsetMinute = modifier::OffsetMinute::default();
457#[cfg(feature = "parsing")]
458const UTC_OFFSET_SECOND: modifier::OffsetSecond = modifier::OffsetSecond::default();
459/// The format used when serializing and deserializing a human-readable `UtcOffset`.
460#[cfg(feature = "parsing")]
461const UTC_OFFSET_FORMAT: &[BorrowedFormatItem<'_>] = &[
462    BorrowedFormatItem::Component(Component::OffsetHour(UTC_OFFSET_HOUR)),
463    BorrowedFormatItem::Optional(&BorrowedFormatItem::Compound(&[
464        BorrowedFormatItem::Literal(b":"),
465        BorrowedFormatItem::Component(Component::OffsetMinute(UTC_OFFSET_MINUTE)),
466        BorrowedFormatItem::Optional(&BorrowedFormatItem::Compound(&[
467            BorrowedFormatItem::Literal(b":"),
468            BorrowedFormatItem::Component(Component::OffsetSecond(UTC_OFFSET_SECOND)),
469        ])),
470    ])),
471];
472
473impl Serialize for UtcOffset {
474    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
475        #[cfg(feature = "serde-human-readable")]
476        if serializer.is_human_readable() {
477            let Ok(s) = self.format(&UTC_OFFSET_FORMAT) else {
478                return Err(S::Error::custom("failed formatting `UtcOffset`"));
479            };
480            return serializer.serialize_str(&s);
481        }
482
483        (
484            self.whole_hours(),
485            self.minutes_past_hour(),
486            self.seconds_past_minute(),
487        )
488            .serialize(serializer)
489    }
490}
491
492impl<'a> Deserialize<'a> for UtcOffset {
493    fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
494        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
495            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
496        } else {
497            deserializer.deserialize_tuple(3, Visitor::<Self>(PhantomData))
498        }
499    }
500}
501// endregion UtcOffset
502
503// region: Weekday
504impl Serialize for Weekday {
505    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
506        #[cfg(feature = "serde-human-readable")]
507        if serializer.is_human_readable() {
508            #[cfg(not(feature = "std"))]
509            use alloc::string::ToString;
510            return self.to_string().serialize(serializer);
511        }
512
513        self.number_from_monday().serialize(serializer)
514    }
515}
516
517impl<'a> Deserialize<'a> for Weekday {
518    fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
519        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
520            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
521        } else {
522            deserializer.deserialize_u8(Visitor::<Self>(PhantomData))
523        }
524    }
525}
526// endregion Weekday
527
528// region: Month
529impl Serialize for Month {
530    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
531        #[cfg(feature = "serde-human-readable")]
532        if serializer.is_human_readable() {
533            #[cfg(not(feature = "std"))]
534            use alloc::string::String;
535            return self.to_string().serialize(serializer);
536        }
537
538        u8::from(*self).serialize(serializer)
539    }
540}
541
542impl<'a> Deserialize<'a> for Month {
543    fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
544        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
545            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
546        } else {
547            deserializer.deserialize_u8(Visitor::<Self>(PhantomData))
548        }
549    }
550}
551// endregion Month