Skip to main content

toml_datetime/
datetime.rs

1use core::fmt;
2use core::str::{self, FromStr};
3
4/// A parsed TOML datetime value
5///
6/// This structure is intended to represent the datetime primitive type that can
7/// be encoded into TOML documents. This type is a parsed version that contains
8/// all metadata internally.
9///
10/// Currently this type is intentionally conservative and only supports
11/// `to_string` as an accessor. Over time though it's intended that it'll grow
12/// more support!
13///
14/// Note that if you're using `Deserialize` to deserialize a TOML document, you
15/// can use this as a placeholder for where you're expecting a datetime to be
16/// specified.
17///
18/// Also note though that while this type implements `Serialize` and
19/// `Deserialize` it's only recommended to use this type with the TOML format,
20/// otherwise encoded in other formats it may look a little odd.
21///
22/// Depending on how the option values are used, this struct will correspond
23/// with one of the following four datetimes from the [TOML v1.0.0 spec]:
24///
25/// | `date`    | `time`    | `offset`  | TOML type          |
26/// | --------- | --------- | --------- | ------------------ |
27/// | `Some(_)` | `Some(_)` | `Some(_)` | [Offset Date-Time] |
28/// | `Some(_)` | `Some(_)` | `None`    | [Local Date-Time]  |
29/// | `Some(_)` | `None`    | `None`    | [Local Date]       |
30/// | `None`    | `Some(_)` | `None`    | [Local Time]       |
31///
32/// **1. Offset Date-Time**: If all the optional values are used, `Datetime`
33/// corresponds to an [Offset Date-Time]. From the TOML v1.0.0 spec:
34///
35/// > To unambiguously represent a specific instant in time, you may use an
36/// > RFC 3339 formatted date-time with offset.
37/// >
38/// > ```toml
39/// > odt1 = 1979-05-27T07:32:00Z
40/// > odt2 = 1979-05-27T00:32:00-07:00
41/// > odt3 = 1979-05-27T00:32:00.999999-07:00
42/// > ```
43/// >
44/// > For the sake of readability, you may replace the T delimiter between date
45/// > and time with a space character (as permitted by RFC 3339 section 5.6).
46/// >
47/// > ```toml
48/// > odt4 = 1979-05-27 07:32:00Z
49/// > ```
50///
51/// **2. Local Date-Time**: If `date` and `time` are given but `offset` is
52/// `None`, `Datetime` corresponds to a [Local Date-Time]. From the spec:
53///
54/// > If you omit the offset from an RFC 3339 formatted date-time, it will
55/// > represent the given date-time without any relation to an offset or
56/// > timezone. It cannot be converted to an instant in time without additional
57/// > information. Conversion to an instant, if required, is implementation-
58/// > specific.
59/// >
60/// > ```toml
61/// > ldt1 = 1979-05-27T07:32:00
62/// > ldt2 = 1979-05-27T00:32:00.999999
63/// > ```
64///
65/// **3. Local Date**: If only `date` is given, `Datetime` corresponds to a
66/// [Local Date]; see the docs for [`Date`].
67///
68/// **4. Local Time**: If only `time` is given, `Datetime` corresponds to a
69/// [Local Time]; see the docs for [`Time`].
70///
71/// [TOML v1.0.0 spec]: https://toml.io/en/v1.0.0
72/// [Offset Date-Time]: https://toml.io/en/v1.0.0#offset-date-time
73/// [Local Date-Time]: https://toml.io/en/v1.0.0#local-date-time
74/// [Local Date]: https://toml.io/en/v1.0.0#local-date
75/// [Local Time]: https://toml.io/en/v1.0.0#local-time
76#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
77pub struct Datetime {
78    /// Optional date.
79    /// Required for: *Offset Date-Time*, *Local Date-Time*, *Local Date*.
80    pub date: Option<Date>,
81
82    /// Optional time.
83    /// Required for: *Offset Date-Time*, *Local Date-Time*, *Local Time*.
84    pub time: Option<Time>,
85
86    /// Optional offset.
87    /// Required for: *Offset Date-Time*.
88    pub offset: Option<Offset>,
89}
90
91// Currently serde itself doesn't have a datetime type, so we map our `Datetime`
92// to a special value in the serde data model. Namely one with these special
93// fields/struct names.
94//
95// In general the TOML encoder/decoder will catch this and not literally emit
96// these strings but rather emit datetimes as they're intended.
97#[cfg(feature = "serde")]
98pub(crate) const FIELD: &str = "$__toml_private_datetime";
99#[cfg(feature = "serde")]
100pub(crate) const NAME: &str = "$__toml_private_Datetime";
101#[cfg(feature = "serde")]
102pub(crate) fn is_datetime(name: &'static str) -> bool {
103    name == NAME
104}
105
106/// A parsed TOML date value
107///
108/// May be part of a [`Datetime`]. Alone, `Date` corresponds to a [Local Date].
109/// From the TOML v1.0.0 spec:
110///
111/// > If you include only the date portion of an RFC 3339 formatted date-time,
112/// > it will represent that entire day without any relation to an offset or
113/// > timezone.
114/// >
115/// > ```toml
116/// > ld1 = 1979-05-27
117/// > ```
118///
119/// [Local Date]: https://toml.io/en/v1.0.0#local-date
120#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
121pub struct Date {
122    /// Year: four digits
123    pub year: u16,
124    /// Month: 1 to 12
125    pub month: u8,
126    /// Day: 1 to {28, 29, 30, 31} (based on month/year)
127    pub day: u8,
128}
129
130/// A parsed TOML time value
131///
132/// May be part of a [`Datetime`]. Alone, `Time` corresponds to a [Local Time].
133/// From the TOML v1.0.0 spec:
134///
135/// > If you include only the time portion of an RFC 3339 formatted date-time,
136/// > it will represent that time of day without any relation to a specific
137/// > day or any offset or timezone.
138/// >
139/// > ```toml
140/// > lt1 = 07:32:00
141/// > lt2 = 00:32:00.999999
142/// > ```
143/// >
144/// > Millisecond precision is required. Further precision of fractional
145/// > seconds is implementation-specific. If the value contains greater
146/// > precision than the implementation can support, the additional precision
147/// > must be truncated, not rounded.
148///
149/// [Local Time]: https://toml.io/en/v1.0.0#local-time
150#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
151pub struct Time {
152    /// Hour: 0 to 23
153    pub hour: u8,
154    /// Minute: 0 to 59
155    pub minute: u8,
156    /// Second: 0 to {58, 59, 60} (based on leap second rules)
157    pub second: Option<u8>,
158    /// Nanosecond: 0 to `999_999_999`
159    pub nanosecond: Option<u32>,
160}
161
162/// A parsed TOML time offset
163///
164#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
165pub enum Offset {
166    /// > A suffix which, when applied to a time, denotes a UTC offset of 00:00;
167    /// > often spoken "Zulu" from the ICAO phonetic alphabet representation of
168    /// > the letter "Z". --- [RFC 3339 section 2]
169    ///
170    /// [RFC 3339 section 2]: https://datatracker.ietf.org/doc/html/rfc3339#section-2
171    Z,
172
173    /// Offset between local time and UTC
174    Custom {
175        /// Minutes: -`1_440..1_440`
176        minutes: i16,
177    },
178}
179
180impl Datetime {
181    #[cfg(feature = "serde")]
182    fn type_name(&self) -> &'static str {
183        match (
184            self.date.is_some(),
185            self.time.is_some(),
186            self.offset.is_some(),
187        ) {
188            (true, true, true) => "offset datetime",
189            (true, true, false) => "local datetime",
190            (true, false, false) => Date::type_name(),
191            (false, true, false) => Time::type_name(),
192            _ => unreachable!("unsupported datetime combination"),
193        }
194    }
195}
196
197impl Date {
198    #[cfg(feature = "serde")]
199    fn type_name() -> &'static str {
200        "local date"
201    }
202}
203
204impl Time {
205    #[cfg(feature = "serde")]
206    fn type_name() -> &'static str {
207        "local time"
208    }
209}
210
211impl From<Date> for Datetime {
212    fn from(other: Date) -> Self {
213        Self {
214            date: Some(other),
215            time: None,
216            offset: None,
217        }
218    }
219}
220
221impl From<Time> for Datetime {
222    fn from(other: Time) -> Self {
223        Self {
224            date: None,
225            time: Some(other),
226            offset: None,
227        }
228    }
229}
230
231#[cfg(feature = "alloc")]
232impl fmt::Display for Datetime {
233    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234        if let Some(ref date) = self.date {
235            write!(f, "{date}")?;
236        }
237        if let Some(ref time) = self.time {
238            if self.date.is_some() {
239                write!(f, "T")?;
240            }
241            write!(f, "{time}")?;
242        }
243        if let Some(ref offset) = self.offset {
244            write!(f, "{offset}")?;
245        }
246        Ok(())
247    }
248}
249
250impl fmt::Display for Date {
251    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252        write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day)
253    }
254}
255
256#[cfg(feature = "alloc")]
257impl fmt::Display for Time {
258    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259        write!(f, "{:02}:{:02}", self.hour, self.minute)?;
260        if let Some(second) = self
261            .second
262            .or_else(|| self.nanosecond.is_some().then_some(0))
263        {
264            write!(f, ":{second:02}")?;
265        }
266        if let Some(nanosecond) = self.nanosecond {
267            let s = alloc::format!("{nanosecond:09}");
268            let mut s = s.trim_end_matches('0');
269            if s.is_empty() {
270                s = "0";
271            }
272            write!(f, ".{s}")?;
273        }
274        Ok(())
275    }
276}
277
278impl fmt::Display for Offset {
279    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
280        match *self {
281            Self::Z => write!(f, "Z"),
282            Self::Custom { mut minutes } => {
283                let mut sign = '+';
284                if minutes < 0 {
285                    minutes *= -1;
286                    sign = '-';
287                }
288                let hours = minutes / 60;
289                let minutes = minutes % 60;
290                write!(f, "{sign}{hours:02}:{minutes:02}")
291            }
292        }
293    }
294}
295
296impl FromStr for Datetime {
297    type Err = DatetimeParseError;
298
299    fn from_str(date: &str) -> Result<Self, DatetimeParseError> {
300        // Accepted formats:
301        //
302        // 0000-00-00T00:00:00.00Z
303        // 0000-00-00T00:00:00.00
304        // 0000-00-00
305        // 00:00:00.00
306        //
307        // ```abnf
308        // ;; Date and Time (as defined in RFC 3339)
309        //
310        // date-time      = offset-date-time / local-date-time / local-date / local-time
311        //
312        // date-fullyear  = 4DIGIT
313        // date-month     = 2DIGIT  ; 01-12
314        // date-mday      = 2DIGIT  ; 01-28, 01-29, 01-30, 01-31 based on month/year
315        // time-delim     = "T" / %x20 ; T, t, or space
316        // time-hour      = 2DIGIT  ; 00-23
317        // time-minute    = 2DIGIT  ; 00-59
318        // time-second    = 2DIGIT  ; 00-58, 00-59, 00-60 based on leap second rules
319        // time-secfrac   = "." 1*DIGIT
320        // time-numoffset = ( "+" / "-" ) time-hour ":" time-minute
321        // time-offset    = "Z" / time-numoffset
322        //
323        // partial-time = time-hour ":" time-minute [ ":" time-second [ time-secfrac ] ]
324        // full-date      = date-fullyear "-" date-month "-" date-mday
325        // full-time      = partial-time time-offset
326        //
327        // ;; Offset Date-Time
328        //
329        // offset-date-time = full-date time-delim full-time
330        //
331        // ;; Local Date-Time
332        //
333        // local-date-time = full-date time-delim partial-time
334        //
335        // ;; Local Date
336        //
337        // local-date = full-date
338        //
339        // ;; Local Time
340        //
341        // local-time = partial-time
342        // ```
343        let mut result = Self {
344            date: None,
345            time: None,
346            offset: None,
347        };
348
349        let mut lexer = Lexer::new(date);
350
351        let digits = lexer
352            .next()
353            .ok_or(DatetimeParseError::new().expected("year or hour"))?;
354        digits
355            .is(TokenKind::Digits)
356            .map_err(|err| err.expected("year or hour"))?;
357        let sep = lexer
358            .next()
359            .ok_or(DatetimeParseError::new().expected("`-` (YYYY-MM) or `:` (HH:MM)"))?;
360        match sep.kind {
361            TokenKind::Dash => {
362                let year = digits;
363                let month = lexer
364                    .next()
365                    .ok_or_else(|| DatetimeParseError::new().what("date").expected("month"))?;
366                month
367                    .is(TokenKind::Digits)
368                    .map_err(|err| err.what("date").expected("month"))?;
369                let sep = lexer.next().ok_or(
370                    DatetimeParseError::new()
371                        .what("date")
372                        .expected("`-` (MM-DD)"),
373                )?;
374                sep.is(TokenKind::Dash)
375                    .map_err(|err| err.what("date").expected("`-` (MM-DD)"))?;
376                let day = lexer
377                    .next()
378                    .ok_or(DatetimeParseError::new().what("date").expected("day"))?;
379                day.is(TokenKind::Digits)
380                    .map_err(|err| err.what("date").expected("day"))?;
381
382                if year.raw.len() != 4 {
383                    return Err(DatetimeParseError::new()
384                        .what("date")
385                        .expected("a four-digit year (YYYY)"));
386                }
387                if month.raw.len() != 2 {
388                    return Err(DatetimeParseError::new()
389                        .what("date")
390                        .expected("a two-digit month (MM)"));
391                }
392                if day.raw.len() != 2 {
393                    return Err(DatetimeParseError::new()
394                        .what("date")
395                        .expected("a two-digit day (DD)"));
396                }
397                let date = Date {
398                    year: year.raw.parse().map_err(|_err| DatetimeParseError::new())?,
399                    month: month
400                        .raw
401                        .parse()
402                        .map_err(|_err| DatetimeParseError::new())?,
403                    day: day.raw.parse().map_err(|_err| DatetimeParseError::new())?,
404                };
405                if date.month < 1 || date.month > 12 {
406                    return Err(DatetimeParseError::new()
407                        .what("date")
408                        .expected("month between 01 and 12"));
409                }
410                let is_leap_year =
411                    (date.year % 4 == 0) && ((date.year % 100 != 0) || (date.year % 400 == 0));
412                let (max_days_in_month, expected_day) = match date.month {
413                    2 if is_leap_year => (29, "day between 01 and 29"),
414                    2 => (28, "day between 01 and 28"),
415                    4 | 6 | 9 | 11 => (30, "day between 01 and 30"),
416                    _ => (31, "day between 01 and 31"),
417                };
418                if date.day < 1 || date.day > max_days_in_month {
419                    return Err(DatetimeParseError::new()
420                        .what("date")
421                        .expected(expected_day));
422                }
423
424                result.date = Some(date);
425            }
426            TokenKind::Colon => lexer = Lexer::new(date),
427            _ => {
428                return Err(DatetimeParseError::new().expected("`-` (YYYY-MM) or `:` (HH:MM)"));
429            }
430        }
431
432        // Next parse the "partial-time" if available
433        let partial_time = if result.date.is_some() {
434            let sep = lexer.next();
435            match sep {
436                Some(token) if matches!(token.kind, TokenKind::T | TokenKind::Space) => true,
437                Some(_token) => {
438                    return Err(DatetimeParseError::new()
439                        .what("date-time")
440                        .expected("`T` between date and time"));
441                }
442                None => false,
443            }
444        } else {
445            result.date.is_none()
446        };
447
448        if partial_time {
449            let hour = lexer
450                .next()
451                .ok_or_else(|| DatetimeParseError::new().what("time").expected("hour"))?;
452            hour.is(TokenKind::Digits)
453                .map_err(|err| err.what("time").expected("hour"))?;
454            let sep = lexer.next().ok_or(
455                DatetimeParseError::new()
456                    .what("time")
457                    .expected("`:` (HH:MM)"),
458            )?;
459            sep.is(TokenKind::Colon)
460                .map_err(|err| err.what("time").expected("`:` (HH:MM)"))?;
461            let minute = lexer
462                .next()
463                .ok_or(DatetimeParseError::new().what("time").expected("minute"))?;
464            minute
465                .is(TokenKind::Digits)
466                .map_err(|err| err.what("time").expected("minute"))?;
467            let second = if lexer.clone().next().map(|t| t.kind) == Some(TokenKind::Colon) {
468                let sep = lexer.next().ok_or(DatetimeParseError::new())?;
469                sep.is(TokenKind::Colon)?;
470                let second = lexer
471                    .next()
472                    .ok_or(DatetimeParseError::new().what("time").expected("second"))?;
473                second
474                    .is(TokenKind::Digits)
475                    .map_err(|err| err.what("time").expected("second"))?;
476                Some(second)
477            } else {
478                None
479            };
480
481            let nanosecond = if second.is_some()
482                && lexer.clone().next().map(|t| t.kind) == Some(TokenKind::Dot)
483            {
484                let sep = lexer.next().ok_or(DatetimeParseError::new())?;
485                sep.is(TokenKind::Dot)?;
486                let nanosecond = lexer.next().ok_or(
487                    DatetimeParseError::new()
488                        .what("time")
489                        .expected("nanosecond"),
490                )?;
491                nanosecond
492                    .is(TokenKind::Digits)
493                    .map_err(|err| err.what("time").expected("nanosecond"))?;
494                Some(nanosecond)
495            } else {
496                None
497            };
498
499            if hour.raw.len() != 2 {
500                return Err(DatetimeParseError::new()
501                    .what("time")
502                    .expected("a two-digit hour (HH)"));
503            }
504            if minute.raw.len() != 2 {
505                return Err(DatetimeParseError::new()
506                    .what("time")
507                    .expected("a two-digit minute (MM)"));
508            }
509            if let Some(second) = second {
510                if second.raw.len() != 2 {
511                    return Err(DatetimeParseError::new()
512                        .what("time")
513                        .expected("a two-digit second (SS)"));
514                }
515            }
516
517            let time = Time {
518                hour: hour.raw.parse().map_err(|_err| DatetimeParseError::new())?,
519                minute: minute
520                    .raw
521                    .parse()
522                    .map_err(|_err| DatetimeParseError::new())?,
523                second: second
524                    .map(|t| t.raw.parse().map_err(|_err| DatetimeParseError::new()))
525                    .transpose()?,
526                nanosecond: nanosecond.map(|t| s_to_nanoseconds(t.raw)),
527            };
528
529            if time.hour > 23 {
530                return Err(DatetimeParseError::new()
531                    .what("time")
532                    .expected("hour between 00 and 23"));
533            }
534            if time.minute > 59 {
535                return Err(DatetimeParseError::new()
536                    .what("time")
537                    .expected("minute between 00 and 59"));
538            }
539            // 00-58, 00-59, 00-60 based on leap second rules
540            if time.second.unwrap_or(0) > 60 {
541                return Err(DatetimeParseError::new()
542                    .what("time")
543                    .expected("second between 00 and 60"));
544            }
545            if time.nanosecond.unwrap_or(0) > 999_999_999 {
546                return Err(DatetimeParseError::new()
547                    .what("time")
548                    .expected("nanoseconds overflowed"));
549            }
550
551            result.time = Some(time);
552        }
553
554        // And finally, parse the offset
555        if result.date.is_some() && result.time.is_some() {
556            match lexer.next() {
557                Some(token) if token.kind == TokenKind::Z => {
558                    result.offset = Some(Offset::Z);
559                }
560                Some(token) if matches!(token.kind, TokenKind::Plus | TokenKind::Dash) => {
561                    let sign = if token.kind == TokenKind::Plus { 1 } else { -1 };
562                    let hours = lexer
563                        .next()
564                        .ok_or(DatetimeParseError::new().what("offset").expected("hour"))?;
565                    hours
566                        .is(TokenKind::Digits)
567                        .map_err(|err| err.what("offset").expected("hour"))?;
568                    let sep = lexer.next().ok_or(
569                        DatetimeParseError::new()
570                            .what("offset")
571                            .expected("`:` (HH:MM)"),
572                    )?;
573                    sep.is(TokenKind::Colon)
574                        .map_err(|err| err.what("offset").expected("`:` (HH:MM)"))?;
575                    let minutes = lexer
576                        .next()
577                        .ok_or(DatetimeParseError::new().what("offset").expected("minute"))?;
578                    minutes
579                        .is(TokenKind::Digits)
580                        .map_err(|err| err.what("offset").expected("minute"))?;
581
582                    if hours.raw.len() != 2 {
583                        return Err(DatetimeParseError::new()
584                            .what("offset")
585                            .expected("a two-digit hour (HH)"));
586                    }
587                    if minutes.raw.len() != 2 {
588                        return Err(DatetimeParseError::new()
589                            .what("offset")
590                            .expected("a two-digit minute (MM)"));
591                    }
592
593                    let hours = hours
594                        .raw
595                        .parse::<u8>()
596                        .map_err(|_err| DatetimeParseError::new())?;
597                    let minutes = minutes
598                        .raw
599                        .parse::<u8>()
600                        .map_err(|_err| DatetimeParseError::new())?;
601
602                    if hours > 23 {
603                        return Err(DatetimeParseError::new()
604                            .what("offset")
605                            .expected("hours between 00 and 23"));
606                    }
607                    if minutes > 59 {
608                        return Err(DatetimeParseError::new()
609                            .what("offset")
610                            .expected("minutes between 00 and 59"));
611                    }
612
613                    let total_minutes = sign * (hours as i16 * 60 + minutes as i16);
614
615                    if !((-24 * 60)..=(24 * 60)).contains(&total_minutes) {
616                        return Err(DatetimeParseError::new().what("offset"));
617                    }
618
619                    result.offset = Some(Offset::Custom {
620                        minutes: total_minutes,
621                    });
622                }
623                Some(_token) => {
624                    return Err(DatetimeParseError::new()
625                        .what("offset")
626                        .expected("`Z`, +OFFSET, -OFFSET"));
627                }
628                None => {}
629            }
630        }
631
632        // Return an error if we didn't hit eof, otherwise return our parsed
633        // date
634        if lexer.unknown().is_some() {
635            return Err(DatetimeParseError::new());
636        }
637
638        Ok(result)
639    }
640}
641
642fn s_to_nanoseconds(input: &str) -> u32 {
643    let mut nanosecond = 0;
644    for (i, byte) in input.bytes().enumerate() {
645        if byte.is_ascii_digit() {
646            if i < 9 {
647                let p = 10_u32.pow(8 - i as u32);
648                nanosecond += p * u32::from(byte - b'0');
649            }
650        } else {
651            panic!("invalid nanoseconds {input:?}");
652        }
653    }
654    nanosecond
655}
656
657#[derive(Copy, Clone)]
658struct Token<'s> {
659    kind: TokenKind,
660    raw: &'s str,
661}
662
663impl Token<'_> {
664    fn is(&self, kind: TokenKind) -> Result<(), DatetimeParseError> {
665        if self.kind == kind {
666            Ok(())
667        } else {
668            Err(DatetimeParseError::new())
669        }
670    }
671}
672
673#[derive(Copy, Clone, PartialEq, Eq)]
674enum TokenKind {
675    Digits,
676    Dash,
677    Colon,
678    Dot,
679    T,
680    Space,
681    Z,
682    Plus,
683    Unknown,
684}
685
686#[derive(Copy, Clone)]
687struct Lexer<'s> {
688    stream: &'s str,
689}
690
691impl<'s> Lexer<'s> {
692    fn new(input: &'s str) -> Self {
693        Self { stream: input }
694    }
695
696    fn unknown(&mut self) -> Option<Token<'s>> {
697        let remaining = self.stream.len();
698        if remaining == 0 {
699            return None;
700        }
701        let raw = self.stream;
702        self.stream = &self.stream[remaining..remaining];
703        Some(Token {
704            kind: TokenKind::Unknown,
705            raw,
706        })
707    }
708}
709
710impl<'s> Iterator for Lexer<'s> {
711    type Item = Token<'s>;
712
713    fn next(&mut self) -> Option<Self::Item> {
714        let (kind, end) = match self.stream.as_bytes().first()? {
715            b'0'..=b'9' => {
716                let end = self
717                    .stream
718                    .as_bytes()
719                    .iter()
720                    .position(|b| !b.is_ascii_digit())
721                    .unwrap_or(self.stream.len());
722                (TokenKind::Digits, end)
723            }
724            b'-' => (TokenKind::Dash, 1),
725            b':' => (TokenKind::Colon, 1),
726            b'T' | b't' => (TokenKind::T, 1),
727            b' ' => (TokenKind::Space, 1),
728            b'Z' | b'z' => (TokenKind::Z, 1),
729            b'+' => (TokenKind::Plus, 1),
730            b'.' => (TokenKind::Dot, 1),
731            _ => (TokenKind::Unknown, self.stream.len()),
732        };
733        let (raw, rest) = self.stream.split_at(end);
734        self.stream = rest;
735        Some(Token { kind, raw })
736    }
737}
738
739/// Error returned from parsing a `Datetime` in the `FromStr` implementation.
740#[derive(Debug, Clone)]
741#[non_exhaustive]
742pub struct DatetimeParseError {
743    what: Option<&'static str>,
744    expected: Option<&'static str>,
745}
746
747impl DatetimeParseError {
748    fn new() -> Self {
749        Self {
750            what: None,
751            expected: None,
752        }
753    }
754    fn what(mut self, what: &'static str) -> Self {
755        self.what = Some(what);
756        self
757    }
758    fn expected(mut self, expected: &'static str) -> Self {
759        self.expected = Some(expected);
760        self
761    }
762}
763
764impl fmt::Display for DatetimeParseError {
765    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
766        if let Some(what) = self.what {
767            write!(f, "invalid {what}")?;
768        } else {
769            "invalid datetime".fmt(f)?;
770        }
771        if let Some(expected) = self.expected {
772            write!(f, ", expected {expected}")?;
773        }
774        Ok(())
775    }
776}
777
778#[cfg(feature = "std")]
779impl std::error::Error for DatetimeParseError {}
780#[cfg(all(not(feature = "std"), feature = "serde"))]
781impl serde_core::de::StdError for DatetimeParseError {}
782
783#[cfg(feature = "serde")]
784#[cfg(feature = "alloc")]
785impl serde_core::ser::Serialize for Datetime {
786    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
787    where
788        S: serde_core::ser::Serializer,
789    {
790        use crate::alloc::string::ToString as _;
791        use serde_core::ser::SerializeStruct;
792
793        let mut s = serializer.serialize_struct(NAME, 1)?;
794        s.serialize_field(FIELD, &self.to_string())?;
795        s.end()
796    }
797}
798
799#[cfg(feature = "serde")]
800#[cfg(feature = "alloc")]
801impl serde_core::ser::Serialize for Date {
802    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
803    where
804        S: serde_core::ser::Serializer,
805    {
806        Datetime::from(*self).serialize(serializer)
807    }
808}
809
810#[cfg(feature = "serde")]
811#[cfg(feature = "alloc")]
812impl serde_core::ser::Serialize for Time {
813    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
814    where
815        S: serde_core::ser::Serializer,
816    {
817        Datetime::from(*self).serialize(serializer)
818    }
819}
820
821#[cfg(feature = "serde")]
822impl<'de> serde_core::de::Deserialize<'de> for Datetime {
823    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
824    where
825        D: serde_core::de::Deserializer<'de>,
826    {
827        struct DatetimeVisitor;
828
829        impl<'de> serde_core::de::Visitor<'de> for DatetimeVisitor {
830            type Value = Datetime;
831
832            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
833                formatter.write_str("a TOML datetime")
834            }
835
836            fn visit_map<V>(self, mut visitor: V) -> Result<Datetime, V::Error>
837            where
838                V: serde_core::de::MapAccess<'de>,
839            {
840                let value = visitor.next_key::<DatetimeKey>()?;
841                if value.is_none() {
842                    return Err(serde_core::de::Error::custom("datetime key not found"));
843                }
844                let v: DatetimeFromString = visitor.next_value()?;
845                Ok(v.value)
846            }
847        }
848
849        static FIELDS: [&str; 1] = [FIELD];
850        deserializer.deserialize_struct(NAME, &FIELDS, DatetimeVisitor)
851    }
852}
853
854#[cfg(feature = "serde")]
855impl<'de> serde_core::de::Deserialize<'de> for Date {
856    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
857    where
858        D: serde_core::de::Deserializer<'de>,
859    {
860        match Datetime::deserialize(deserializer)? {
861            Datetime {
862                date: Some(date),
863                time: None,
864                offset: None,
865            } => Ok(date),
866            datetime => Err(serde_core::de::Error::invalid_type(
867                serde_core::de::Unexpected::Other(datetime.type_name()),
868                &Self::type_name(),
869            )),
870        }
871    }
872}
873
874#[cfg(feature = "serde")]
875impl<'de> serde_core::de::Deserialize<'de> for Time {
876    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
877    where
878        D: serde_core::de::Deserializer<'de>,
879    {
880        match Datetime::deserialize(deserializer)? {
881            Datetime {
882                date: None,
883                time: Some(time),
884                offset: None,
885            } => Ok(time),
886            datetime => Err(serde_core::de::Error::invalid_type(
887                serde_core::de::Unexpected::Other(datetime.type_name()),
888                &Self::type_name(),
889            )),
890        }
891    }
892}
893
894#[cfg(feature = "serde")]
895struct DatetimeKey;
896
897#[cfg(feature = "serde")]
898impl<'de> serde_core::de::Deserialize<'de> for DatetimeKey {
899    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
900    where
901        D: serde_core::de::Deserializer<'de>,
902    {
903        struct FieldVisitor;
904
905        impl serde_core::de::Visitor<'_> for FieldVisitor {
906            type Value = ();
907
908            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
909                formatter.write_str("a valid datetime field")
910            }
911
912            fn visit_str<E>(self, s: &str) -> Result<(), E>
913            where
914                E: serde_core::de::Error,
915            {
916                if s == FIELD {
917                    Ok(())
918                } else {
919                    Err(serde_core::de::Error::custom(
920                        "expected field with custom name",
921                    ))
922                }
923            }
924        }
925
926        deserializer.deserialize_identifier(FieldVisitor)?;
927        Ok(Self)
928    }
929}
930
931#[cfg(feature = "serde")]
932pub(crate) struct DatetimeFromString {
933    pub(crate) value: Datetime,
934}
935
936#[cfg(feature = "serde")]
937impl<'de> serde_core::de::Deserialize<'de> for DatetimeFromString {
938    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
939    where
940        D: serde_core::de::Deserializer<'de>,
941    {
942        struct Visitor;
943
944        impl serde_core::de::Visitor<'_> for Visitor {
945            type Value = DatetimeFromString;
946
947            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
948                formatter.write_str("string containing a datetime")
949            }
950
951            fn visit_str<E>(self, s: &str) -> Result<DatetimeFromString, E>
952            where
953                E: serde_core::de::Error,
954            {
955                match s.parse() {
956                    Ok(date) => Ok(DatetimeFromString { value: date }),
957                    Err(e) => Err(serde_core::de::Error::custom(e)),
958                }
959            }
960        }
961
962        deserializer.deserialize_str(Visitor)
963    }
964}