1use core::fmt;
2use core::str::{self, FromStr};
3
4#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
77pub struct Datetime {
78 pub date: Option<Date>,
81
82 pub time: Option<Time>,
85
86 pub offset: Option<Offset>,
89}
90
91#[cfg(feature = "serde")]
98pub(crate) const FIELD: &str = "$__toml_private_datetime";
99#[cfg(feature = "serde")]
100pub(crate) const NAME: &str = "$__toml_private_Datetime";
101#[cfg(feature = "serde")]
102pub(crate) fn is_datetime(name: &'static str) -> bool {
103 name == NAME
104}
105
106#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
121pub struct Date {
122 pub year: u16,
124 pub month: u8,
126 pub day: u8,
128}
129
130#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
151pub struct Time {
152 pub hour: u8,
154 pub minute: u8,
156 pub second: Option<u8>,
158 pub nanosecond: Option<u32>,
160}
161
162#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
165pub enum Offset {
166 Z,
172
173 Custom {
175 minutes: i16,
177 },
178}
179
180impl Datetime {
181 #[cfg(feature = "serde")]
182 fn type_name(&self) -> &'static str {
183 match (
184 self.date.is_some(),
185 self.time.is_some(),
186 self.offset.is_some(),
187 ) {
188 (true, true, true) => "offset datetime",
189 (true, true, false) => "local datetime",
190 (true, false, false) => Date::type_name(),
191 (false, true, false) => Time::type_name(),
192 _ => unreachable!("unsupported datetime combination"),
193 }
194 }
195}
196
197impl Date {
198 #[cfg(feature = "serde")]
199 fn type_name() -> &'static str {
200 "local date"
201 }
202}
203
204impl Time {
205 #[cfg(feature = "serde")]
206 fn type_name() -> &'static str {
207 "local time"
208 }
209}
210
211impl From<Date> for Datetime {
212 fn from(other: Date) -> Self {
213 Self {
214 date: Some(other),
215 time: None,
216 offset: None,
217 }
218 }
219}
220
221impl From<Time> for Datetime {
222 fn from(other: Time) -> Self {
223 Self {
224 date: None,
225 time: Some(other),
226 offset: None,
227 }
228 }
229}
230
231#[cfg(feature = "alloc")]
232impl fmt::Display for Datetime {
233 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234 if let Some(ref date) = self.date {
235 write!(f, "{date}")?;
236 }
237 if let Some(ref time) = self.time {
238 if self.date.is_some() {
239 write!(f, "T")?;
240 }
241 write!(f, "{time}")?;
242 }
243 if let Some(ref offset) = self.offset {
244 write!(f, "{offset}")?;
245 }
246 Ok(())
247 }
248}
249
250impl fmt::Display for Date {
251 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252 write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day)
253 }
254}
255
256#[cfg(feature = "alloc")]
257impl fmt::Display for Time {
258 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259 write!(f, "{:02}:{:02}", self.hour, self.minute)?;
260 if let Some(second) = self
261 .second
262 .or_else(|| self.nanosecond.is_some().then_some(0))
263 {
264 write!(f, ":{second:02}")?;
265 }
266 if let Some(nanosecond) = self.nanosecond {
267 let s = alloc::format!("{nanosecond:09}");
268 let mut s = s.trim_end_matches('0');
269 if s.is_empty() {
270 s = "0";
271 }
272 write!(f, ".{s}")?;
273 }
274 Ok(())
275 }
276}
277
278impl fmt::Display for Offset {
279 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
280 match *self {
281 Self::Z => write!(f, "Z"),
282 Self::Custom { mut minutes } => {
283 let mut sign = '+';
284 if minutes < 0 {
285 minutes *= -1;
286 sign = '-';
287 }
288 let hours = minutes / 60;
289 let minutes = minutes % 60;
290 write!(f, "{sign}{hours:02}:{minutes:02}")
291 }
292 }
293 }
294}
295
296impl FromStr for Datetime {
297 type Err = DatetimeParseError;
298
299 fn from_str(date: &str) -> Result<Self, DatetimeParseError> {
300 let mut result = Self {
344 date: None,
345 time: None,
346 offset: None,
347 };
348
349 let mut lexer = Lexer::new(date);
350
351 let digits = lexer
352 .next()
353 .ok_or(DatetimeParseError::new().expected("year or hour"))?;
354 digits
355 .is(TokenKind::Digits)
356 .map_err(|err| err.expected("year or hour"))?;
357 let sep = lexer
358 .next()
359 .ok_or(DatetimeParseError::new().expected("`-` (YYYY-MM) or `:` (HH:MM)"))?;
360 match sep.kind {
361 TokenKind::Dash => {
362 let year = digits;
363 let month = lexer
364 .next()
365 .ok_or_else(|| DatetimeParseError::new().what("date").expected("month"))?;
366 month
367 .is(TokenKind::Digits)
368 .map_err(|err| err.what("date").expected("month"))?;
369 let sep = lexer.next().ok_or(
370 DatetimeParseError::new()
371 .what("date")
372 .expected("`-` (MM-DD)"),
373 )?;
374 sep.is(TokenKind::Dash)
375 .map_err(|err| err.what("date").expected("`-` (MM-DD)"))?;
376 let day = lexer
377 .next()
378 .ok_or(DatetimeParseError::new().what("date").expected("day"))?;
379 day.is(TokenKind::Digits)
380 .map_err(|err| err.what("date").expected("day"))?;
381
382 if year.raw.len() != 4 {
383 return Err(DatetimeParseError::new()
384 .what("date")
385 .expected("a four-digit year (YYYY)"));
386 }
387 if month.raw.len() != 2 {
388 return Err(DatetimeParseError::new()
389 .what("date")
390 .expected("a two-digit month (MM)"));
391 }
392 if day.raw.len() != 2 {
393 return Err(DatetimeParseError::new()
394 .what("date")
395 .expected("a two-digit day (DD)"));
396 }
397 let date = Date {
398 year: year.raw.parse().map_err(|_err| DatetimeParseError::new())?,
399 month: month
400 .raw
401 .parse()
402 .map_err(|_err| DatetimeParseError::new())?,
403 day: day.raw.parse().map_err(|_err| DatetimeParseError::new())?,
404 };
405 if date.month < 1 || date.month > 12 {
406 return Err(DatetimeParseError::new()
407 .what("date")
408 .expected("month between 01 and 12"));
409 }
410 let is_leap_year =
411 (date.year % 4 == 0) && ((date.year % 100 != 0) || (date.year % 400 == 0));
412 let (max_days_in_month, expected_day) = match date.month {
413 2 if is_leap_year => (29, "day between 01 and 29"),
414 2 => (28, "day between 01 and 28"),
415 4 | 6 | 9 | 11 => (30, "day between 01 and 30"),
416 _ => (31, "day between 01 and 31"),
417 };
418 if date.day < 1 || date.day > max_days_in_month {
419 return Err(DatetimeParseError::new()
420 .what("date")
421 .expected(expected_day));
422 }
423
424 result.date = Some(date);
425 }
426 TokenKind::Colon => lexer = Lexer::new(date),
427 _ => {
428 return Err(DatetimeParseError::new().expected("`-` (YYYY-MM) or `:` (HH:MM)"));
429 }
430 }
431
432 let partial_time = if result.date.is_some() {
434 let sep = lexer.next();
435 match sep {
436 Some(token) if matches!(token.kind, TokenKind::T | TokenKind::Space) => true,
437 Some(_token) => {
438 return Err(DatetimeParseError::new()
439 .what("date-time")
440 .expected("`T` between date and time"));
441 }
442 None => false,
443 }
444 } else {
445 result.date.is_none()
446 };
447
448 if partial_time {
449 let hour = lexer
450 .next()
451 .ok_or_else(|| DatetimeParseError::new().what("time").expected("hour"))?;
452 hour.is(TokenKind::Digits)
453 .map_err(|err| err.what("time").expected("hour"))?;
454 let sep = lexer.next().ok_or(
455 DatetimeParseError::new()
456 .what("time")
457 .expected("`:` (HH:MM)"),
458 )?;
459 sep.is(TokenKind::Colon)
460 .map_err(|err| err.what("time").expected("`:` (HH:MM)"))?;
461 let minute = lexer
462 .next()
463 .ok_or(DatetimeParseError::new().what("time").expected("minute"))?;
464 minute
465 .is(TokenKind::Digits)
466 .map_err(|err| err.what("time").expected("minute"))?;
467 let second = if lexer.clone().next().map(|t| t.kind) == Some(TokenKind::Colon) {
468 let sep = lexer.next().ok_or(DatetimeParseError::new())?;
469 sep.is(TokenKind::Colon)?;
470 let second = lexer
471 .next()
472 .ok_or(DatetimeParseError::new().what("time").expected("second"))?;
473 second
474 .is(TokenKind::Digits)
475 .map_err(|err| err.what("time").expected("second"))?;
476 Some(second)
477 } else {
478 None
479 };
480
481 let nanosecond = if second.is_some()
482 && lexer.clone().next().map(|t| t.kind) == Some(TokenKind::Dot)
483 {
484 let sep = lexer.next().ok_or(DatetimeParseError::new())?;
485 sep.is(TokenKind::Dot)?;
486 let nanosecond = lexer.next().ok_or(
487 DatetimeParseError::new()
488 .what("time")
489 .expected("nanosecond"),
490 )?;
491 nanosecond
492 .is(TokenKind::Digits)
493 .map_err(|err| err.what("time").expected("nanosecond"))?;
494 Some(nanosecond)
495 } else {
496 None
497 };
498
499 if hour.raw.len() != 2 {
500 return Err(DatetimeParseError::new()
501 .what("time")
502 .expected("a two-digit hour (HH)"));
503 }
504 if minute.raw.len() != 2 {
505 return Err(DatetimeParseError::new()
506 .what("time")
507 .expected("a two-digit minute (MM)"));
508 }
509 if let Some(second) = second {
510 if second.raw.len() != 2 {
511 return Err(DatetimeParseError::new()
512 .what("time")
513 .expected("a two-digit second (SS)"));
514 }
515 }
516
517 let time = Time {
518 hour: hour.raw.parse().map_err(|_err| DatetimeParseError::new())?,
519 minute: minute
520 .raw
521 .parse()
522 .map_err(|_err| DatetimeParseError::new())?,
523 second: second
524 .map(|t| t.raw.parse().map_err(|_err| DatetimeParseError::new()))
525 .transpose()?,
526 nanosecond: nanosecond.map(|t| s_to_nanoseconds(t.raw)),
527 };
528
529 if time.hour > 23 {
530 return Err(DatetimeParseError::new()
531 .what("time")
532 .expected("hour between 00 and 23"));
533 }
534 if time.minute > 59 {
535 return Err(DatetimeParseError::new()
536 .what("time")
537 .expected("minute between 00 and 59"));
538 }
539 if time.second.unwrap_or(0) > 60 {
541 return Err(DatetimeParseError::new()
542 .what("time")
543 .expected("second between 00 and 60"));
544 }
545 if time.nanosecond.unwrap_or(0) > 999_999_999 {
546 return Err(DatetimeParseError::new()
547 .what("time")
548 .expected("nanoseconds overflowed"));
549 }
550
551 result.time = Some(time);
552 }
553
554 if result.date.is_some() && result.time.is_some() {
556 match lexer.next() {
557 Some(token) if token.kind == TokenKind::Z => {
558 result.offset = Some(Offset::Z);
559 }
560 Some(token) if matches!(token.kind, TokenKind::Plus | TokenKind::Dash) => {
561 let sign = if token.kind == TokenKind::Plus { 1 } else { -1 };
562 let hours = lexer
563 .next()
564 .ok_or(DatetimeParseError::new().what("offset").expected("hour"))?;
565 hours
566 .is(TokenKind::Digits)
567 .map_err(|err| err.what("offset").expected("hour"))?;
568 let sep = lexer.next().ok_or(
569 DatetimeParseError::new()
570 .what("offset")
571 .expected("`:` (HH:MM)"),
572 )?;
573 sep.is(TokenKind::Colon)
574 .map_err(|err| err.what("offset").expected("`:` (HH:MM)"))?;
575 let minutes = lexer
576 .next()
577 .ok_or(DatetimeParseError::new().what("offset").expected("minute"))?;
578 minutes
579 .is(TokenKind::Digits)
580 .map_err(|err| err.what("offset").expected("minute"))?;
581
582 if hours.raw.len() != 2 {
583 return Err(DatetimeParseError::new()
584 .what("offset")
585 .expected("a two-digit hour (HH)"));
586 }
587 if minutes.raw.len() != 2 {
588 return Err(DatetimeParseError::new()
589 .what("offset")
590 .expected("a two-digit minute (MM)"));
591 }
592
593 let hours = hours
594 .raw
595 .parse::<u8>()
596 .map_err(|_err| DatetimeParseError::new())?;
597 let minutes = minutes
598 .raw
599 .parse::<u8>()
600 .map_err(|_err| DatetimeParseError::new())?;
601
602 if hours > 23 {
603 return Err(DatetimeParseError::new()
604 .what("offset")
605 .expected("hours between 00 and 23"));
606 }
607 if minutes > 59 {
608 return Err(DatetimeParseError::new()
609 .what("offset")
610 .expected("minutes between 00 and 59"));
611 }
612
613 let total_minutes = sign * (hours as i16 * 60 + minutes as i16);
614
615 if !((-24 * 60)..=(24 * 60)).contains(&total_minutes) {
616 return Err(DatetimeParseError::new().what("offset"));
617 }
618
619 result.offset = Some(Offset::Custom {
620 minutes: total_minutes,
621 });
622 }
623 Some(_token) => {
624 return Err(DatetimeParseError::new()
625 .what("offset")
626 .expected("`Z`, +OFFSET, -OFFSET"));
627 }
628 None => {}
629 }
630 }
631
632 if lexer.unknown().is_some() {
635 return Err(DatetimeParseError::new());
636 }
637
638 Ok(result)
639 }
640}
641
642fn s_to_nanoseconds(input: &str) -> u32 {
643 let mut nanosecond = 0;
644 for (i, byte) in input.bytes().enumerate() {
645 if byte.is_ascii_digit() {
646 if i < 9 {
647 let p = 10_u32.pow(8 - i as u32);
648 nanosecond += p * u32::from(byte - b'0');
649 }
650 } else {
651 panic!("invalid nanoseconds {input:?}");
652 }
653 }
654 nanosecond
655}
656
657#[derive(Copy, Clone)]
658struct Token<'s> {
659 kind: TokenKind,
660 raw: &'s str,
661}
662
663impl Token<'_> {
664 fn is(&self, kind: TokenKind) -> Result<(), DatetimeParseError> {
665 if self.kind == kind {
666 Ok(())
667 } else {
668 Err(DatetimeParseError::new())
669 }
670 }
671}
672
673#[derive(Copy, Clone, PartialEq, Eq)]
674enum TokenKind {
675 Digits,
676 Dash,
677 Colon,
678 Dot,
679 T,
680 Space,
681 Z,
682 Plus,
683 Unknown,
684}
685
686#[derive(Copy, Clone)]
687struct Lexer<'s> {
688 stream: &'s str,
689}
690
691impl<'s> Lexer<'s> {
692 fn new(input: &'s str) -> Self {
693 Self { stream: input }
694 }
695
696 fn unknown(&mut self) -> Option<Token<'s>> {
697 let remaining = self.stream.len();
698 if remaining == 0 {
699 return None;
700 }
701 let raw = self.stream;
702 self.stream = &self.stream[remaining..remaining];
703 Some(Token {
704 kind: TokenKind::Unknown,
705 raw,
706 })
707 }
708}
709
710impl<'s> Iterator for Lexer<'s> {
711 type Item = Token<'s>;
712
713 fn next(&mut self) -> Option<Self::Item> {
714 let (kind, end) = match self.stream.as_bytes().first()? {
715 b'0'..=b'9' => {
716 let end = self
717 .stream
718 .as_bytes()
719 .iter()
720 .position(|b| !b.is_ascii_digit())
721 .unwrap_or(self.stream.len());
722 (TokenKind::Digits, end)
723 }
724 b'-' => (TokenKind::Dash, 1),
725 b':' => (TokenKind::Colon, 1),
726 b'T' | b't' => (TokenKind::T, 1),
727 b' ' => (TokenKind::Space, 1),
728 b'Z' | b'z' => (TokenKind::Z, 1),
729 b'+' => (TokenKind::Plus, 1),
730 b'.' => (TokenKind::Dot, 1),
731 _ => (TokenKind::Unknown, self.stream.len()),
732 };
733 let (raw, rest) = self.stream.split_at(end);
734 self.stream = rest;
735 Some(Token { kind, raw })
736 }
737}
738
739#[derive(Debug, Clone)]
741#[non_exhaustive]
742pub struct DatetimeParseError {
743 what: Option<&'static str>,
744 expected: Option<&'static str>,
745}
746
747impl DatetimeParseError {
748 fn new() -> Self {
749 Self {
750 what: None,
751 expected: None,
752 }
753 }
754 fn what(mut self, what: &'static str) -> Self {
755 self.what = Some(what);
756 self
757 }
758 fn expected(mut self, expected: &'static str) -> Self {
759 self.expected = Some(expected);
760 self
761 }
762}
763
764impl fmt::Display for DatetimeParseError {
765 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
766 if let Some(what) = self.what {
767 write!(f, "invalid {what}")?;
768 } else {
769 "invalid datetime".fmt(f)?;
770 }
771 if let Some(expected) = self.expected {
772 write!(f, ", expected {expected}")?;
773 }
774 Ok(())
775 }
776}
777
778#[cfg(feature = "std")]
779impl std::error::Error for DatetimeParseError {}
780#[cfg(all(not(feature = "std"), feature = "serde"))]
781impl serde_core::de::StdError for DatetimeParseError {}
782
783#[cfg(feature = "serde")]
784#[cfg(feature = "alloc")]
785impl serde_core::ser::Serialize for Datetime {
786 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
787 where
788 S: serde_core::ser::Serializer,
789 {
790 use crate::alloc::string::ToString as _;
791 use serde_core::ser::SerializeStruct;
792
793 let mut s = serializer.serialize_struct(NAME, 1)?;
794 s.serialize_field(FIELD, &self.to_string())?;
795 s.end()
796 }
797}
798
799#[cfg(feature = "serde")]
800#[cfg(feature = "alloc")]
801impl serde_core::ser::Serialize for Date {
802 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
803 where
804 S: serde_core::ser::Serializer,
805 {
806 Datetime::from(*self).serialize(serializer)
807 }
808}
809
810#[cfg(feature = "serde")]
811#[cfg(feature = "alloc")]
812impl serde_core::ser::Serialize for Time {
813 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
814 where
815 S: serde_core::ser::Serializer,
816 {
817 Datetime::from(*self).serialize(serializer)
818 }
819}
820
821#[cfg(feature = "serde")]
822impl<'de> serde_core::de::Deserialize<'de> for Datetime {
823 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
824 where
825 D: serde_core::de::Deserializer<'de>,
826 {
827 struct DatetimeVisitor;
828
829 impl<'de> serde_core::de::Visitor<'de> for DatetimeVisitor {
830 type Value = Datetime;
831
832 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
833 formatter.write_str("a TOML datetime")
834 }
835
836 fn visit_map<V>(self, mut visitor: V) -> Result<Datetime, V::Error>
837 where
838 V: serde_core::de::MapAccess<'de>,
839 {
840 let value = visitor.next_key::<DatetimeKey>()?;
841 if value.is_none() {
842 return Err(serde_core::de::Error::custom("datetime key not found"));
843 }
844 let v: DatetimeFromString = visitor.next_value()?;
845 Ok(v.value)
846 }
847 }
848
849 static FIELDS: [&str; 1] = [FIELD];
850 deserializer.deserialize_struct(NAME, &FIELDS, DatetimeVisitor)
851 }
852}
853
854#[cfg(feature = "serde")]
855impl<'de> serde_core::de::Deserialize<'de> for Date {
856 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
857 where
858 D: serde_core::de::Deserializer<'de>,
859 {
860 match Datetime::deserialize(deserializer)? {
861 Datetime {
862 date: Some(date),
863 time: None,
864 offset: None,
865 } => Ok(date),
866 datetime => Err(serde_core::de::Error::invalid_type(
867 serde_core::de::Unexpected::Other(datetime.type_name()),
868 &Self::type_name(),
869 )),
870 }
871 }
872}
873
874#[cfg(feature = "serde")]
875impl<'de> serde_core::de::Deserialize<'de> for Time {
876 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
877 where
878 D: serde_core::de::Deserializer<'de>,
879 {
880 match Datetime::deserialize(deserializer)? {
881 Datetime {
882 date: None,
883 time: Some(time),
884 offset: None,
885 } => Ok(time),
886 datetime => Err(serde_core::de::Error::invalid_type(
887 serde_core::de::Unexpected::Other(datetime.type_name()),
888 &Self::type_name(),
889 )),
890 }
891 }
892}
893
894#[cfg(feature = "serde")]
895struct DatetimeKey;
896
897#[cfg(feature = "serde")]
898impl<'de> serde_core::de::Deserialize<'de> for DatetimeKey {
899 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
900 where
901 D: serde_core::de::Deserializer<'de>,
902 {
903 struct FieldVisitor;
904
905 impl serde_core::de::Visitor<'_> for FieldVisitor {
906 type Value = ();
907
908 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
909 formatter.write_str("a valid datetime field")
910 }
911
912 fn visit_str<E>(self, s: &str) -> Result<(), E>
913 where
914 E: serde_core::de::Error,
915 {
916 if s == FIELD {
917 Ok(())
918 } else {
919 Err(serde_core::de::Error::custom(
920 "expected field with custom name",
921 ))
922 }
923 }
924 }
925
926 deserializer.deserialize_identifier(FieldVisitor)?;
927 Ok(Self)
928 }
929}
930
931#[cfg(feature = "serde")]
932pub(crate) struct DatetimeFromString {
933 pub(crate) value: Datetime,
934}
935
936#[cfg(feature = "serde")]
937impl<'de> serde_core::de::Deserialize<'de> for DatetimeFromString {
938 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
939 where
940 D: serde_core::de::Deserializer<'de>,
941 {
942 struct Visitor;
943
944 impl serde_core::de::Visitor<'_> for Visitor {
945 type Value = DatetimeFromString;
946
947 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
948 formatter.write_str("string containing a datetime")
949 }
950
951 fn visit_str<E>(self, s: &str) -> Result<DatetimeFromString, E>
952 where
953 E: serde_core::de::Error,
954 {
955 match s.parse() {
956 Ok(date) => Ok(DatetimeFromString { value: date }),
957 Err(e) => Err(serde_core::de::Error::custom(e)),
958 }
959 }
960 }
961
962 deserializer.deserialize_str(Visitor)
963 }
964}