toml/
datetime.rs

1use std::error;
2use std::fmt;
3use std::str::{self, FromStr};
4
5use serde::{de, ser};
6
7/// A parsed TOML datetime value
8///
9/// This structure is intended to represent the datetime primitive type that can
10/// be encoded into TOML documents. This type is a parsed version that contains
11/// all metadata internally.
12///
13/// Currently this type is intentionally conservative and only supports
14/// `to_string` as an accessor. Over time though it's intended that it'll grow
15/// more support!
16///
17/// Note that if you're using `Deserialize` to deserialize a TOML document, you
18/// can use this as a placeholder for where you're expecting a datetime to be
19/// specified.
20///
21/// Also note though that while this type implements `Serialize` and
22/// `Deserialize` it's only recommended to use this type with the TOML format,
23/// otherwise encoded in other formats it may look a little odd.
24#[derive(PartialEq, Clone)]
25pub struct Datetime {
26    date: Option<Date>,
27    time: Option<Time>,
28    offset: Option<Offset>,
29}
30
31/// Error returned from parsing a `Datetime` in the `FromStr` implementation.
32#[derive(Debug, Clone)]
33pub struct DatetimeParseError {
34    _private: (),
35}
36
37// Currently serde itself doesn't have a datetime type, so we map our `Datetime`
38// to a special valid in the serde data model. Namely one with thiese special
39// fields/struct names.
40//
41// In general the TOML encoder/decoder will catch this and not literally emit
42// these strings but rather emit datetimes as they're intended.
43pub const FIELD: &str = "$__toml_private_datetime";
44pub const NAME: &str = "$__toml_private_Datetime";
45
46#[derive(PartialEq, Clone)]
47struct Date {
48    year: u16,
49    month: u8,
50    day: u8,
51}
52
53#[derive(PartialEq, Clone)]
54struct Time {
55    hour: u8,
56    minute: u8,
57    second: u8,
58    nanosecond: u32,
59}
60
61#[derive(PartialEq, Clone)]
62enum Offset {
63    Z,
64    Custom { hours: i8, minutes: u8 },
65}
66
67impl fmt::Debug for Datetime {
68    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69        fmt::Display::fmt(self, f)
70    }
71}
72
73impl fmt::Display for Datetime {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        if let Some(ref date) = self.date {
76            write!(f, "{}", date)?;
77        }
78        if let Some(ref time) = self.time {
79            if self.date.is_some() {
80                write!(f, "T")?;
81            }
82            write!(f, "{}", time)?;
83        }
84        if let Some(ref offset) = self.offset {
85            write!(f, "{}", offset)?;
86        }
87        Ok(())
88    }
89}
90
91impl fmt::Display for Date {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day)
94    }
95}
96
97impl fmt::Display for Time {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        write!(f, "{:02}:{:02}:{:02}", self.hour, self.minute, self.second)?;
100        if self.nanosecond != 0 {
101            let s = format!("{:09}", self.nanosecond);
102            write!(f, ".{}", s.trim_end_matches('0'))?;
103        }
104        Ok(())
105    }
106}
107
108impl fmt::Display for Offset {
109    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110        match *self {
111            Offset::Z => write!(f, "Z"),
112            Offset::Custom { hours, minutes } => write!(f, "{:+03}:{:02}", hours, minutes),
113        }
114    }
115}
116
117impl FromStr for Datetime {
118    type Err = DatetimeParseError;
119
120    fn from_str(date: &str) -> Result<Datetime, DatetimeParseError> {
121        // Accepted formats:
122        //
123        // 0000-00-00T00:00:00.00Z
124        // 0000-00-00T00:00:00.00
125        // 0000-00-00
126        // 00:00:00.00
127        if date.len() < 3 {
128            return Err(DatetimeParseError { _private: () });
129        }
130        let mut offset_allowed = true;
131        let mut chars = date.chars();
132
133        // First up, parse the full date if we can
134        let full_date = if chars.clone().nth(2) == Some(':') {
135            offset_allowed = false;
136            None
137        } else {
138            let y1 = u16::from(digit(&mut chars)?);
139            let y2 = u16::from(digit(&mut chars)?);
140            let y3 = u16::from(digit(&mut chars)?);
141            let y4 = u16::from(digit(&mut chars)?);
142
143            match chars.next() {
144                Some('-') => {}
145                _ => return Err(DatetimeParseError { _private: () }),
146            }
147
148            let m1 = digit(&mut chars)?;
149            let m2 = digit(&mut chars)?;
150
151            match chars.next() {
152                Some('-') => {}
153                _ => return Err(DatetimeParseError { _private: () }),
154            }
155
156            let d1 = digit(&mut chars)?;
157            let d2 = digit(&mut chars)?;
158
159            let date = Date {
160                year: y1 * 1000 + y2 * 100 + y3 * 10 + y4,
161                month: m1 * 10 + m2,
162                day: d1 * 10 + d2,
163            };
164
165            if date.month < 1 || date.month > 12 {
166                return Err(DatetimeParseError { _private: () });
167            }
168            if date.day < 1 || date.day > 31 {
169                return Err(DatetimeParseError { _private: () });
170            }
171
172            Some(date)
173        };
174
175        // Next parse the "partial-time" if available
176        let next = chars.clone().next();
177        let partial_time = if full_date.is_some()
178            && (next == Some('T') || next == Some('t') || next == Some(' '))
179        {
180            chars.next();
181            true
182        } else {
183            full_date.is_none()
184        };
185
186        let time = if partial_time {
187            let h1 = digit(&mut chars)?;
188            let h2 = digit(&mut chars)?;
189            match chars.next() {
190                Some(':') => {}
191                _ => return Err(DatetimeParseError { _private: () }),
192            }
193            let m1 = digit(&mut chars)?;
194            let m2 = digit(&mut chars)?;
195            match chars.next() {
196                Some(':') => {}
197                _ => return Err(DatetimeParseError { _private: () }),
198            }
199            let s1 = digit(&mut chars)?;
200            let s2 = digit(&mut chars)?;
201
202            let mut nanosecond = 0;
203            if chars.clone().next() == Some('.') {
204                chars.next();
205                let whole = chars.as_str();
206
207                let mut end = whole.len();
208                for (i, byte) in whole.bytes().enumerate() {
209                    match byte {
210                        b'0'..=b'9' => {
211                            if i < 9 {
212                                let p = 10_u32.pow(8 - i as u32);
213                                nanosecond += p * u32::from(byte - b'0');
214                            }
215                        }
216                        _ => {
217                            end = i;
218                            break;
219                        }
220                    }
221                }
222                if end == 0 {
223                    return Err(DatetimeParseError { _private: () });
224                }
225                chars = whole[end..].chars();
226            }
227
228            let time = Time {
229                hour: h1 * 10 + h2,
230                minute: m1 * 10 + m2,
231                second: s1 * 10 + s2,
232                nanosecond,
233            };
234
235            if time.hour > 24 {
236                return Err(DatetimeParseError { _private: () });
237            }
238            if time.minute > 59 {
239                return Err(DatetimeParseError { _private: () });
240            }
241            if time.second > 59 {
242                return Err(DatetimeParseError { _private: () });
243            }
244            if time.nanosecond > 999_999_999 {
245                return Err(DatetimeParseError { _private: () });
246            }
247
248            Some(time)
249        } else {
250            offset_allowed = false;
251            None
252        };
253
254        // And finally, parse the offset
255        let offset = if offset_allowed {
256            let next = chars.clone().next();
257            if next == Some('Z') || next == Some('z') {
258                chars.next();
259                Some(Offset::Z)
260            } else if next.is_none() {
261                None
262            } else {
263                let sign = match next {
264                    Some('+') => 1,
265                    Some('-') => -1,
266                    _ => return Err(DatetimeParseError { _private: () }),
267                };
268                chars.next();
269                let h1 = digit(&mut chars)? as i8;
270                let h2 = digit(&mut chars)? as i8;
271                match chars.next() {
272                    Some(':') => {}
273                    _ => return Err(DatetimeParseError { _private: () }),
274                }
275                let m1 = digit(&mut chars)?;
276                let m2 = digit(&mut chars)?;
277
278                Some(Offset::Custom {
279                    hours: sign * (h1 * 10 + h2),
280                    minutes: m1 * 10 + m2,
281                })
282            }
283        } else {
284            None
285        };
286
287        // Return an error if we didn't hit eof, otherwise return our parsed
288        // date
289        if chars.next().is_some() {
290            return Err(DatetimeParseError { _private: () });
291        }
292
293        Ok(Datetime {
294            date: full_date,
295            time,
296            offset,
297        })
298    }
299}
300
301fn digit(chars: &mut str::Chars<'_>) -> Result<u8, DatetimeParseError> {
302    match chars.next() {
303        Some(c) if '0' <= c && c <= '9' => Ok(c as u8 - b'0'),
304        _ => Err(DatetimeParseError { _private: () }),
305    }
306}
307
308impl ser::Serialize for Datetime {
309    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
310    where
311        S: ser::Serializer,
312    {
313        use serde::ser::SerializeStruct;
314
315        let mut s = serializer.serialize_struct(NAME, 1)?;
316        s.serialize_field(FIELD, &self.to_string())?;
317        s.end()
318    }
319}
320
321impl<'de> de::Deserialize<'de> for Datetime {
322    fn deserialize<D>(deserializer: D) -> Result<Datetime, D::Error>
323    where
324        D: de::Deserializer<'de>,
325    {
326        struct DatetimeVisitor;
327
328        impl<'de> de::Visitor<'de> for DatetimeVisitor {
329            type Value = Datetime;
330
331            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
332                formatter.write_str("a TOML datetime")
333            }
334
335            fn visit_map<V>(self, mut visitor: V) -> Result<Datetime, V::Error>
336            where
337                V: de::MapAccess<'de>,
338            {
339                let value = visitor.next_key::<DatetimeKey>()?;
340                if value.is_none() {
341                    return Err(de::Error::custom("datetime key not found"));
342                }
343                let v: DatetimeFromString = visitor.next_value()?;
344                Ok(v.value)
345            }
346        }
347
348        static FIELDS: [&str; 1] = [FIELD];
349        deserializer.deserialize_struct(NAME, &FIELDS, DatetimeVisitor)
350    }
351}
352
353struct DatetimeKey;
354
355impl<'de> de::Deserialize<'de> for DatetimeKey {
356    fn deserialize<D>(deserializer: D) -> Result<DatetimeKey, D::Error>
357    where
358        D: de::Deserializer<'de>,
359    {
360        struct FieldVisitor;
361
362        impl<'de> de::Visitor<'de> for FieldVisitor {
363            type Value = ();
364
365            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
366                formatter.write_str("a valid datetime field")
367            }
368
369            fn visit_str<E>(self, s: &str) -> Result<(), E>
370            where
371                E: de::Error,
372            {
373                if s == FIELD {
374                    Ok(())
375                } else {
376                    Err(de::Error::custom("expected field with custom name"))
377                }
378            }
379        }
380
381        deserializer.deserialize_identifier(FieldVisitor)?;
382        Ok(DatetimeKey)
383    }
384}
385
386pub struct DatetimeFromString {
387    pub value: Datetime,
388}
389
390impl<'de> de::Deserialize<'de> for DatetimeFromString {
391    fn deserialize<D>(deserializer: D) -> Result<DatetimeFromString, D::Error>
392    where
393        D: de::Deserializer<'de>,
394    {
395        struct Visitor;
396
397        impl<'de> de::Visitor<'de> for Visitor {
398            type Value = DatetimeFromString;
399
400            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
401                formatter.write_str("string containing a datetime")
402            }
403
404            fn visit_str<E>(self, s: &str) -> Result<DatetimeFromString, E>
405            where
406                E: de::Error,
407            {
408                match s.parse() {
409                    Ok(date) => Ok(DatetimeFromString { value: date }),
410                    Err(e) => Err(de::Error::custom(e)),
411                }
412            }
413        }
414
415        deserializer.deserialize_str(Visitor)
416    }
417}
418
419impl fmt::Display for DatetimeParseError {
420    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
421        "failed to parse datetime".fmt(f)
422    }
423}
424
425impl error::Error for DatetimeParseError {}