]>
Commit | Line | Data |
---|---|---|
7cac9316 XL |
1 | use std::fmt; |
2 | use std::str::{self, FromStr}; | |
3 | use std::error; | |
4 | ||
5 | use 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)] | |
25 | pub 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)] | |
33 | pub 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. | |
43 | pub const SERDE_STRUCT_FIELD_NAME: &'static str = "$__toml_private_datetime"; | |
44 | pub const SERDE_STRUCT_NAME: &'static str = "$__toml_private_Datetime"; | |
45 | ||
46 | #[derive(PartialEq, Clone)] | |
47 | struct Date { | |
48 | year: u16, | |
49 | month: u8, | |
50 | day: u8, | |
51 | } | |
52 | ||
53 | #[derive(PartialEq, Clone)] | |
54 | struct Time { | |
55 | hour: u8, | |
56 | minute: u8, | |
57 | second: u8, | |
58 | secfract: Option<f64>, | |
59 | } | |
60 | ||
61 | #[derive(PartialEq, Clone)] | |
62 | enum Offset { | |
63 | Z, | |
64 | Custom { hours: i8, minutes: u8 }, | |
65 | } | |
66 | ||
67 | impl fmt::Debug for Datetime { | |
68 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
69 | fmt::Display::fmt(self, f) | |
70 | } | |
71 | } | |
72 | ||
73 | impl 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 | ||
91 | impl 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 | ||
97 | impl 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 let Some(i) = self.secfract { | |
101 | let s = format!("{}", i); | |
102 | write!(f, "{}", s.trim_left_matches("0"))?; | |
103 | } | |
104 | Ok(()) | |
105 | } | |
106 | } | |
107 | ||
108 | impl 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 } => { | |
113 | write!(f, "{:+03}:{:02}", hours, minutes) | |
114 | } | |
115 | } | |
116 | } | |
117 | } | |
118 | ||
119 | impl FromStr for Datetime { | |
120 | type Err = DatetimeParseError; | |
121 | ||
122 | fn from_str(date: &str) -> Result<Datetime, DatetimeParseError> { | |
123 | // Accepted formats: | |
124 | // | |
125 | // 0000-00-00T00:00:00.00Z | |
126 | // 0000-00-00T00:00:00.00 | |
127 | // 0000-00-00 | |
128 | // 00:00:00.00 | |
129 | if date.len() < 3 { | |
130 | return Err(DatetimeParseError { _private: () }) | |
131 | } | |
132 | let mut offset_allowed = true; | |
133 | let mut chars = date.chars(); | |
134 | ||
135 | // First up, parse the full date if we can | |
136 | let full_date = if chars.clone().nth(2) == Some(':') { | |
137 | offset_allowed = false; | |
138 | None | |
139 | } else { | |
140 | let y1 = digit(&mut chars)? as u16; | |
141 | let y2 = digit(&mut chars)? as u16; | |
142 | let y3 = digit(&mut chars)? as u16; | |
143 | let y4 = digit(&mut chars)? as u16; | |
144 | ||
145 | match chars.next() { | |
146 | Some('-') => {} | |
147 | _ => return Err(DatetimeParseError { _private: () }), | |
148 | } | |
149 | ||
150 | let m1 = digit(&mut chars)?; | |
151 | let m2 = digit(&mut chars)?; | |
152 | ||
153 | match chars.next() { | |
154 | Some('-') => {} | |
155 | _ => return Err(DatetimeParseError { _private: () }), | |
156 | } | |
157 | ||
158 | let d1 = digit(&mut chars)?; | |
159 | let d2 = digit(&mut chars)?; | |
160 | ||
161 | let date = Date { | |
162 | year: y1 * 1000 + y2 * 100 + y3 * 10 + y4, | |
163 | month: m1 * 10 + m2, | |
164 | day: d1 * 10 + d2, | |
165 | }; | |
166 | ||
167 | if date.month < 1 || date.month > 12 { | |
168 | return Err(DatetimeParseError { _private: () }) | |
169 | } | |
170 | if date.day < 1 || date.day > 31 { | |
171 | return Err(DatetimeParseError { _private: () }) | |
172 | } | |
173 | ||
174 | Some(date) | |
175 | }; | |
176 | ||
177 | // Next parse the "partial-time" if available | |
178 | let partial_time = if full_date.is_some() && | |
179 | chars.clone().next() == Some('T') { | |
180 | chars.next(); | |
181 | true | |
182 | } else if full_date.is_none() { | |
183 | true | |
184 | } else { | |
185 | false | |
186 | }; | |
187 | let time = if partial_time { | |
188 | let h1 = digit(&mut chars)?; | |
189 | let h2 = digit(&mut chars)?; | |
190 | match chars.next() { | |
191 | Some(':') => {} | |
192 | _ => return Err(DatetimeParseError { _private: () }), | |
193 | } | |
194 | let m1 = digit(&mut chars)?; | |
195 | let m2 = digit(&mut chars)?; | |
196 | match chars.next() { | |
197 | Some(':') => {} | |
198 | _ => return Err(DatetimeParseError { _private: () }), | |
199 | } | |
200 | let s1 = digit(&mut chars)?; | |
201 | let s2 = digit(&mut chars)?; | |
202 | ||
203 | let secfract = if chars.clone().next() == Some('.') { | |
204 | chars.next(); | |
205 | let mut first = true; | |
206 | let whole = chars.as_str(); | |
207 | let mut end = whole.len(); | |
208 | for (i, c) in whole.char_indices() { | |
209 | match c { | |
210 | '0' ... '9' => {} | |
211 | _ => { | |
212 | end = i; | |
213 | break | |
214 | } | |
215 | } | |
216 | first = false; | |
217 | } | |
218 | if first { | |
219 | return Err(DatetimeParseError { _private: () }) | |
220 | } | |
221 | chars = whole[end..].chars(); | |
222 | match format!("0.{}", &whole[..end]).parse() { | |
223 | Ok(f) => Some(f), | |
224 | Err(_) => return Err(DatetimeParseError { _private: () }), | |
225 | } | |
226 | } else { | |
227 | None | |
228 | }; | |
229 | ||
230 | let time = Time { | |
231 | hour: h1 * 10 + h2, | |
232 | minute: m1 * 10 + m2, | |
233 | second: s1 * 10 + s2, | |
234 | secfract: secfract, | |
235 | }; | |
236 | ||
237 | if time.hour > 24 { | |
238 | return Err(DatetimeParseError { _private: () }) | |
239 | } | |
240 | if time.minute > 59 { | |
241 | return Err(DatetimeParseError { _private: () }) | |
242 | } | |
243 | if time.second > 60 { | |
244 | return Err(DatetimeParseError { _private: () }) | |
245 | } | |
246 | ||
247 | Some(time) | |
248 | } else { | |
249 | offset_allowed = false; | |
250 | None | |
251 | }; | |
252 | ||
253 | // And finally, parse the offset | |
254 | let offset = if offset_allowed { | |
255 | let next = chars.clone().next(); | |
256 | if next == Some('Z') { | |
257 | chars.next(); | |
258 | Some(Offset::Z) | |
259 | } else if next.is_none() { | |
260 | None | |
261 | } else { | |
262 | let sign = match next { | |
263 | Some('+') => 1, | |
264 | Some('-') => -1, | |
265 | _ => return Err(DatetimeParseError { _private: () }), | |
266 | }; | |
267 | chars.next(); | |
268 | let h1 = digit(&mut chars)? as i8; | |
269 | let h2 = digit(&mut chars)? as i8; | |
270 | match chars.next() { | |
271 | Some(':') => {} | |
272 | _ => return Err(DatetimeParseError { _private: () }), | |
273 | } | |
274 | let m1 = digit(&mut chars)?; | |
275 | let m2 = digit(&mut chars)?; | |
276 | ||
277 | Some(Offset::Custom { | |
278 | hours: sign * (h1 * 10 + h2), | |
279 | minutes: m1 * 10 + m2, | |
280 | }) | |
281 | } | |
282 | } else { | |
283 | None | |
284 | }; | |
285 | ||
286 | // Return an error if we didn't hit eof, otherwise return our parsed | |
287 | // date | |
288 | if chars.next().is_some() { | |
289 | return Err(DatetimeParseError { _private: () }) | |
290 | } | |
291 | ||
292 | Ok(Datetime { | |
293 | date: full_date, | |
294 | time: time, | |
295 | offset: offset, | |
296 | }) | |
297 | } | |
298 | } | |
299 | ||
300 | fn digit(chars: &mut str::Chars) -> Result<u8, DatetimeParseError> { | |
301 | match chars.next() { | |
302 | Some(c) if '0' <= c && c <= '9' => Ok(c as u8 - '0' as u8), | |
303 | _ => Err(DatetimeParseError { _private: () }), | |
304 | } | |
305 | } | |
306 | ||
307 | impl ser::Serialize for Datetime { | |
308 | fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | |
309 | where S: ser::Serializer | |
310 | { | |
311 | use serde::ser::SerializeStruct; | |
312 | ||
313 | let mut s = serializer.serialize_struct(SERDE_STRUCT_NAME, 1)?; | |
314 | s.serialize_field(SERDE_STRUCT_FIELD_NAME, &self.to_string())?; | |
315 | s.end() | |
316 | } | |
317 | } | |
318 | ||
319 | impl de::Deserialize for Datetime { | |
320 | fn deserialize<D>(deserializer: D) -> Result<Datetime, D::Error> | |
321 | where D: de::Deserializer | |
322 | { | |
323 | struct DatetimeVisitor; | |
324 | ||
325 | impl de::Visitor for DatetimeVisitor { | |
326 | type Value = Datetime; | |
327 | ||
328 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { | |
329 | formatter.write_str("a TOML datetime") | |
330 | } | |
331 | ||
332 | fn visit_map<V>(self, mut visitor: V) -> Result<Datetime, V::Error> | |
333 | where V: de::MapVisitor | |
334 | { | |
335 | let value = visitor.visit_key::<DatetimeKey>()?; | |
336 | if value.is_none() { | |
337 | return Err(de::Error::custom("datetime key not found")) | |
338 | } | |
339 | let v: DatetimeFromString = visitor.visit_value()?; | |
340 | Ok(v.value) | |
341 | ||
342 | } | |
343 | } | |
344 | ||
345 | static FIELDS: [&'static str; 1] = [SERDE_STRUCT_FIELD_NAME]; | |
346 | deserializer.deserialize_struct(SERDE_STRUCT_NAME, | |
347 | &FIELDS, | |
348 | DatetimeVisitor) | |
349 | } | |
350 | } | |
351 | ||
352 | struct DatetimeKey; | |
353 | ||
354 | impl de::Deserialize for DatetimeKey { | |
355 | fn deserialize<D>(deserializer: D) -> Result<DatetimeKey, D::Error> | |
356 | where D: de::Deserializer | |
357 | { | |
358 | struct FieldVisitor; | |
359 | ||
360 | impl de::Visitor for FieldVisitor { | |
361 | type Value = (); | |
362 | ||
363 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { | |
364 | formatter.write_str("a valid datetime field") | |
365 | } | |
366 | ||
367 | fn visit_str<E>(self, s: &str) -> Result<(), E> | |
368 | where E: de::Error | |
369 | { | |
370 | if s == SERDE_STRUCT_FIELD_NAME { | |
371 | Ok(()) | |
372 | } else { | |
373 | Err(de::Error::custom("expected field with custom name")) | |
374 | } | |
375 | } | |
376 | } | |
377 | ||
378 | deserializer.deserialize_struct_field(FieldVisitor)?; | |
379 | Ok(DatetimeKey) | |
380 | } | |
381 | } | |
382 | ||
383 | pub struct DatetimeFromString { | |
384 | pub value: Datetime, | |
385 | } | |
386 | ||
387 | impl de::Deserialize for DatetimeFromString { | |
388 | fn deserialize<D>(deserializer: D) -> Result<DatetimeFromString, D::Error> | |
389 | where D: de::Deserializer | |
390 | { | |
391 | struct Visitor; | |
392 | ||
393 | impl de::Visitor for Visitor { | |
394 | type Value = DatetimeFromString; | |
395 | ||
396 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { | |
397 | formatter.write_str("string containing a datetime") | |
398 | } | |
399 | ||
400 | fn visit_str<E>(self, s: &str) -> Result<DatetimeFromString, E> | |
401 | where E: de::Error, | |
402 | { | |
403 | match s.parse() { | |
404 | Ok(date) => Ok(DatetimeFromString { value: date }), | |
405 | Err(e) => Err(de::Error::custom(e)), | |
406 | } | |
407 | } | |
408 | } | |
409 | ||
410 | deserializer.deserialize_str(Visitor) | |
411 | } | |
412 | } | |
413 | ||
414 | impl fmt::Display for DatetimeParseError { | |
415 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
416 | "failed to parse datetime".fmt(f) | |
417 | } | |
418 | } | |
419 | ||
420 | impl error::Error for DatetimeParseError { | |
421 | fn description(&self) -> &str { | |
422 | "failed to parse datetime" | |
423 | } | |
424 | } |