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