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#[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#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
123pub struct Date {
124 pub year: u16,
126 pub month: u8,
128 pub day: u8,
130}
131
132#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
153pub struct Time {
154 pub hour: u8,
156 pub minute: u8,
158 pub second: u8,
160 pub nanosecond: u32,
162}
163
164#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
167pub enum Offset {
168 Z,
174
175 Custom {
177 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 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 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 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 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 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#[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}