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: u8,
158 pub nanosecond: 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}:{:02}", self.hour, self.minute, self.second)?;
260 if self.nanosecond != 0 {
261 let s = alloc::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 Self::Z => write!(f, "Z"),
272 Self::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<Self, DatetimeParseError> {
290 let mut result = Self {
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 second = if lexer.clone().next().map(|t| t.kind) == Some(TokenKind::Colon) {
458 let sep = lexer.next().ok_or(DatetimeParseError::new())?;
459 sep.is(TokenKind::Colon)?;
460 let second = lexer
461 .next()
462 .ok_or(DatetimeParseError::new().what("time").expected("second"))?;
463 second
464 .is(TokenKind::Digits)
465 .map_err(|err| err.what("time").expected("second"))?;
466 Some(second)
467 } else {
468 None
469 };
470
471 let nanosecond = if second.is_some()
472 && lexer.clone().next().map(|t| t.kind) == Some(TokenKind::Dot)
473 {
474 let sep = lexer.next().ok_or(DatetimeParseError::new())?;
475 sep.is(TokenKind::Dot)?;
476 let nanosecond = lexer.next().ok_or(
477 DatetimeParseError::new()
478 .what("time")
479 .expected("nanosecond"),
480 )?;
481 nanosecond
482 .is(TokenKind::Digits)
483 .map_err(|err| err.what("time").expected("nanosecond"))?;
484 Some(nanosecond)
485 } else {
486 None
487 };
488
489 if hour.raw.len() != 2 {
490 return Err(DatetimeParseError::new()
491 .what("time")
492 .expected("a two-digit hour (HH)"));
493 }
494 if minute.raw.len() != 2 {
495 return Err(DatetimeParseError::new()
496 .what("time")
497 .expected("a two-digit minute (MM)"));
498 }
499 if let Some(second) = second {
500 if second.raw.len() != 2 {
501 return Err(DatetimeParseError::new()
502 .what("time")
503 .expected("a two-digit second (SS)"));
504 }
505 }
506
507 let time = Time {
508 hour: hour.raw.parse().map_err(|_err| DatetimeParseError::new())?,
509 minute: minute
510 .raw
511 .parse()
512 .map_err(|_err| DatetimeParseError::new())?,
513 second: second
514 .map(|t| t.raw.parse().map_err(|_err| DatetimeParseError::new()))
515 .unwrap_or(Ok(0))?,
516 nanosecond: nanosecond.map(|t| s_to_nanoseconds(t.raw)).unwrap_or(0),
517 };
518
519 if time.hour > 23 {
520 return Err(DatetimeParseError::new()
521 .what("time")
522 .expected("hour between 00 and 23"));
523 }
524 if time.minute > 59 {
525 return Err(DatetimeParseError::new()
526 .what("time")
527 .expected("minute between 00 and 59"));
528 }
529 if time.second > 60 {
531 return Err(DatetimeParseError::new()
532 .what("time")
533 .expected("second between 00 and 60"));
534 }
535 if time.nanosecond > 999_999_999 {
536 return Err(DatetimeParseError::new()
537 .what("time")
538 .expected("nanoseconds overflowed"));
539 }
540
541 result.time = Some(time);
542 }
543
544 if result.date.is_some() && result.time.is_some() {
546 match lexer.next() {
547 Some(token) if token.kind == TokenKind::Z => {
548 result.offset = Some(Offset::Z);
549 }
550 Some(token) if matches!(token.kind, TokenKind::Plus | TokenKind::Dash) => {
551 let sign = if token.kind == TokenKind::Plus { 1 } else { -1 };
552 let hours = lexer
553 .next()
554 .ok_or(DatetimeParseError::new().what("offset").expected("hour"))?;
555 hours
556 .is(TokenKind::Digits)
557 .map_err(|err| err.what("offset").expected("hour"))?;
558 let sep = lexer.next().ok_or(
559 DatetimeParseError::new()
560 .what("offset")
561 .expected("`:` (HH:MM)"),
562 )?;
563 sep.is(TokenKind::Colon)
564 .map_err(|err| err.what("offset").expected("`:` (HH:MM)"))?;
565 let minutes = lexer
566 .next()
567 .ok_or(DatetimeParseError::new().what("offset").expected("minute"))?;
568 minutes
569 .is(TokenKind::Digits)
570 .map_err(|err| err.what("offset").expected("minute"))?;
571
572 if hours.raw.len() != 2 {
573 return Err(DatetimeParseError::new()
574 .what("offset")
575 .expected("a two-digit hour (HH)"));
576 }
577 if minutes.raw.len() != 2 {
578 return Err(DatetimeParseError::new()
579 .what("offset")
580 .expected("a two-digit minute (MM)"));
581 }
582
583 let hours = hours
584 .raw
585 .parse::<u8>()
586 .map_err(|_err| DatetimeParseError::new())?;
587 let minutes = minutes
588 .raw
589 .parse::<u8>()
590 .map_err(|_err| DatetimeParseError::new())?;
591
592 if hours > 23 {
593 return Err(DatetimeParseError::new()
594 .what("offset")
595 .expected("hours between 00 and 23"));
596 }
597 if minutes > 59 {
598 return Err(DatetimeParseError::new()
599 .what("offset")
600 .expected("minutes between 00 and 59"));
601 }
602
603 let total_minutes = sign * (hours as i16 * 60 + minutes as i16);
604
605 if !((-24 * 60)..=(24 * 60)).contains(&total_minutes) {
606 return Err(DatetimeParseError::new().what("offset"));
607 }
608
609 result.offset = Some(Offset::Custom {
610 minutes: total_minutes,
611 });
612 }
613 Some(_token) => {
614 return Err(DatetimeParseError::new()
615 .what("offset")
616 .expected("`Z`, +OFFSET, -OFFSET"));
617 }
618 None => {}
619 }
620 }
621
622 if lexer.unknown().is_some() {
625 return Err(DatetimeParseError::new());
626 }
627
628 Ok(result)
629 }
630}
631
632fn s_to_nanoseconds(input: &str) -> u32 {
633 let mut nanosecond = 0;
634 for (i, byte) in input.bytes().enumerate() {
635 if byte.is_ascii_digit() {
636 if i < 9 {
637 let p = 10_u32.pow(8 - i as u32);
638 nanosecond += p * u32::from(byte - b'0');
639 }
640 } else {
641 panic!("invalid nanoseconds {input:?}");
642 }
643 }
644 nanosecond
645}
646
647#[derive(Copy, Clone)]
648struct Token<'s> {
649 kind: TokenKind,
650 raw: &'s str,
651}
652
653impl Token<'_> {
654 fn is(&self, kind: TokenKind) -> Result<(), DatetimeParseError> {
655 if self.kind == kind {
656 Ok(())
657 } else {
658 Err(DatetimeParseError::new())
659 }
660 }
661}
662
663#[derive(Copy, Clone, PartialEq, Eq)]
664enum TokenKind {
665 Digits,
666 Dash,
667 Colon,
668 Dot,
669 T,
670 Space,
671 Z,
672 Plus,
673 Unknown,
674}
675
676#[derive(Copy, Clone)]
677struct Lexer<'s> {
678 stream: &'s str,
679}
680
681impl<'s> Lexer<'s> {
682 fn new(input: &'s str) -> Self {
683 Self { stream: input }
684 }
685
686 fn unknown(&mut self) -> Option<Token<'s>> {
687 let remaining = self.stream.len();
688 if remaining == 0 {
689 return None;
690 }
691 let raw = self.stream;
692 self.stream = &self.stream[remaining..remaining];
693 Some(Token {
694 kind: TokenKind::Unknown,
695 raw,
696 })
697 }
698}
699
700impl<'s> Iterator for Lexer<'s> {
701 type Item = Token<'s>;
702
703 fn next(&mut self) -> Option<Self::Item> {
704 let (kind, end) = match self.stream.as_bytes().first()? {
705 b'0'..=b'9' => {
706 let end = self
707 .stream
708 .as_bytes()
709 .iter()
710 .position(|b| !b.is_ascii_digit())
711 .unwrap_or(self.stream.len());
712 (TokenKind::Digits, end)
713 }
714 b'-' => (TokenKind::Dash, 1),
715 b':' => (TokenKind::Colon, 1),
716 b'T' | b't' => (TokenKind::T, 1),
717 b' ' => (TokenKind::Space, 1),
718 b'Z' | b'z' => (TokenKind::Z, 1),
719 b'+' => (TokenKind::Plus, 1),
720 b'.' => (TokenKind::Dot, 1),
721 _ => (TokenKind::Unknown, self.stream.len()),
722 };
723 let (raw, rest) = self.stream.split_at(end);
724 self.stream = rest;
725 Some(Token { kind, raw })
726 }
727}
728
729#[derive(Debug, Clone)]
731#[non_exhaustive]
732pub struct DatetimeParseError {
733 what: Option<&'static str>,
734 expected: Option<&'static str>,
735}
736
737impl DatetimeParseError {
738 fn new() -> Self {
739 Self {
740 what: None,
741 expected: None,
742 }
743 }
744 fn what(mut self, what: &'static str) -> Self {
745 self.what = Some(what);
746 self
747 }
748 fn expected(mut self, expected: &'static str) -> Self {
749 self.expected = Some(expected);
750 self
751 }
752}
753
754impl fmt::Display for DatetimeParseError {
755 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
756 if let Some(what) = self.what {
757 write!(f, "invalid {what}")?;
758 } else {
759 "invalid datetime".fmt(f)?;
760 }
761 if let Some(expected) = self.expected {
762 write!(f, ", expected {expected}")?;
763 }
764 Ok(())
765 }
766}
767
768#[cfg(feature = "std")]
769impl std::error::Error for DatetimeParseError {}
770#[cfg(all(not(feature = "std"), feature = "serde"))]
771impl serde_core::de::StdError for DatetimeParseError {}
772
773#[cfg(feature = "serde")]
774#[cfg(feature = "alloc")]
775impl serde_core::ser::Serialize for Datetime {
776 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
777 where
778 S: serde_core::ser::Serializer,
779 {
780 use crate::alloc::string::ToString as _;
781 use serde_core::ser::SerializeStruct;
782
783 let mut s = serializer.serialize_struct(NAME, 1)?;
784 s.serialize_field(FIELD, &self.to_string())?;
785 s.end()
786 }
787}
788
789#[cfg(feature = "serde")]
790#[cfg(feature = "alloc")]
791impl serde_core::ser::Serialize for Date {
792 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
793 where
794 S: serde_core::ser::Serializer,
795 {
796 Datetime::from(*self).serialize(serializer)
797 }
798}
799
800#[cfg(feature = "serde")]
801#[cfg(feature = "alloc")]
802impl serde_core::ser::Serialize for Time {
803 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
804 where
805 S: serde_core::ser::Serializer,
806 {
807 Datetime::from(*self).serialize(serializer)
808 }
809}
810
811#[cfg(feature = "serde")]
812impl<'de> serde_core::de::Deserialize<'de> for Datetime {
813 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
814 where
815 D: serde_core::de::Deserializer<'de>,
816 {
817 struct DatetimeVisitor;
818
819 impl<'de> serde_core::de::Visitor<'de> for DatetimeVisitor {
820 type Value = Datetime;
821
822 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
823 formatter.write_str("a TOML datetime")
824 }
825
826 fn visit_map<V>(self, mut visitor: V) -> Result<Datetime, V::Error>
827 where
828 V: serde_core::de::MapAccess<'de>,
829 {
830 let value = visitor.next_key::<DatetimeKey>()?;
831 if value.is_none() {
832 return Err(serde_core::de::Error::custom("datetime key not found"));
833 }
834 let v: DatetimeFromString = visitor.next_value()?;
835 Ok(v.value)
836 }
837 }
838
839 static FIELDS: [&str; 1] = [FIELD];
840 deserializer.deserialize_struct(NAME, &FIELDS, DatetimeVisitor)
841 }
842}
843
844#[cfg(feature = "serde")]
845impl<'de> serde_core::de::Deserialize<'de> for Date {
846 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
847 where
848 D: serde_core::de::Deserializer<'de>,
849 {
850 match Datetime::deserialize(deserializer)? {
851 Datetime {
852 date: Some(date),
853 time: None,
854 offset: None,
855 } => Ok(date),
856 datetime => Err(serde_core::de::Error::invalid_type(
857 serde_core::de::Unexpected::Other(datetime.type_name()),
858 &Self::type_name(),
859 )),
860 }
861 }
862}
863
864#[cfg(feature = "serde")]
865impl<'de> serde_core::de::Deserialize<'de> for Time {
866 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
867 where
868 D: serde_core::de::Deserializer<'de>,
869 {
870 match Datetime::deserialize(deserializer)? {
871 Datetime {
872 date: None,
873 time: Some(time),
874 offset: None,
875 } => Ok(time),
876 datetime => Err(serde_core::de::Error::invalid_type(
877 serde_core::de::Unexpected::Other(datetime.type_name()),
878 &Self::type_name(),
879 )),
880 }
881 }
882}
883
884#[cfg(feature = "serde")]
885struct DatetimeKey;
886
887#[cfg(feature = "serde")]
888impl<'de> serde_core::de::Deserialize<'de> for DatetimeKey {
889 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
890 where
891 D: serde_core::de::Deserializer<'de>,
892 {
893 struct FieldVisitor;
894
895 impl serde_core::de::Visitor<'_> for FieldVisitor {
896 type Value = ();
897
898 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
899 formatter.write_str("a valid datetime field")
900 }
901
902 fn visit_str<E>(self, s: &str) -> Result<(), E>
903 where
904 E: serde_core::de::Error,
905 {
906 if s == FIELD {
907 Ok(())
908 } else {
909 Err(serde_core::de::Error::custom(
910 "expected field with custom name",
911 ))
912 }
913 }
914 }
915
916 deserializer.deserialize_identifier(FieldVisitor)?;
917 Ok(Self)
918 }
919}
920
921#[cfg(feature = "serde")]
922pub(crate) struct DatetimeFromString {
923 pub(crate) value: Datetime,
924}
925
926#[cfg(feature = "serde")]
927impl<'de> serde_core::de::Deserialize<'de> for DatetimeFromString {
928 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
929 where
930 D: serde_core::de::Deserializer<'de>,
931 {
932 struct Visitor;
933
934 impl serde_core::de::Visitor<'_> for Visitor {
935 type Value = DatetimeFromString;
936
937 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
938 formatter.write_str("string containing a datetime")
939 }
940
941 fn visit_str<E>(self, s: &str) -> Result<DatetimeFromString, E>
942 where
943 E: serde_core::de::Error,
944 {
945 match s.parse() {
946 Ok(date) => Ok(DatetimeFromString { value: date }),
947 Err(e) => Err(serde_core::de::Error::custom(e)),
948 }
949 }
950 }
951
952 deserializer.deserialize_str(Visitor)
953 }
954}