1use std::error;
2use std::fmt;
3use std::str::{self, FromStr};
4
5#[cfg(feature = "serde")]
6use serde::{de, ser};
7
8#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
81pub struct Datetime {
82 pub date: Option<Date>,
85
86 pub time: Option<Time>,
89
90 pub offset: Option<Offset>,
93}
94
95#[derive(Debug, Clone)]
97#[non_exhaustive]
98pub struct DatetimeParseError {}
99
100#[doc(hidden)]
107#[cfg(feature = "serde")]
108pub const FIELD: &str = "$__toml_private_datetime";
109#[doc(hidden)]
110#[cfg(feature = "serde")]
111pub const NAME: &str = "$__toml_private_Datetime";
112
113#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
128pub struct Date {
129 pub year: u16,
131 pub month: u8,
133 pub day: u8,
135}
136
137#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
158pub struct Time {
159 pub hour: u8,
161 pub minute: u8,
163 pub second: u8,
165 pub nanosecond: u32,
167}
168
169#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
172pub enum Offset {
173 Z,
179
180 Custom {
182 minutes: i16,
184 },
185}
186
187impl Datetime {
188 #[cfg(feature = "serde")]
189 fn type_name(&self) -> &'static str {
190 match (
191 self.date.is_some(),
192 self.time.is_some(),
193 self.offset.is_some(),
194 ) {
195 (true, true, true) => "offset datetime",
196 (true, true, false) => "local datetime",
197 (true, false, false) => Date::type_name(),
198 (false, true, false) => Time::type_name(),
199 _ => unreachable!("unsupported datetime combination"),
200 }
201 }
202}
203
204impl Date {
205 #[cfg(feature = "serde")]
206 fn type_name() -> &'static str {
207 "local date"
208 }
209}
210
211impl Time {
212 #[cfg(feature = "serde")]
213 fn type_name() -> &'static str {
214 "local time"
215 }
216}
217
218impl From<Date> for Datetime {
219 fn from(other: Date) -> Self {
220 Datetime {
221 date: Some(other),
222 time: None,
223 offset: None,
224 }
225 }
226}
227
228impl From<Time> for Datetime {
229 fn from(other: Time) -> Self {
230 Datetime {
231 date: None,
232 time: Some(other),
233 offset: None,
234 }
235 }
236}
237
238impl fmt::Display for Datetime {
239 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240 if let Some(ref date) = self.date {
241 write!(f, "{}", date)?;
242 }
243 if let Some(ref time) = self.time {
244 if self.date.is_some() {
245 write!(f, "T")?;
246 }
247 write!(f, "{}", time)?;
248 }
249 if let Some(ref offset) = self.offset {
250 write!(f, "{}", offset)?;
251 }
252 Ok(())
253 }
254}
255
256impl fmt::Display for Date {
257 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258 write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day)
259 }
260}
261
262impl fmt::Display for Time {
263 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
264 write!(f, "{:02}:{:02}:{:02}", self.hour, self.minute, self.second)?;
265 if self.nanosecond != 0 {
266 let s = format!("{:09}", self.nanosecond);
267 write!(f, ".{}", s.trim_end_matches('0'))?;
268 }
269 Ok(())
270 }
271}
272
273impl fmt::Display for Offset {
274 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275 match *self {
276 Offset::Z => write!(f, "Z"),
277 Offset::Custom { mut minutes } => {
278 let mut sign = '+';
279 if minutes < 0 {
280 minutes *= -1;
281 sign = '-';
282 }
283 let hours = minutes / 60;
284 let minutes = minutes % 60;
285 write!(f, "{}{:02}:{:02}", sign, hours, minutes)
286 }
287 }
288 }
289}
290
291impl FromStr for Datetime {
292 type Err = DatetimeParseError;
293
294 fn from_str(date: &str) -> Result<Datetime, DatetimeParseError> {
295 if date.len() < 3 {
302 return Err(DatetimeParseError {});
303 }
304 let mut offset_allowed = true;
305 let mut chars = date.chars();
306
307 let full_date = if chars.clone().nth(2) == Some(':') {
309 offset_allowed = false;
310 None
311 } else {
312 let y1 = u16::from(digit(&mut chars)?);
313 let y2 = u16::from(digit(&mut chars)?);
314 let y3 = u16::from(digit(&mut chars)?);
315 let y4 = u16::from(digit(&mut chars)?);
316
317 match chars.next() {
318 Some('-') => {}
319 _ => return Err(DatetimeParseError {}),
320 }
321
322 let m1 = digit(&mut chars)?;
323 let m2 = digit(&mut chars)?;
324
325 match chars.next() {
326 Some('-') => {}
327 _ => return Err(DatetimeParseError {}),
328 }
329
330 let d1 = digit(&mut chars)?;
331 let d2 = digit(&mut chars)?;
332
333 let date = Date {
334 year: y1 * 1000 + y2 * 100 + y3 * 10 + y4,
335 month: m1 * 10 + m2,
336 day: d1 * 10 + d2,
337 };
338
339 if date.month < 1 || date.month > 12 {
340 return Err(DatetimeParseError {});
341 }
342 let is_leap_year =
343 (date.year % 4 == 0) && ((date.year % 100 != 0) || (date.year % 400 == 0));
344 let max_days_in_month = match date.month {
345 2 if is_leap_year => 29,
346 2 => 28,
347 4 | 6 | 9 | 11 => 30,
348 _ => 31,
349 };
350 if date.day < 1 || date.day > max_days_in_month {
351 return Err(DatetimeParseError {});
352 }
353
354 Some(date)
355 };
356
357 let next = chars.clone().next();
359 let partial_time = if full_date.is_some()
360 && (next == Some('T') || next == Some('t') || next == Some(' '))
361 {
362 chars.next();
363 true
364 } else {
365 full_date.is_none()
366 };
367
368 let time = if partial_time {
369 let h1 = digit(&mut chars)?;
370 let h2 = digit(&mut chars)?;
371 match chars.next() {
372 Some(':') => {}
373 _ => return Err(DatetimeParseError {}),
374 }
375 let m1 = digit(&mut chars)?;
376 let m2 = digit(&mut chars)?;
377 match chars.next() {
378 Some(':') => {}
379 _ => return Err(DatetimeParseError {}),
380 }
381 let s1 = digit(&mut chars)?;
382 let s2 = digit(&mut chars)?;
383
384 let mut nanosecond = 0;
385 if chars.clone().next() == Some('.') {
386 chars.next();
387 let whole = chars.as_str();
388
389 let mut end = whole.len();
390 for (i, byte) in whole.bytes().enumerate() {
391 #[allow(clippy::single_match_else)]
392 match byte {
393 b'0'..=b'9' => {
394 if i < 9 {
395 let p = 10_u32.pow(8 - i as u32);
396 nanosecond += p * u32::from(byte - b'0');
397 }
398 }
399 _ => {
400 end = i;
401 break;
402 }
403 }
404 }
405 if end == 0 {
406 return Err(DatetimeParseError {});
407 }
408 chars = whole[end..].chars();
409 }
410
411 let time = Time {
412 hour: h1 * 10 + h2,
413 minute: m1 * 10 + m2,
414 second: s1 * 10 + s2,
415 nanosecond,
416 };
417
418 if time.hour > 24 {
419 return Err(DatetimeParseError {});
420 }
421 if time.minute > 59 {
422 return Err(DatetimeParseError {});
423 }
424 if time.second > 60 {
426 return Err(DatetimeParseError {});
427 }
428 if time.nanosecond > 999_999_999 {
429 return Err(DatetimeParseError {});
430 }
431
432 Some(time)
433 } else {
434 offset_allowed = false;
435 None
436 };
437
438 let offset = if offset_allowed {
440 let next = chars.clone().next();
441 if next == Some('Z') || next == Some('z') {
442 chars.next();
443 Some(Offset::Z)
444 } else if next.is_none() {
445 None
446 } else {
447 let sign = match next {
448 Some('+') => 1,
449 Some('-') => -1,
450 _ => return Err(DatetimeParseError {}),
451 };
452 chars.next();
453 let h1 = digit(&mut chars)? as i16;
454 let h2 = digit(&mut chars)? as i16;
455 match chars.next() {
456 Some(':') => {}
457 _ => return Err(DatetimeParseError {}),
458 }
459 let m1 = digit(&mut chars)? as i16;
460 let m2 = digit(&mut chars)? as i16;
461
462 let hours = h1 * 10 + h2;
463 let minutes = m1 * 10 + m2;
464
465 let total_minutes = sign * (hours * 60 + minutes);
466
467 if !((-24 * 60)..=(24 * 60)).contains(&total_minutes) {
468 return Err(DatetimeParseError {});
469 }
470
471 Some(Offset::Custom {
472 minutes: total_minutes,
473 })
474 }
475 } else {
476 None
477 };
478
479 if chars.next().is_some() {
482 return Err(DatetimeParseError {});
483 }
484
485 Ok(Datetime {
486 date: full_date,
487 time,
488 offset,
489 })
490 }
491}
492
493fn digit(chars: &mut str::Chars<'_>) -> Result<u8, DatetimeParseError> {
494 match chars.next() {
495 Some(c) if c.is_ascii_digit() => Ok(c as u8 - b'0'),
496 _ => Err(DatetimeParseError {}),
497 }
498}
499
500#[cfg(feature = "serde")]
501impl ser::Serialize for Datetime {
502 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
503 where
504 S: ser::Serializer,
505 {
506 use serde::ser::SerializeStruct;
507
508 let mut s = serializer.serialize_struct(NAME, 1)?;
509 s.serialize_field(FIELD, &self.to_string())?;
510 s.end()
511 }
512}
513
514#[cfg(feature = "serde")]
515impl ser::Serialize for Date {
516 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
517 where
518 S: ser::Serializer,
519 {
520 Datetime::from(*self).serialize(serializer)
521 }
522}
523
524#[cfg(feature = "serde")]
525impl ser::Serialize for Time {
526 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
527 where
528 S: ser::Serializer,
529 {
530 Datetime::from(*self).serialize(serializer)
531 }
532}
533
534#[cfg(feature = "serde")]
535impl<'de> de::Deserialize<'de> for Datetime {
536 fn deserialize<D>(deserializer: D) -> Result<Datetime, D::Error>
537 where
538 D: de::Deserializer<'de>,
539 {
540 struct DatetimeVisitor;
541
542 impl<'de> de::Visitor<'de> for DatetimeVisitor {
543 type Value = Datetime;
544
545 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
546 formatter.write_str("a TOML datetime")
547 }
548
549 fn visit_map<V>(self, mut visitor: V) -> Result<Datetime, V::Error>
550 where
551 V: de::MapAccess<'de>,
552 {
553 let value = visitor.next_key::<DatetimeKey>()?;
554 if value.is_none() {
555 return Err(de::Error::custom("datetime key not found"));
556 }
557 let v: DatetimeFromString = visitor.next_value()?;
558 Ok(v.value)
559 }
560 }
561
562 static FIELDS: [&str; 1] = [FIELD];
563 deserializer.deserialize_struct(NAME, &FIELDS, DatetimeVisitor)
564 }
565}
566
567#[cfg(feature = "serde")]
568impl<'de> de::Deserialize<'de> for Date {
569 fn deserialize<D>(deserializer: D) -> Result<Date, D::Error>
570 where
571 D: de::Deserializer<'de>,
572 {
573 match Datetime::deserialize(deserializer)? {
574 Datetime {
575 date: Some(date),
576 time: None,
577 offset: None,
578 } => Ok(date),
579 datetime => Err(de::Error::invalid_type(
580 de::Unexpected::Other(datetime.type_name()),
581 &Self::type_name(),
582 )),
583 }
584 }
585}
586
587#[cfg(feature = "serde")]
588impl<'de> de::Deserialize<'de> for Time {
589 fn deserialize<D>(deserializer: D) -> Result<Time, D::Error>
590 where
591 D: de::Deserializer<'de>,
592 {
593 match Datetime::deserialize(deserializer)? {
594 Datetime {
595 date: None,
596 time: Some(time),
597 offset: None,
598 } => Ok(time),
599 datetime => Err(de::Error::invalid_type(
600 de::Unexpected::Other(datetime.type_name()),
601 &Self::type_name(),
602 )),
603 }
604 }
605}
606
607#[cfg(feature = "serde")]
608struct DatetimeKey;
609
610#[cfg(feature = "serde")]
611impl<'de> de::Deserialize<'de> for DatetimeKey {
612 fn deserialize<D>(deserializer: D) -> Result<DatetimeKey, D::Error>
613 where
614 D: de::Deserializer<'de>,
615 {
616 struct FieldVisitor;
617
618 impl<'de> de::Visitor<'de> for FieldVisitor {
619 type Value = ();
620
621 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
622 formatter.write_str("a valid datetime field")
623 }
624
625 fn visit_str<E>(self, s: &str) -> Result<(), E>
626 where
627 E: de::Error,
628 {
629 if s == FIELD {
630 Ok(())
631 } else {
632 Err(de::Error::custom("expected field with custom name"))
633 }
634 }
635 }
636
637 deserializer.deserialize_identifier(FieldVisitor)?;
638 Ok(DatetimeKey)
639 }
640}
641
642#[doc(hidden)]
643#[cfg(feature = "serde")]
644pub struct DatetimeFromString {
645 pub value: Datetime,
646}
647
648#[cfg(feature = "serde")]
649impl<'de> de::Deserialize<'de> for DatetimeFromString {
650 fn deserialize<D>(deserializer: D) -> Result<DatetimeFromString, D::Error>
651 where
652 D: de::Deserializer<'de>,
653 {
654 struct Visitor;
655
656 impl<'de> de::Visitor<'de> for Visitor {
657 type Value = DatetimeFromString;
658
659 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
660 formatter.write_str("string containing a datetime")
661 }
662
663 fn visit_str<E>(self, s: &str) -> Result<DatetimeFromString, E>
664 where
665 E: de::Error,
666 {
667 match s.parse() {
668 Ok(date) => Ok(DatetimeFromString { value: date }),
669 Err(e) => Err(de::Error::custom(e)),
670 }
671 }
672 }
673
674 deserializer.deserialize_str(Visitor)
675 }
676}
677
678impl fmt::Display for DatetimeParseError {
679 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
680 "failed to parse datetime".fmt(f)
681 }
682}
683
684impl error::Error for DatetimeParseError {}