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 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
765#[cfg(feature = "std")]
766impl std::error::Error for DatetimeParseError {}
767#[cfg(all(not(feature = "std"), feature = "serde"))]
768impl serde::de::StdError for DatetimeParseError {}
769
770#[cfg(feature = "serde")]
771#[cfg(feature = "alloc")]
772impl serde::ser::Serialize for Datetime {
773 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
774 where
775 S: serde::ser::Serializer,
776 {
777 use crate::alloc::string::ToString as _;
778 use serde::ser::SerializeStruct;
779
780 let mut s = serializer.serialize_struct(NAME, 1)?;
781 s.serialize_field(FIELD, &self.to_string())?;
782 s.end()
783 }
784}
785
786#[cfg(feature = "serde")]
787#[cfg(feature = "alloc")]
788impl serde::ser::Serialize for Date {
789 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
790 where
791 S: serde::ser::Serializer,
792 {
793 Datetime::from(*self).serialize(serializer)
794 }
795}
796
797#[cfg(feature = "serde")]
798#[cfg(feature = "alloc")]
799impl serde::ser::Serialize for Time {
800 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
801 where
802 S: serde::ser::Serializer,
803 {
804 Datetime::from(*self).serialize(serializer)
805 }
806}
807
808#[cfg(feature = "serde")]
809impl<'de> serde::de::Deserialize<'de> for Datetime {
810 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
811 where
812 D: serde::de::Deserializer<'de>,
813 {
814 struct DatetimeVisitor;
815
816 impl<'de> serde::de::Visitor<'de> for DatetimeVisitor {
817 type Value = Datetime;
818
819 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
820 formatter.write_str("a TOML datetime")
821 }
822
823 fn visit_map<V>(self, mut visitor: V) -> Result<Datetime, V::Error>
824 where
825 V: serde::de::MapAccess<'de>,
826 {
827 let value = visitor.next_key::<DatetimeKey>()?;
828 if value.is_none() {
829 return Err(serde::de::Error::custom("datetime key not found"));
830 }
831 let v: DatetimeFromString = visitor.next_value()?;
832 Ok(v.value)
833 }
834 }
835
836 static FIELDS: [&str; 1] = [FIELD];
837 deserializer.deserialize_struct(NAME, &FIELDS, DatetimeVisitor)
838 }
839}
840
841#[cfg(feature = "serde")]
842impl<'de> serde::de::Deserialize<'de> for Date {
843 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
844 where
845 D: serde::de::Deserializer<'de>,
846 {
847 match Datetime::deserialize(deserializer)? {
848 Datetime {
849 date: Some(date),
850 time: None,
851 offset: None,
852 } => Ok(date),
853 datetime => Err(serde::de::Error::invalid_type(
854 serde::de::Unexpected::Other(datetime.type_name()),
855 &Self::type_name(),
856 )),
857 }
858 }
859}
860
861#[cfg(feature = "serde")]
862impl<'de> serde::de::Deserialize<'de> for Time {
863 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
864 where
865 D: serde::de::Deserializer<'de>,
866 {
867 match Datetime::deserialize(deserializer)? {
868 Datetime {
869 date: None,
870 time: Some(time),
871 offset: None,
872 } => Ok(time),
873 datetime => Err(serde::de::Error::invalid_type(
874 serde::de::Unexpected::Other(datetime.type_name()),
875 &Self::type_name(),
876 )),
877 }
878 }
879}
880
881#[cfg(feature = "serde")]
882struct DatetimeKey;
883
884#[cfg(feature = "serde")]
885impl<'de> serde::de::Deserialize<'de> for DatetimeKey {
886 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
887 where
888 D: serde::de::Deserializer<'de>,
889 {
890 struct FieldVisitor;
891
892 impl serde::de::Visitor<'_> for FieldVisitor {
893 type Value = ();
894
895 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
896 formatter.write_str("a valid datetime field")
897 }
898
899 fn visit_str<E>(self, s: &str) -> Result<(), E>
900 where
901 E: serde::de::Error,
902 {
903 if s == FIELD {
904 Ok(())
905 } else {
906 Err(serde::de::Error::custom("expected field with custom name"))
907 }
908 }
909 }
910
911 deserializer.deserialize_identifier(FieldVisitor)?;
912 Ok(Self)
913 }
914}
915
916#[cfg(feature = "serde")]
917pub(crate) struct DatetimeFromString {
918 pub(crate) value: Datetime,
919}
920
921#[cfg(feature = "serde")]
922impl<'de> serde::de::Deserialize<'de> for DatetimeFromString {
923 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
924 where
925 D: serde::de::Deserializer<'de>,
926 {
927 struct Visitor;
928
929 impl serde::de::Visitor<'_> for Visitor {
930 type Value = DatetimeFromString;
931
932 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
933 formatter.write_str("string containing a datetime")
934 }
935
936 fn visit_str<E>(self, s: &str) -> Result<DatetimeFromString, E>
937 where
938 E: serde::de::Error,
939 {
940 match s.parse() {
941 Ok(date) => Ok(DatetimeFromString { value: date }),
942 Err(e) => Err(serde::de::Error::custom(e)),
943 }
944 }
945 }
946
947 deserializer.deserialize_str(Visitor)
948 }
949}