toml_datetime/
datetime.rs

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