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_core::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_core::ser::Error as _;
31use serde_core::{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 by default. To increase visibility, you can
57/// specify `pub`, `pub(crate)`, or similar before the module name:
58/// `serde::format_description!(pub mod_name, Date, FORMAT)`.
59///
60/// The returned `Option` will contain a deserialized value if present and `None` if the field
61/// is present but the value is `null` (or the equivalent in other formats). To return `None`
62/// when the field is not present, you should use `#[serde(default)]` on the field.
63///
64/// Note: Due to [serde-rs/serde#2878], you will need to apply `#[serde(default)]` if you want
65/// a missing field to deserialize as `None`.
66///
67/// # Examples
68///
69/// Using a format string:
70///
71/// ```rust,no_run
72/// # use time::OffsetDateTime;
73#[cfg_attr(
74    all(feature = "formatting", feature = "parsing"),
75    doc = "use ::serde::{Serialize, Deserialize};"
76)]
77#[cfg_attr(
78    all(feature = "formatting", not(feature = "parsing")),
79    doc = "use ::serde::Serialize;"
80)]
81#[cfg_attr(
82    all(not(feature = "formatting"), feature = "parsing"),
83    doc = "use ::serde::Deserialize;"
84)]
85/// use time::serde;
86///
87/// // Makes a module `mod my_format { ... }`.
88/// serde::format_description!(my_format, OffsetDateTime, "hour=[hour], minute=[minute]");
89///
90/// # #[allow(dead_code)]
91#[cfg_attr(
92    all(feature = "formatting", feature = "parsing"),
93    doc = "#[derive(Serialize, Deserialize)]"
94)]
95#[cfg_attr(
96    all(feature = "formatting", not(feature = "parsing")),
97    doc = "#[derive(Serialize)]"
98)]
99#[cfg_attr(
100    all(not(feature = "formatting"), feature = "parsing"),
101    doc = "#[derive(Deserialize)]"
102)]
103/// struct SerializesWithCustom {
104///     #[serde(with = "my_format")]
105///     dt: OffsetDateTime,
106///     #[serde(with = "my_format::option", default)]
107///     maybe_dt: Option<OffsetDateTime>,
108/// }
109/// ```
110/// 
111/// Define the format separately to be used in multiple places:
112/// ```rust,no_run
113/// # use time::OffsetDateTime;
114#[cfg_attr(
115    all(feature = "formatting", feature = "parsing"),
116    doc = "use ::serde::{Serialize, Deserialize};"
117)]
118#[cfg_attr(
119    all(feature = "formatting", not(feature = "parsing")),
120    doc = "use ::serde::Serialize;"
121)]
122#[cfg_attr(
123    all(not(feature = "formatting"), feature = "parsing"),
124    doc = "use ::serde::Deserialize;"
125)]
126/// use time::serde;
127/// use time::format_description::StaticFormatDescription;
128///
129/// const DATE_TIME_FORMAT: StaticFormatDescription = time::macros::format_description!(
130///     "hour=[hour], minute=[minute]"
131/// );
132///
133/// // Makes a module `mod my_format { ... }`.
134/// serde::format_description!(my_format, OffsetDateTime, DATE_TIME_FORMAT);
135///
136/// # #[allow(dead_code)]
137#[cfg_attr(
138    all(feature = "formatting", feature = "parsing"),
139    doc = "#[derive(Serialize, Deserialize)]"
140)]
141#[cfg_attr(
142    all(feature = "formatting", not(feature = "parsing")),
143    doc = "#[derive(Serialize)]"
144)]
145#[cfg_attr(
146    all(not(feature = "formatting"), feature = "parsing"),
147    doc = "#[derive(Deserialize)]"
148)]
149/// struct SerializesWithCustom {
150///     #[serde(with = "my_format")]
151///     dt: OffsetDateTime,
152///     #[serde(with = "my_format::option", default)]
153///     maybe_dt: Option<OffsetDateTime>,
154/// }
155///
156/// fn main() {
157///     # #[expect(unused_variables)]
158///     let str_ts = OffsetDateTime::now_utc().format(DATE_TIME_FORMAT).unwrap();
159/// }
160/// ```
161/// 
162/// Customize the configuration of ISO 8601 formatting/parsing:
163/// ```rust,no_run
164/// # use time::OffsetDateTime;
165#[cfg_attr(
166    all(feature = "formatting", feature = "parsing"),
167    doc = "use ::serde::{Serialize, Deserialize};"
168)]
169#[cfg_attr(
170    all(feature = "formatting", not(feature = "parsing")),
171    doc = "use ::serde::Serialize;"
172)]
173#[cfg_attr(
174    all(not(feature = "formatting"), feature = "parsing"),
175    doc = "use ::serde::Deserialize;"
176)]
177/// use time::serde;
178/// use time::format_description::well_known::{iso8601, Iso8601};
179///
180/// # #[allow(dead_code)]
181/// const CONFIG: iso8601::EncodedConfig = iso8601::Config::DEFAULT
182///     .set_year_is_six_digits(false)
183///     .encode();
184/// # #[allow(dead_code)]
185/// const FORMAT: Iso8601<CONFIG> = Iso8601::<CONFIG>;
186///
187/// // Makes a module `mod my_format { ... }`.
188/// serde::format_description!(my_format, OffsetDateTime, FORMAT);
189///
190/// # #[allow(dead_code)]
191#[cfg_attr(
192    all(feature = "formatting", feature = "parsing"),
193    doc = "#[derive(Serialize, Deserialize)]"
194)]
195#[cfg_attr(
196    all(feature = "formatting", not(feature = "parsing")),
197    doc = "#[derive(Serialize)]"
198)]
199#[cfg_attr(
200    all(not(feature = "formatting"), feature = "parsing"),
201    doc = "#[derive(Deserialize)]"
202)]
203/// struct SerializesWithCustom {
204///     #[serde(with = "my_format")]
205///     dt: OffsetDateTime,
206///     #[serde(with = "my_format::option", default)]
207///     maybe_dt: Option<OffsetDateTime>,
208/// }
209/// # fn main() {}
210/// ```
211/// 
212/// [`format_description::parse()`]: crate::format_description::parse()
213#[cfg(all(feature = "macros", any(feature = "formatting", feature = "parsing")))]
214pub use time_macros::serde_format_description as format_description;
215
216use self::visitor::Visitor;
217#[cfg(feature = "parsing")]
218use crate::format_description::{BorrowedFormatItem, Component, StaticFormatDescription, modifier};
219use crate::{
220    Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcDateTime, UtcOffset, Weekday,
221};
222
223/// The format used when serializing and deserializing a human-readable `Date`.
224#[cfg(feature = "parsing")]
225const DATE_FORMAT: StaticFormatDescription = &[
226    #[cfg(feature = "large-dates")]
227    BorrowedFormatItem::Component(Component::CalendarYearFullExtendedRange(
228        modifier::CalendarYearFullExtendedRange::default(),
229    )),
230    #[cfg(not(feature = "large-dates"))]
231    BorrowedFormatItem::Component(Component::CalendarYearFullStandardRange(
232        modifier::CalendarYearFullStandardRange::default(),
233    )),
234    BorrowedFormatItem::StringLiteral("-"),
235    BorrowedFormatItem::Component(Component::MonthNumerical(
236        modifier::MonthNumerical::default(),
237    )),
238    BorrowedFormatItem::StringLiteral("-"),
239    BorrowedFormatItem::Component(Component::Day(modifier::Day::default())),
240];
241
242impl Serialize for Date {
243    #[inline]
244    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
245    where
246        S: Serializer,
247    {
248        #[cfg(feature = "serde-human-readable")]
249        if serializer.is_human_readable() {
250            let Ok(s) = self.format(&DATE_FORMAT) else {
251                return Err(S::Error::custom("failed formatting `Date`"));
252            };
253            return serializer.serialize_str(&s);
254        }
255
256        (self.year(), self.ordinal()).serialize(serializer)
257    }
258}
259
260impl<'a> Deserialize<'a> for Date {
261    #[inline]
262    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
263    where
264        D: Deserializer<'a>,
265    {
266        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
267            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
268        } else {
269            deserializer.deserialize_tuple(2, Visitor::<Self>(PhantomData))
270        }
271    }
272}
273
274impl Serialize for Duration {
275    #[inline]
276    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
277    where
278        S: Serializer,
279    {
280        #[cfg(feature = "serde-human-readable")]
281        if serializer.is_human_readable() {
282            return serializer.collect_str(&format_args!(
283                "{}{}.{:>09}",
284                if self.is_negative() { "-" } else { "" },
285                self.whole_seconds().unsigned_abs(),
286                self.subsec_nanoseconds().abs(),
287            ));
288        }
289
290        (self.whole_seconds(), self.subsec_nanoseconds()).serialize(serializer)
291    }
292}
293
294impl<'a> Deserialize<'a> for Duration {
295    #[inline]
296    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
297    where
298        D: Deserializer<'a>,
299    {
300        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
301            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
302        } else {
303            deserializer.deserialize_tuple(2, Visitor::<Self>(PhantomData))
304        }
305    }
306}
307
308/// The format used when serializing and deserializing a human-readable `OffsetDateTime`.
309#[cfg(feature = "parsing")]
310const OFFSET_DATE_TIME_FORMAT: StaticFormatDescription = &[
311    BorrowedFormatItem::Compound(DATE_FORMAT),
312    BorrowedFormatItem::StringLiteral(" "),
313    BorrowedFormatItem::Compound(TIME_FORMAT),
314    BorrowedFormatItem::StringLiteral(" "),
315    BorrowedFormatItem::Compound(UTC_OFFSET_FORMAT),
316];
317
318impl Serialize for OffsetDateTime {
319    #[inline]
320    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
321    where
322        S: Serializer,
323    {
324        #[cfg(feature = "serde-human-readable")]
325        if serializer.is_human_readable() {
326            let Ok(s) = self.format(&OFFSET_DATE_TIME_FORMAT) else {
327                return Err(S::Error::custom("failed formatting `OffsetDateTime`"));
328            };
329            return serializer.serialize_str(&s);
330        }
331
332        (
333            self.year(),
334            self.ordinal(),
335            self.hour(),
336            self.minute(),
337            self.second(),
338            self.nanosecond(),
339            self.offset().whole_hours(),
340            self.offset().minutes_past_hour(),
341            self.offset().seconds_past_minute(),
342        )
343            .serialize(serializer)
344    }
345}
346
347impl<'a> Deserialize<'a> for OffsetDateTime {
348    #[inline]
349    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
350    where
351        D: Deserializer<'a>,
352    {
353        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
354            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
355        } else {
356            deserializer.deserialize_tuple(9, Visitor::<Self>(PhantomData))
357        }
358    }
359}
360
361/// The format used when serializing and deserializing a human-readable `PrimitiveDateTime`.
362#[cfg(feature = "parsing")]
363const PRIMITIVE_DATE_TIME_FORMAT: StaticFormatDescription = &[
364    BorrowedFormatItem::Compound(DATE_FORMAT),
365    BorrowedFormatItem::StringLiteral(" "),
366    BorrowedFormatItem::Compound(TIME_FORMAT),
367];
368
369impl Serialize for PrimitiveDateTime {
370    #[inline]
371    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
372    where
373        S: Serializer,
374    {
375        #[cfg(feature = "serde-human-readable")]
376        if serializer.is_human_readable() {
377            let Ok(s) = self.format(&PRIMITIVE_DATE_TIME_FORMAT) else {
378                return Err(S::Error::custom("failed formatting `PrimitiveDateTime`"));
379            };
380            return serializer.serialize_str(&s);
381        }
382
383        (
384            self.year(),
385            self.ordinal(),
386            self.hour(),
387            self.minute(),
388            self.second(),
389            self.nanosecond(),
390        )
391            .serialize(serializer)
392    }
393}
394
395impl<'a> Deserialize<'a> for PrimitiveDateTime {
396    #[inline]
397    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
398    where
399        D: Deserializer<'a>,
400    {
401        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
402            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
403        } else {
404            deserializer.deserialize_tuple(6, Visitor::<Self>(PhantomData))
405        }
406    }
407}
408
409/// The format used when serializing and deserializing a human-readable `UtcDateTime`.
410#[cfg(feature = "parsing")]
411const UTC_DATE_TIME_FORMAT: StaticFormatDescription = PRIMITIVE_DATE_TIME_FORMAT;
412
413impl Serialize for UtcDateTime {
414    #[inline]
415    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
416    where
417        S: Serializer,
418    {
419        #[cfg(feature = "serde-human-readable")]
420        if serializer.is_human_readable() {
421            let Ok(s) = self.format(&PRIMITIVE_DATE_TIME_FORMAT) else {
422                return Err(S::Error::custom("failed formatting `UtcDateTime`"));
423            };
424            return serializer.serialize_str(&s);
425        }
426
427        (
428            self.year(),
429            self.ordinal(),
430            self.hour(),
431            self.minute(),
432            self.second(),
433            self.nanosecond(),
434        )
435            .serialize(serializer)
436    }
437}
438
439impl<'a> Deserialize<'a> for UtcDateTime {
440    #[inline]
441    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
442    where
443        D: Deserializer<'a>,
444    {
445        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
446            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
447        } else {
448            deserializer.deserialize_tuple(6, Visitor::<Self>(PhantomData))
449        }
450    }
451}
452
453/// The format used when serializing and deserializing a human-readable `Time`.
454#[cfg(feature = "parsing")]
455const TIME_FORMAT: StaticFormatDescription = &[
456    BorrowedFormatItem::Component(Component::Hour24(modifier::Hour24::default())),
457    BorrowedFormatItem::StringLiteral(":"),
458    BorrowedFormatItem::Component(Component::Minute(modifier::Minute::default())),
459    BorrowedFormatItem::StringLiteral(":"),
460    BorrowedFormatItem::Component(Component::Second(modifier::Second::default())),
461    BorrowedFormatItem::StringLiteral("."),
462    BorrowedFormatItem::Component(Component::Subsecond(modifier::Subsecond::default())),
463];
464
465impl Serialize for Time {
466    #[inline]
467    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
468    where
469        S: Serializer,
470    {
471        #[cfg(feature = "serde-human-readable")]
472        if serializer.is_human_readable() {
473            let Ok(s) = self.format(&TIME_FORMAT) else {
474                return Err(S::Error::custom("failed formatting `Time`"));
475            };
476            return serializer.serialize_str(&s);
477        }
478
479        (self.hour(), self.minute(), self.second(), self.nanosecond()).serialize(serializer)
480    }
481}
482
483impl<'a> Deserialize<'a> for Time {
484    #[inline]
485    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
486    where
487        D: Deserializer<'a>,
488    {
489        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
490            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
491        } else {
492            deserializer.deserialize_tuple(4, Visitor::<Self>(PhantomData))
493        }
494    }
495}
496
497/// The format used when serializing and deserializing a human-readable `UtcOffset`.
498#[cfg(feature = "parsing")]
499const UTC_OFFSET_FORMAT: StaticFormatDescription = &[
500    BorrowedFormatItem::Component(Component::OffsetHour(
501        const {
502            let mut m = modifier::OffsetHour::default();
503            m.sign_is_mandatory = true;
504            m
505        },
506    )),
507    BorrowedFormatItem::Optional(&BorrowedFormatItem::Compound(&[
508        BorrowedFormatItem::StringLiteral(":"),
509        BorrowedFormatItem::Component(Component::OffsetMinute(
510            const { modifier::OffsetMinute::default() },
511        )),
512        BorrowedFormatItem::Optional(&BorrowedFormatItem::Compound(&[
513            BorrowedFormatItem::StringLiteral(":"),
514            BorrowedFormatItem::Component(Component::OffsetSecond(
515                const { modifier::OffsetSecond::default() },
516            )),
517        ])),
518    ])),
519];
520
521impl Serialize for UtcOffset {
522    #[inline]
523    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
524    where
525        S: Serializer,
526    {
527        #[cfg(feature = "serde-human-readable")]
528        if serializer.is_human_readable() {
529            let Ok(s) = self.format(&UTC_OFFSET_FORMAT) else {
530                return Err(S::Error::custom("failed formatting `UtcOffset`"));
531            };
532            return serializer.serialize_str(&s);
533        }
534
535        (
536            self.whole_hours(),
537            self.minutes_past_hour(),
538            self.seconds_past_minute(),
539        )
540            .serialize(serializer)
541    }
542}
543
544impl<'a> Deserialize<'a> for UtcOffset {
545    #[inline]
546    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
547    where
548        D: Deserializer<'a>,
549    {
550        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
551            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
552        } else {
553            deserializer.deserialize_tuple(3, Visitor::<Self>(PhantomData))
554        }
555    }
556}
557
558impl Serialize for Weekday {
559    #[inline]
560    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
561    where
562        S: Serializer,
563    {
564        #[cfg(feature = "serde-human-readable")]
565        if serializer.is_human_readable() {
566            #[cfg(not(feature = "std"))]
567            use alloc::string::ToString;
568            return self.to_string().serialize(serializer);
569        }
570
571        self.number_from_monday().serialize(serializer)
572    }
573}
574
575impl<'a> Deserialize<'a> for Weekday {
576    #[inline]
577    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
578    where
579        D: Deserializer<'a>,
580    {
581        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
582            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
583        } else {
584            deserializer.deserialize_u8(Visitor::<Self>(PhantomData))
585        }
586    }
587}
588
589impl Serialize for Month {
590    #[inline]
591    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
592    where
593        S: Serializer,
594    {
595        #[cfg(feature = "serde-human-readable")]
596        if serializer.is_human_readable() {
597            #[cfg(not(feature = "std"))]
598            use alloc::string::String;
599            return self.to_string().serialize(serializer);
600        }
601
602        u8::from(*self).serialize(serializer)
603    }
604}
605
606impl<'a> Deserialize<'a> for Month {
607    #[inline]
608    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
609    where
610        D: Deserializer<'a>,
611    {
612        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
613            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
614        } else {
615            deserializer.deserialize_u8(Visitor::<Self>(PhantomData))
616        }
617    }
618}