Skip to main content

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/// const CONFIG: iso8601::EncodedConfig = iso8601::Config::DEFAULT
176///     .set_year_is_six_digits(false)
177///     .encode();
178/// const FORMAT: Iso8601<CONFIG> = Iso8601::<CONFIG>;
179///
180/// // Makes a module `mod my_format { ... }`.
181/// serde::format_description!(my_format, OffsetDateTime, FORMAT);
182///
183/// # #[allow(dead_code)]
184#[cfg_attr(
185    all(feature = "formatting", feature = "parsing"),
186    doc = "#[derive(Serialize, Deserialize)]"
187)]
188#[cfg_attr(
189    all(feature = "formatting", not(feature = "parsing")),
190    doc = "#[derive(Serialize)]"
191)]
192#[cfg_attr(
193    all(not(feature = "formatting"), feature = "parsing"),
194    doc = "#[derive(Deserialize)]"
195)]
196/// struct SerializesWithCustom {
197///     #[serde(with = "my_format")]
198///     dt: OffsetDateTime,
199///     #[serde(with = "my_format::option")]
200///     maybe_dt: Option<OffsetDateTime>,
201/// }
202/// # fn main() {}
203/// ```
204/// 
205/// [`format_description::parse()`]: crate::format_description::parse()
206#[cfg(all(feature = "macros", any(feature = "formatting", feature = "parsing"),))]
207pub use time_macros::serde_format_description as format_description;
208
209use self::visitor::Visitor;
210#[cfg(feature = "parsing")]
211use crate::format_description::{modifier, BorrowedFormatItem, Component};
212use crate::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday};
213
214// region: Date
215/// The format used when serializing and deserializing a human-readable `Date`.
216#[cfg(feature = "parsing")]
217const DATE_FORMAT: &[BorrowedFormatItem<'_>] = &[
218    BorrowedFormatItem::Component(Component::Year(modifier::Year::default())),
219    BorrowedFormatItem::Literal(b"-"),
220    BorrowedFormatItem::Component(Component::Month(modifier::Month::default())),
221    BorrowedFormatItem::Literal(b"-"),
222    BorrowedFormatItem::Component(Component::Day(modifier::Day::default())),
223];
224
225impl Serialize for Date {
226    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
227        #[cfg(feature = "serde-human-readable")]
228        if serializer.is_human_readable() {
229            let Ok(s) = self.format(&DATE_FORMAT) else {
230                return Err(S::Error::custom("failed formatting `Date`"));
231            };
232            return serializer.serialize_str(&s);
233        }
234
235        (self.year(), self.ordinal()).serialize(serializer)
236    }
237}
238
239impl<'a> Deserialize<'a> for Date {
240    fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
241        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
242            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
243        } else {
244            deserializer.deserialize_tuple(2, Visitor::<Self>(PhantomData))
245        }
246    }
247}
248// endregion date
249
250// region: Duration
251impl Serialize for Duration {
252    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
253        #[cfg(feature = "serde-human-readable")]
254        if serializer.is_human_readable() {
255            return serializer.collect_str(&format_args!(
256                "{}.{:>09}",
257                self.whole_seconds(),
258                self.subsec_nanoseconds().abs()
259            ));
260        }
261
262        (self.whole_seconds(), self.subsec_nanoseconds()).serialize(serializer)
263    }
264}
265
266impl<'a> Deserialize<'a> for Duration {
267    fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
268        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
269            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
270        } else {
271            deserializer.deserialize_tuple(2, Visitor::<Self>(PhantomData))
272        }
273    }
274}
275// endregion Duration
276
277// region: OffsetDateTime
278/// The format used when serializing and deserializing a human-readable `OffsetDateTime`.
279#[cfg(feature = "parsing")]
280const OFFSET_DATE_TIME_FORMAT: &[BorrowedFormatItem<'_>] = &[
281    BorrowedFormatItem::Compound(DATE_FORMAT),
282    BorrowedFormatItem::Literal(b" "),
283    BorrowedFormatItem::Compound(TIME_FORMAT),
284    BorrowedFormatItem::Literal(b" "),
285    BorrowedFormatItem::Compound(UTC_OFFSET_FORMAT),
286];
287
288impl Serialize for OffsetDateTime {
289    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
290        #[cfg(feature = "serde-human-readable")]
291        if serializer.is_human_readable() {
292            let Ok(s) = self.format(&OFFSET_DATE_TIME_FORMAT) else {
293                return Err(S::Error::custom("failed formatting `OffsetDateTime`"));
294            };
295            return serializer.serialize_str(&s);
296        }
297
298        (
299            self.year(),
300            self.ordinal(),
301            self.hour(),
302            self.minute(),
303            self.second(),
304            self.nanosecond(),
305            self.offset().whole_hours(),
306            self.offset().minutes_past_hour(),
307            self.offset().seconds_past_minute(),
308        )
309            .serialize(serializer)
310    }
311}
312
313impl<'a> Deserialize<'a> for OffsetDateTime {
314    fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
315        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
316            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
317        } else {
318            deserializer.deserialize_tuple(9, Visitor::<Self>(PhantomData))
319        }
320    }
321}
322// endregion OffsetDateTime
323
324// region: PrimitiveDateTime
325/// The format used when serializing and deserializing a human-readable `PrimitiveDateTime`.
326#[cfg(feature = "parsing")]
327const PRIMITIVE_DATE_TIME_FORMAT: &[BorrowedFormatItem<'_>] = &[
328    BorrowedFormatItem::Compound(DATE_FORMAT),
329    BorrowedFormatItem::Literal(b" "),
330    BorrowedFormatItem::Compound(TIME_FORMAT),
331];
332
333impl Serialize for PrimitiveDateTime {
334    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
335        #[cfg(feature = "serde-human-readable")]
336        if serializer.is_human_readable() {
337            let Ok(s) = self.format(&PRIMITIVE_DATE_TIME_FORMAT) else {
338                return Err(S::Error::custom("failed formatting `PrimitiveDateTime`"));
339            };
340            return serializer.serialize_str(&s);
341        }
342
343        (
344            self.year(),
345            self.ordinal(),
346            self.hour(),
347            self.minute(),
348            self.second(),
349            self.nanosecond(),
350        )
351            .serialize(serializer)
352    }
353}
354
355impl<'a> Deserialize<'a> for PrimitiveDateTime {
356    fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
357        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
358            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
359        } else {
360            deserializer.deserialize_tuple(6, Visitor::<Self>(PhantomData))
361        }
362    }
363}
364// endregion PrimitiveDateTime
365
366// region: Time
367/// The format used when serializing and deserializing a human-readable `Time`.
368#[cfg(feature = "parsing")]
369const TIME_FORMAT: &[BorrowedFormatItem<'_>] = &[
370    BorrowedFormatItem::Component(Component::Hour(modifier::Hour::default())),
371    BorrowedFormatItem::Literal(b":"),
372    BorrowedFormatItem::Component(Component::Minute(modifier::Minute::default())),
373    BorrowedFormatItem::Literal(b":"),
374    BorrowedFormatItem::Component(Component::Second(modifier::Second::default())),
375    BorrowedFormatItem::Literal(b"."),
376    BorrowedFormatItem::Component(Component::Subsecond(modifier::Subsecond::default())),
377];
378
379impl Serialize for Time {
380    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
381        #[cfg(feature = "serde-human-readable")]
382        if serializer.is_human_readable() {
383            let Ok(s) = self.format(&TIME_FORMAT) else {
384                return Err(S::Error::custom("failed formatting `Time`"));
385            };
386            return serializer.serialize_str(&s);
387        }
388
389        (self.hour(), self.minute(), self.second(), self.nanosecond()).serialize(serializer)
390    }
391}
392
393impl<'a> Deserialize<'a> for Time {
394    fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
395        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
396            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
397        } else {
398            deserializer.deserialize_tuple(4, Visitor::<Self>(PhantomData))
399        }
400    }
401}
402// endregion Time
403
404// region: UtcOffset
405/// The format used when serializing and deserializing a human-readable `UtcOffset`.
406#[cfg(feature = "parsing")]
407const UTC_OFFSET_FORMAT: &[BorrowedFormatItem<'_>] = &[
408    BorrowedFormatItem::Component(Component::OffsetHour({
409        let mut m = modifier::OffsetHour::default();
410        m.sign_is_mandatory = true;
411        m
412    })),
413    BorrowedFormatItem::Optional(&BorrowedFormatItem::Compound(&[
414        BorrowedFormatItem::Literal(b":"),
415        BorrowedFormatItem::Component(Component::OffsetMinute(modifier::OffsetMinute::default())),
416        BorrowedFormatItem::Optional(&BorrowedFormatItem::Compound(&[
417            BorrowedFormatItem::Literal(b":"),
418            BorrowedFormatItem::Component(Component::OffsetSecond(
419                modifier::OffsetSecond::default(),
420            )),
421        ])),
422    ])),
423];
424
425impl Serialize for UtcOffset {
426    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
427        #[cfg(feature = "serde-human-readable")]
428        if serializer.is_human_readable() {
429            let Ok(s) = self.format(&UTC_OFFSET_FORMAT) else {
430                return Err(S::Error::custom("failed formatting `UtcOffset`"));
431            };
432            return serializer.serialize_str(&s);
433        }
434
435        (
436            self.whole_hours(),
437            self.minutes_past_hour(),
438            self.seconds_past_minute(),
439        )
440            .serialize(serializer)
441    }
442}
443
444impl<'a> Deserialize<'a> for UtcOffset {
445    fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
446        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
447            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
448        } else {
449            deserializer.deserialize_tuple(3, Visitor::<Self>(PhantomData))
450        }
451    }
452}
453// endregion UtcOffset
454
455// region: Weekday
456impl Serialize for Weekday {
457    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
458        #[cfg(feature = "serde-human-readable")]
459        if serializer.is_human_readable() {
460            #[cfg(not(feature = "std"))]
461            use alloc::string::ToString;
462            return self.to_string().serialize(serializer);
463        }
464
465        self.number_from_monday().serialize(serializer)
466    }
467}
468
469impl<'a> Deserialize<'a> for Weekday {
470    fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
471        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
472            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
473        } else {
474            deserializer.deserialize_u8(Visitor::<Self>(PhantomData))
475        }
476    }
477}
478// endregion Weekday
479
480// region: Month
481impl Serialize for Month {
482    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
483        #[cfg(feature = "serde-human-readable")]
484        if serializer.is_human_readable() {
485            #[cfg(not(feature = "std"))]
486            use alloc::string::String;
487            return self.to_string().serialize(serializer);
488        }
489
490        u8::from(*self).serialize(serializer)
491    }
492}
493
494impl<'a> Deserialize<'a> for Month {
495    fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
496        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
497            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
498        } else {
499            deserializer.deserialize_u8(Visitor::<Self>(PhantomData))
500        }
501    }
502}
503// endregion Month