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    BorrowedFormatItem::Component(Component::Year(modifier::Year::default())),
227    BorrowedFormatItem::Literal(b"-"),
228    BorrowedFormatItem::Component(Component::Month(modifier::Month::default())),
229    BorrowedFormatItem::Literal(b"-"),
230    BorrowedFormatItem::Component(Component::Day(modifier::Day::default())),
231];
232
233impl Serialize for Date {
234    #[inline]
235    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
236    where
237        S: Serializer,
238    {
239        #[cfg(feature = "serde-human-readable")]
240        if serializer.is_human_readable() {
241            let Ok(s) = self.format(&DATE_FORMAT) else {
242                return Err(S::Error::custom("failed formatting `Date`"));
243            };
244            return serializer.serialize_str(&s);
245        }
246
247        (self.year(), self.ordinal()).serialize(serializer)
248    }
249}
250
251impl<'a> Deserialize<'a> for Date {
252    #[inline]
253    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
254    where
255        D: Deserializer<'a>,
256    {
257        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
258            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
259        } else {
260            deserializer.deserialize_tuple(2, Visitor::<Self>(PhantomData))
261        }
262    }
263}
264
265impl Serialize for Duration {
266    #[inline]
267    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
268    where
269        S: Serializer,
270    {
271        #[cfg(feature = "serde-human-readable")]
272        if serializer.is_human_readable() {
273            return serializer.collect_str(&format_args!(
274                "{}{}.{:>09}",
275                if self.is_negative() { "-" } else { "" },
276                self.whole_seconds().unsigned_abs(),
277                self.subsec_nanoseconds().abs(),
278            ));
279        }
280
281        (self.whole_seconds(), self.subsec_nanoseconds()).serialize(serializer)
282    }
283}
284
285impl<'a> Deserialize<'a> for Duration {
286    #[inline]
287    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
288    where
289        D: Deserializer<'a>,
290    {
291        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
292            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
293        } else {
294            deserializer.deserialize_tuple(2, Visitor::<Self>(PhantomData))
295        }
296    }
297}
298
299/// The format used when serializing and deserializing a human-readable `OffsetDateTime`.
300#[cfg(feature = "parsing")]
301const OFFSET_DATE_TIME_FORMAT: StaticFormatDescription = &[
302    BorrowedFormatItem::Compound(DATE_FORMAT),
303    BorrowedFormatItem::Literal(b" "),
304    BorrowedFormatItem::Compound(TIME_FORMAT),
305    BorrowedFormatItem::Literal(b" "),
306    BorrowedFormatItem::Compound(UTC_OFFSET_FORMAT),
307];
308
309impl Serialize for OffsetDateTime {
310    #[inline]
311    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
312    where
313        S: Serializer,
314    {
315        #[cfg(feature = "serde-human-readable")]
316        if serializer.is_human_readable() {
317            let Ok(s) = self.format(&OFFSET_DATE_TIME_FORMAT) else {
318                return Err(S::Error::custom("failed formatting `OffsetDateTime`"));
319            };
320            return serializer.serialize_str(&s);
321        }
322
323        (
324            self.year(),
325            self.ordinal(),
326            self.hour(),
327            self.minute(),
328            self.second(),
329            self.nanosecond(),
330            self.offset().whole_hours(),
331            self.offset().minutes_past_hour(),
332            self.offset().seconds_past_minute(),
333        )
334            .serialize(serializer)
335    }
336}
337
338impl<'a> Deserialize<'a> for OffsetDateTime {
339    #[inline]
340    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
341    where
342        D: Deserializer<'a>,
343    {
344        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
345            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
346        } else {
347            deserializer.deserialize_tuple(9, Visitor::<Self>(PhantomData))
348        }
349    }
350}
351
352/// The format used when serializing and deserializing a human-readable `PrimitiveDateTime`.
353#[cfg(feature = "parsing")]
354const PRIMITIVE_DATE_TIME_FORMAT: StaticFormatDescription = &[
355    BorrowedFormatItem::Compound(DATE_FORMAT),
356    BorrowedFormatItem::Literal(b" "),
357    BorrowedFormatItem::Compound(TIME_FORMAT),
358];
359
360impl Serialize for PrimitiveDateTime {
361    #[inline]
362    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
363    where
364        S: Serializer,
365    {
366        #[cfg(feature = "serde-human-readable")]
367        if serializer.is_human_readable() {
368            let Ok(s) = self.format(&PRIMITIVE_DATE_TIME_FORMAT) else {
369                return Err(S::Error::custom("failed formatting `PrimitiveDateTime`"));
370            };
371            return serializer.serialize_str(&s);
372        }
373
374        (
375            self.year(),
376            self.ordinal(),
377            self.hour(),
378            self.minute(),
379            self.second(),
380            self.nanosecond(),
381        )
382            .serialize(serializer)
383    }
384}
385
386impl<'a> Deserialize<'a> for PrimitiveDateTime {
387    #[inline]
388    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
389    where
390        D: Deserializer<'a>,
391    {
392        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
393            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
394        } else {
395            deserializer.deserialize_tuple(6, Visitor::<Self>(PhantomData))
396        }
397    }
398}
399
400/// The format used when serializing and deserializing a human-readable `UtcDateTime`.
401#[cfg(feature = "parsing")]
402const UTC_DATE_TIME_FORMAT: StaticFormatDescription = PRIMITIVE_DATE_TIME_FORMAT;
403
404impl Serialize for UtcDateTime {
405    #[inline]
406    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
407    where
408        S: Serializer,
409    {
410        #[cfg(feature = "serde-human-readable")]
411        if serializer.is_human_readable() {
412            let Ok(s) = self.format(&PRIMITIVE_DATE_TIME_FORMAT) else {
413                return Err(S::Error::custom("failed formatting `UtcDateTime`"));
414            };
415            return serializer.serialize_str(&s);
416        }
417
418        (
419            self.year(),
420            self.ordinal(),
421            self.hour(),
422            self.minute(),
423            self.second(),
424            self.nanosecond(),
425        )
426            .serialize(serializer)
427    }
428}
429
430impl<'a> Deserialize<'a> for UtcDateTime {
431    #[inline]
432    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
433    where
434        D: Deserializer<'a>,
435    {
436        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
437            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
438        } else {
439            deserializer.deserialize_tuple(6, Visitor::<Self>(PhantomData))
440        }
441    }
442}
443
444/// The format used when serializing and deserializing a human-readable `Time`.
445#[cfg(feature = "parsing")]
446const TIME_FORMAT: StaticFormatDescription = &[
447    BorrowedFormatItem::Component(Component::Hour(modifier::Hour::default())),
448    BorrowedFormatItem::Literal(b":"),
449    BorrowedFormatItem::Component(Component::Minute(modifier::Minute::default())),
450    BorrowedFormatItem::Literal(b":"),
451    BorrowedFormatItem::Component(Component::Second(modifier::Second::default())),
452    BorrowedFormatItem::Literal(b"."),
453    BorrowedFormatItem::Component(Component::Subsecond(modifier::Subsecond::default())),
454];
455
456impl Serialize for Time {
457    #[inline]
458    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
459    where
460        S: Serializer,
461    {
462        #[cfg(feature = "serde-human-readable")]
463        if serializer.is_human_readable() {
464            let Ok(s) = self.format(&TIME_FORMAT) else {
465                return Err(S::Error::custom("failed formatting `Time`"));
466            };
467            return serializer.serialize_str(&s);
468        }
469
470        (self.hour(), self.minute(), self.second(), self.nanosecond()).serialize(serializer)
471    }
472}
473
474impl<'a> Deserialize<'a> for Time {
475    #[inline]
476    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
477    where
478        D: Deserializer<'a>,
479    {
480        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
481            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
482        } else {
483            deserializer.deserialize_tuple(4, Visitor::<Self>(PhantomData))
484        }
485    }
486}
487
488/// The format used when serializing and deserializing a human-readable `UtcOffset`.
489#[cfg(feature = "parsing")]
490const UTC_OFFSET_FORMAT: StaticFormatDescription = &[
491    BorrowedFormatItem::Component(Component::OffsetHour(
492        const {
493            let mut m = modifier::OffsetHour::default();
494            m.sign_is_mandatory = true;
495            m
496        },
497    )),
498    BorrowedFormatItem::Optional(&BorrowedFormatItem::Compound(&[
499        BorrowedFormatItem::Literal(b":"),
500        BorrowedFormatItem::Component(Component::OffsetMinute(
501            const { modifier::OffsetMinute::default() },
502        )),
503        BorrowedFormatItem::Optional(&BorrowedFormatItem::Compound(&[
504            BorrowedFormatItem::Literal(b":"),
505            BorrowedFormatItem::Component(Component::OffsetSecond(
506                const { modifier::OffsetSecond::default() },
507            )),
508        ])),
509    ])),
510];
511
512impl Serialize for UtcOffset {
513    #[inline]
514    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
515    where
516        S: Serializer,
517    {
518        #[cfg(feature = "serde-human-readable")]
519        if serializer.is_human_readable() {
520            let Ok(s) = self.format(&UTC_OFFSET_FORMAT) else {
521                return Err(S::Error::custom("failed formatting `UtcOffset`"));
522            };
523            return serializer.serialize_str(&s);
524        }
525
526        (
527            self.whole_hours(),
528            self.minutes_past_hour(),
529            self.seconds_past_minute(),
530        )
531            .serialize(serializer)
532    }
533}
534
535impl<'a> Deserialize<'a> for UtcOffset {
536    #[inline]
537    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
538    where
539        D: Deserializer<'a>,
540    {
541        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
542            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
543        } else {
544            deserializer.deserialize_tuple(3, Visitor::<Self>(PhantomData))
545        }
546    }
547}
548
549impl Serialize for Weekday {
550    #[inline]
551    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
552    where
553        S: Serializer,
554    {
555        #[cfg(feature = "serde-human-readable")]
556        if serializer.is_human_readable() {
557            #[cfg(not(feature = "std"))]
558            use alloc::string::ToString;
559            return self.to_string().serialize(serializer);
560        }
561
562        self.number_from_monday().serialize(serializer)
563    }
564}
565
566impl<'a> Deserialize<'a> for Weekday {
567    #[inline]
568    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
569    where
570        D: Deserializer<'a>,
571    {
572        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
573            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
574        } else {
575            deserializer.deserialize_u8(Visitor::<Self>(PhantomData))
576        }
577    }
578}
579
580impl Serialize for Month {
581    #[inline]
582    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
583    where
584        S: Serializer,
585    {
586        #[cfg(feature = "serde-human-readable")]
587        if serializer.is_human_readable() {
588            #[cfg(not(feature = "std"))]
589            use alloc::string::String;
590            return self.to_string().serialize(serializer);
591        }
592
593        u8::from(*self).serialize(serializer)
594    }
595}
596
597impl<'a> Deserialize<'a> for Month {
598    #[inline]
599    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
600    where
601        D: Deserializer<'a>,
602    {
603        if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
604            deserializer.deserialize_any(Visitor::<Self>(PhantomData))
605        } else {
606            deserializer.deserialize_u8(Visitor::<Self>(PhantomData))
607        }
608    }
609}