1#[cfg(feature = "alloc")]
163extern crate alloc;
164
165#[cfg(any(feature = "alloc", feature = "std"))]
166use super::{BAD_FORMAT, ParseError};
167use super::{Fixed, InternalInternal, Item, Numeric, Pad};
168#[cfg(feature = "unstable-locales")]
169use super::{Locale, locales};
170use super::{fixed, internal_fixed, num, num0, nums};
171#[cfg(all(feature = "alloc", not(feature = "std"), not(test)))]
172use alloc::vec::Vec;
173
174#[derive(Clone, Debug)]
192pub struct StrftimeItems<'a> {
193 remainder: &'a str,
195 queue: &'static [Item<'static>],
198 lenient: bool,
199 #[cfg(feature = "unstable-locales")]
200 locale_str: &'a str,
201 #[cfg(feature = "unstable-locales")]
202 locale: Option<Locale>,
203}
204
205impl<'a> StrftimeItems<'a> {
206 #[must_use]
230 pub const fn new(s: &'a str) -> StrftimeItems<'a> {
231 StrftimeItems {
232 remainder: s,
233 queue: &[],
234 lenient: false,
235 #[cfg(feature = "unstable-locales")]
236 locale_str: "",
237 #[cfg(feature = "unstable-locales")]
238 locale: None,
239 }
240 }
241
242 #[must_use]
263 pub const fn new_lenient(s: &'a str) -> StrftimeItems<'a> {
264 StrftimeItems {
265 remainder: s,
266 queue: &[],
267 lenient: true,
268 #[cfg(feature = "unstable-locales")]
269 locale_str: "",
270 #[cfg(feature = "unstable-locales")]
271 locale: None,
272 }
273 }
274
275 #[cfg(feature = "unstable-locales")]
322 #[must_use]
323 pub const fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> {
324 StrftimeItems {
325 remainder: s,
326 queue: &[],
327 lenient: false,
328 locale_str: "",
329 locale: Some(locale),
330 }
331 }
332
333 #[cfg(any(feature = "alloc", feature = "std"))]
377 pub fn parse(self) -> Result<Vec<Item<'a>>, ParseError> {
378 self.into_iter()
379 .map(|item| match item == Item::Error {
380 false => Ok(item),
381 true => Err(BAD_FORMAT),
382 })
383 .collect()
384 }
385
386 #[cfg(any(feature = "alloc", feature = "std"))]
420 pub fn parse_to_owned(self) -> Result<Vec<Item<'static>>, ParseError> {
421 self.into_iter()
422 .map(|item| match item == Item::Error {
423 false => Ok(item.to_owned()),
424 true => Err(BAD_FORMAT),
425 })
426 .collect()
427 }
428}
429
430const HAVE_ALTERNATES: &str = "z";
431
432impl<'a> Iterator for StrftimeItems<'a> {
433 type Item = Item<'a>;
434
435 fn next(&mut self) -> Option<Item<'a>> {
436 if let Some((item, remainder)) = self.queue.split_first() {
438 self.queue = remainder;
439 return Some(item.clone());
440 }
441
442 #[cfg(feature = "unstable-locales")]
444 if !self.locale_str.is_empty() {
445 let (remainder, item) = self.parse_next_item(self.locale_str)?;
446 self.locale_str = remainder;
447 return Some(item);
448 }
449
450 let (remainder, item) = self.parse_next_item(self.remainder)?;
452 self.remainder = remainder;
453 Some(item)
454 }
455}
456
457impl<'a> StrftimeItems<'a> {
458 fn error<'b>(
459 &mut self,
460 original: &'b str,
461 error_len: &mut usize,
462 ch: Option<char>,
463 ) -> (&'b str, Item<'b>) {
464 if !self.lenient {
465 return (&original[*error_len..], Item::Error);
466 }
467
468 if let Some(c) = ch {
469 *error_len -= c.len_utf8();
470 }
471 (&original[*error_len..], Item::Literal(&original[..*error_len]))
472 }
473
474 fn parse_next_item(&mut self, mut remainder: &'a str) -> Option<(&'a str, Item<'a>)> {
475 use InternalInternal::*;
476 use Item::{Literal, Space};
477 use Numeric::*;
478
479 static D_FMT: &[Item<'static>] =
480 &[num0(Month), Literal("/"), num0(Day), Literal("/"), num0(YearMod100)];
481 static D_T_FMT: &[Item<'static>] = &[
482 fixed(Fixed::ShortWeekdayName),
483 Space(" "),
484 fixed(Fixed::ShortMonthName),
485 Space(" "),
486 nums(Day),
487 Space(" "),
488 num0(Hour),
489 Literal(":"),
490 num0(Minute),
491 Literal(":"),
492 num0(Second),
493 Space(" "),
494 num0(Year),
495 ];
496 static T_FMT: &[Item<'static>] =
497 &[num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)];
498 static T_FMT_AMPM: &[Item<'static>] = &[
499 num0(Hour12),
500 Literal(":"),
501 num0(Minute),
502 Literal(":"),
503 num0(Second),
504 Space(" "),
505 fixed(Fixed::UpperAmPm),
506 ];
507
508 match remainder.chars().next() {
509 None => None,
511
512 Some('%') => {
514 let original = remainder;
515 remainder = &remainder[1..];
516 let mut error_len = 0;
517 if self.lenient {
518 error_len += 1;
519 }
520
521 macro_rules! next {
522 () => {
523 match remainder.chars().next() {
524 Some(x) => {
525 remainder = &remainder[x.len_utf8()..];
526 if self.lenient {
527 error_len += x.len_utf8();
528 }
529 x
530 }
531 None => return Some(self.error(original, &mut error_len, None)), }
533 };
534 }
535
536 let spec = next!();
537 let pad_override = match spec {
538 '-' => Some(Pad::None),
539 '0' => Some(Pad::Zero),
540 '_' => Some(Pad::Space),
541 _ => None,
542 };
543 let is_alternate = spec == '#';
544 let spec = if pad_override.is_some() || is_alternate { next!() } else { spec };
545 if is_alternate && !HAVE_ALTERNATES.contains(spec) {
546 return Some(self.error(original, &mut error_len, Some(spec)));
547 }
548
549 macro_rules! queue {
550 [$head:expr, $($tail:expr),+ $(,)*] => ({
551 const QUEUE: &'static [Item<'static>] = &[$($tail),+];
552 self.queue = QUEUE;
553 $head
554 })
555 }
556 #[cfg(not(feature = "unstable-locales"))]
557 macro_rules! queue_from_slice {
558 ($slice:expr) => {{
559 self.queue = &$slice[1..];
560 $slice[0].clone()
561 }};
562 }
563
564 let item = match spec {
565 'A' => fixed(Fixed::LongWeekdayName),
566 'B' => fixed(Fixed::LongMonthName),
567 'C' => num0(YearDiv100),
568 'D' => {
569 queue![num0(Month), Literal("/"), num0(Day), Literal("/"), num0(YearMod100)]
570 }
571 'F' => queue![num0(Year), Literal("-"), num0(Month), Literal("-"), num0(Day)],
572 'G' => num0(IsoYear),
573 'H' => num0(Hour),
574 'I' => num0(Hour12),
575 'M' => num0(Minute),
576 'P' => fixed(Fixed::LowerAmPm),
577 'R' => queue![num0(Hour), Literal(":"), num0(Minute)],
578 'S' => num0(Second),
579 'T' => {
580 queue![num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)]
581 }
582 'U' => num0(WeekFromSun),
583 'V' => num0(IsoWeek),
584 'W' => num0(WeekFromMon),
585 #[cfg(not(feature = "unstable-locales"))]
586 'X' => queue_from_slice!(T_FMT),
587 #[cfg(feature = "unstable-locales")]
588 'X' => self.switch_to_locale_str(locales::t_fmt, T_FMT),
589 'Y' => num0(Year),
590 'Z' => fixed(Fixed::TimezoneName),
591 'a' => fixed(Fixed::ShortWeekdayName),
592 'b' | 'h' => fixed(Fixed::ShortMonthName),
593 #[cfg(not(feature = "unstable-locales"))]
594 'c' => queue_from_slice!(D_T_FMT),
595 #[cfg(feature = "unstable-locales")]
596 'c' => self.switch_to_locale_str(locales::d_t_fmt, D_T_FMT),
597 'd' => num0(Day),
598 'e' => nums(Day),
599 'f' => num0(Nanosecond),
600 'g' => num0(IsoYearMod100),
601 'j' => num0(Ordinal),
602 'k' => nums(Hour),
603 'l' => nums(Hour12),
604 'm' => num0(Month),
605 'n' => Space("\n"),
606 'p' => fixed(Fixed::UpperAmPm),
607 'q' => num(Quarter),
608 #[cfg(not(feature = "unstable-locales"))]
609 'r' => queue_from_slice!(T_FMT_AMPM),
610 #[cfg(feature = "unstable-locales")]
611 'r' => {
612 if self.locale.is_some()
613 && locales::t_fmt_ampm(self.locale.unwrap()).is_empty()
614 {
615 self.switch_to_locale_str(locales::t_fmt, T_FMT)
617 } else {
618 self.switch_to_locale_str(locales::t_fmt_ampm, T_FMT_AMPM)
619 }
620 }
621 's' => num(Timestamp),
622 't' => Space("\t"),
623 'u' => num(WeekdayFromMon),
624 'v' => {
625 queue![
626 nums(Day),
627 Literal("-"),
628 fixed(Fixed::ShortMonthName),
629 Literal("-"),
630 num0(Year)
631 ]
632 }
633 'w' => num(NumDaysFromSun),
634 #[cfg(not(feature = "unstable-locales"))]
635 'x' => queue_from_slice!(D_FMT),
636 #[cfg(feature = "unstable-locales")]
637 'x' => self.switch_to_locale_str(locales::d_fmt, D_FMT),
638 'y' => num0(YearMod100),
639 'z' => {
640 if is_alternate {
641 internal_fixed(TimezoneOffsetPermissive)
642 } else {
643 fixed(Fixed::TimezoneOffset)
644 }
645 }
646 '+' => fixed(Fixed::RFC3339),
647 ':' => {
648 if remainder.starts_with("::z") {
649 remainder = &remainder[3..];
650 fixed(Fixed::TimezoneOffsetTripleColon)
651 } else if remainder.starts_with(":z") {
652 remainder = &remainder[2..];
653 fixed(Fixed::TimezoneOffsetDoubleColon)
654 } else if remainder.starts_with('z') {
655 remainder = &remainder[1..];
656 fixed(Fixed::TimezoneOffsetColon)
657 } else {
658 self.error(original, &mut error_len, None).1
659 }
660 }
661 '.' => match next!() {
662 '3' => match next!() {
663 'f' => fixed(Fixed::Nanosecond3),
664 c => {
665 let res = self.error(original, &mut error_len, Some(c));
666 remainder = res.0;
667 res.1
668 }
669 },
670 '6' => match next!() {
671 'f' => fixed(Fixed::Nanosecond6),
672 c => {
673 let res = self.error(original, &mut error_len, Some(c));
674 remainder = res.0;
675 res.1
676 }
677 },
678 '9' => match next!() {
679 'f' => fixed(Fixed::Nanosecond9),
680 c => {
681 let res = self.error(original, &mut error_len, Some(c));
682 remainder = res.0;
683 res.1
684 }
685 },
686 'f' => fixed(Fixed::Nanosecond),
687 c => {
688 let res = self.error(original, &mut error_len, Some(c));
689 remainder = res.0;
690 res.1
691 }
692 },
693 '3' => match next!() {
694 'f' => internal_fixed(Nanosecond3NoDot),
695 c => {
696 let res = self.error(original, &mut error_len, Some(c));
697 remainder = res.0;
698 res.1
699 }
700 },
701 '6' => match next!() {
702 'f' => internal_fixed(Nanosecond6NoDot),
703 c => {
704 let res = self.error(original, &mut error_len, Some(c));
705 remainder = res.0;
706 res.1
707 }
708 },
709 '9' => match next!() {
710 'f' => internal_fixed(Nanosecond9NoDot),
711 c => {
712 let res = self.error(original, &mut error_len, Some(c));
713 remainder = res.0;
714 res.1
715 }
716 },
717 '%' => Literal("%"),
718 c => {
719 let res = self.error(original, &mut error_len, Some(c));
720 remainder = res.0;
721 res.1
722 }
723 };
724
725 if let Some(new_pad) = pad_override {
729 match item {
730 Item::Numeric(ref kind, _pad) if self.queue.is_empty() => {
731 Some((remainder, Item::Numeric(kind.clone(), new_pad)))
732 }
733 _ => Some(self.error(original, &mut error_len, None)),
734 }
735 } else {
736 Some((remainder, item))
737 }
738 }
739
740 Some(c) if c.is_whitespace() => {
742 let nextspec =
744 remainder.find(|c: char| !c.is_whitespace()).unwrap_or(remainder.len());
745 assert!(nextspec > 0);
746 let item = Space(&remainder[..nextspec]);
747 remainder = &remainder[nextspec..];
748 Some((remainder, item))
749 }
750
751 _ => {
753 let nextspec = remainder
754 .find(|c: char| c.is_whitespace() || c == '%')
755 .unwrap_or(remainder.len());
756 assert!(nextspec > 0);
757 let item = Literal(&remainder[..nextspec]);
758 remainder = &remainder[nextspec..];
759 Some((remainder, item))
760 }
761 }
762 }
763
764 #[cfg(feature = "unstable-locales")]
765 fn switch_to_locale_str(
766 &mut self,
767 localized_fmt_str: impl Fn(Locale) -> &'static str,
768 fallback: &'static [Item<'static>],
769 ) -> Item<'a> {
770 if let Some(locale) = self.locale {
771 assert!(self.locale_str.is_empty());
772 let (fmt_str, item) = self.parse_next_item(localized_fmt_str(locale)).unwrap();
773 self.locale_str = fmt_str;
774 item
775 } else {
776 self.queue = &fallback[1..];
777 fallback[0].clone()
778 }
779 }
780}
781
782#[cfg(test)]
783mod tests {
784 use super::StrftimeItems;
785 use crate::format::Item::{self, Literal, Space};
786 #[cfg(feature = "unstable-locales")]
787 use crate::format::Locale;
788 use crate::format::{Fixed, InternalInternal, Numeric::*};
789 use crate::format::{fixed, internal_fixed, num, num0, nums};
790 #[cfg(feature = "alloc")]
791 use crate::{DateTime, FixedOffset, NaiveDate, TimeZone, Timelike, Utc};
792
793 #[test]
794 fn test_strftime_items() {
795 fn parse_and_collect(s: &str) -> Vec<Item<'_>> {
796 eprintln!("test_strftime_items: parse_and_collect({:?})", s);
798 let items = StrftimeItems::new(s);
799 let items = items.map(|spec| if spec == Item::Error { None } else { Some(spec) });
800 items.collect::<Option<Vec<_>>>().unwrap_or_else(|| vec![Item::Error])
801 }
802
803 assert_eq!(parse_and_collect(""), []);
804 assert_eq!(parse_and_collect(" "), [Space(" ")]);
805 assert_eq!(parse_and_collect(" "), [Space(" ")]);
806 assert_ne!(parse_and_collect(" "), [Space(" "), Space(" ")]);
808 assert_eq!(parse_and_collect(" "), [Space(" ")]);
810 assert_eq!(parse_and_collect("a"), [Literal("a")]);
811 assert_eq!(parse_and_collect("ab"), [Literal("ab")]);
812 assert_eq!(parse_and_collect("😽"), [Literal("😽")]);
813 assert_eq!(parse_and_collect("a😽"), [Literal("a😽")]);
814 assert_eq!(parse_and_collect("😽a"), [Literal("😽a")]);
815 assert_eq!(parse_and_collect(" 😽"), [Space(" "), Literal("😽")]);
816 assert_eq!(parse_and_collect("😽 "), [Literal("😽"), Space(" ")]);
817 assert_ne!(parse_and_collect("😽😽"), [Literal("😽")]);
819 assert_ne!(parse_and_collect("😽"), [Literal("😽😽")]);
820 assert_ne!(parse_and_collect("😽😽"), [Literal("😽😽"), Literal("😽")]);
821 assert_eq!(parse_and_collect("😽😽"), [Literal("😽😽")]);
823 assert_eq!(parse_and_collect(" \t\n\r "), [Space(" \t\n\r ")]);
824 assert_eq!(parse_and_collect("hello?"), [Literal("hello?")]);
825 assert_eq!(
826 parse_and_collect("a b\t\nc"),
827 [Literal("a"), Space(" "), Literal("b"), Space("\t\n"), Literal("c")]
828 );
829 assert_eq!(parse_and_collect("100%%"), [Literal("100"), Literal("%")]);
830 assert_eq!(
831 parse_and_collect("100%% ok"),
832 [Literal("100"), Literal("%"), Space(" "), Literal("ok")]
833 );
834 assert_eq!(parse_and_collect("%%PDF-1.0"), [Literal("%"), Literal("PDF-1.0")]);
835 assert_eq!(
836 parse_and_collect("%Y-%m-%d"),
837 [num0(Year), Literal("-"), num0(Month), Literal("-"), num0(Day)]
838 );
839 assert_eq!(parse_and_collect("😽 "), [Literal("😽"), Space(" ")]);
840 assert_eq!(parse_and_collect("😽😽"), [Literal("😽😽")]);
841 assert_eq!(parse_and_collect("😽😽😽"), [Literal("😽😽😽")]);
842 assert_eq!(parse_and_collect("😽😽 😽"), [Literal("😽😽"), Space(" "), Literal("😽")]);
843 assert_eq!(parse_and_collect("😽😽a 😽"), [Literal("😽😽a"), Space(" "), Literal("😽")]);
844 assert_eq!(parse_and_collect("😽😽a b😽"), [Literal("😽😽a"), Space(" "), Literal("b😽")]);
845 assert_eq!(
846 parse_and_collect("😽😽a b😽c"),
847 [Literal("😽😽a"), Space(" "), Literal("b😽c")]
848 );
849 assert_eq!(parse_and_collect("😽😽 "), [Literal("😽😽"), Space(" ")]);
850 assert_eq!(parse_and_collect("😽😽 😽"), [Literal("😽😽"), Space(" "), Literal("😽")]);
851 assert_eq!(parse_and_collect(" 😽"), [Space(" "), Literal("😽")]);
852 assert_eq!(parse_and_collect(" 😽 "), [Space(" "), Literal("😽"), Space(" ")]);
853 assert_eq!(
854 parse_and_collect(" 😽 😽"),
855 [Space(" "), Literal("😽"), Space(" "), Literal("😽")]
856 );
857 assert_eq!(
858 parse_and_collect(" 😽 😽 "),
859 [Space(" "), Literal("😽"), Space(" "), Literal("😽"), Space(" ")]
860 );
861 assert_eq!(
862 parse_and_collect(" 😽 😽 "),
863 [Space(" "), Literal("😽"), Space(" "), Literal("😽"), Space(" ")]
864 );
865 assert_eq!(
866 parse_and_collect(" 😽 😽😽 "),
867 [Space(" "), Literal("😽"), Space(" "), Literal("😽😽"), Space(" ")]
868 );
869 assert_eq!(parse_and_collect(" 😽😽"), [Space(" "), Literal("😽😽")]);
870 assert_eq!(parse_and_collect(" 😽😽 "), [Space(" "), Literal("😽😽"), Space(" ")]);
871 assert_eq!(
872 parse_and_collect(" 😽😽 "),
873 [Space(" "), Literal("😽😽"), Space(" ")]
874 );
875 assert_eq!(
876 parse_and_collect(" 😽😽 "),
877 [Space(" "), Literal("😽😽"), Space(" ")]
878 );
879 assert_eq!(parse_and_collect(" 😽😽 "), [Space(" "), Literal("😽😽"), Space(" ")]);
880 assert_eq!(
881 parse_and_collect(" 😽 😽😽 "),
882 [Space(" "), Literal("😽"), Space(" "), Literal("😽😽"), Space(" ")]
883 );
884 assert_eq!(
885 parse_and_collect(" 😽 😽はい😽 ハンバーガー"),
886 [
887 Space(" "),
888 Literal("😽"),
889 Space(" "),
890 Literal("😽はい😽"),
891 Space(" "),
892 Literal("ハンバーガー")
893 ]
894 );
895 assert_eq!(
896 parse_and_collect("%%😽%%😽"),
897 [Literal("%"), Literal("😽"), Literal("%"), Literal("😽")]
898 );
899 assert_eq!(parse_and_collect("%Y--%m"), [num0(Year), Literal("--"), num0(Month)]);
900 assert_eq!(parse_and_collect("[%F]"), parse_and_collect("[%Y-%m-%d]"));
901 assert_eq!(parse_and_collect("100%%😽"), [Literal("100"), Literal("%"), Literal("😽")]);
902 assert_eq!(
903 parse_and_collect("100%%😽%%a"),
904 [Literal("100"), Literal("%"), Literal("😽"), Literal("%"), Literal("a")]
905 );
906 assert_eq!(parse_and_collect("😽100%%"), [Literal("😽100"), Literal("%")]);
907 assert_eq!(parse_and_collect("%m %d"), [num0(Month), Space(" "), num0(Day)]);
908 assert_eq!(parse_and_collect("%"), [Item::Error]);
909 assert_eq!(parse_and_collect("%%"), [Literal("%")]);
910 assert_eq!(parse_and_collect("%%%"), [Item::Error]);
911 assert_eq!(parse_and_collect("%a"), [fixed(Fixed::ShortWeekdayName)]);
912 assert_eq!(parse_and_collect("%aa"), [fixed(Fixed::ShortWeekdayName), Literal("a")]);
913 assert_eq!(parse_and_collect("%%a%"), [Item::Error]);
914 assert_eq!(parse_and_collect("%😽"), [Item::Error]);
915 assert_eq!(parse_and_collect("%😽😽"), [Item::Error]);
916 assert_eq!(parse_and_collect("%%%%"), [Literal("%"), Literal("%")]);
917 assert_eq!(
918 parse_and_collect("%%%%ハンバーガー"),
919 [Literal("%"), Literal("%"), Literal("ハンバーガー")]
920 );
921 assert_eq!(parse_and_collect("foo%?"), [Item::Error]);
922 assert_eq!(parse_and_collect("bar%42"), [Item::Error]);
923 assert_eq!(parse_and_collect("quux% +"), [Item::Error]);
924 assert_eq!(parse_and_collect("%.Z"), [Item::Error]);
925 assert_eq!(parse_and_collect("%:Z"), [Item::Error]);
926 assert_eq!(parse_and_collect("%-Z"), [Item::Error]);
927 assert_eq!(parse_and_collect("%0Z"), [Item::Error]);
928 assert_eq!(parse_and_collect("%_Z"), [Item::Error]);
929 assert_eq!(parse_and_collect("%.j"), [Item::Error]);
930 assert_eq!(parse_and_collect("%:j"), [Item::Error]);
931 assert_eq!(parse_and_collect("%-j"), [num(Ordinal)]);
932 assert_eq!(parse_and_collect("%0j"), [num0(Ordinal)]);
933 assert_eq!(parse_and_collect("%_j"), [nums(Ordinal)]);
934 assert_eq!(parse_and_collect("%.e"), [Item::Error]);
935 assert_eq!(parse_and_collect("%:e"), [Item::Error]);
936 assert_eq!(parse_and_collect("%-e"), [num(Day)]);
937 assert_eq!(parse_and_collect("%0e"), [num0(Day)]);
938 assert_eq!(parse_and_collect("%_e"), [nums(Day)]);
939 assert_eq!(parse_and_collect("%z"), [fixed(Fixed::TimezoneOffset)]);
940 assert_eq!(parse_and_collect("%:z"), [fixed(Fixed::TimezoneOffsetColon)]);
941 assert_eq!(parse_and_collect("%Z"), [fixed(Fixed::TimezoneName)]);
942 assert_eq!(parse_and_collect("%ZZZZ"), [fixed(Fixed::TimezoneName), Literal("ZZZ")]);
943 assert_eq!(parse_and_collect("%Z😽"), [fixed(Fixed::TimezoneName), Literal("😽")]);
944 assert_eq!(
945 parse_and_collect("%#z"),
946 [internal_fixed(InternalInternal::TimezoneOffsetPermissive)]
947 );
948 assert_eq!(parse_and_collect("%#m"), [Item::Error]);
949 }
950
951 #[test]
952 #[cfg(feature = "alloc")]
953 fn test_strftime_docs() {
954 let dt = FixedOffset::east_opt(34200)
955 .unwrap()
956 .from_local_datetime(
957 &NaiveDate::from_ymd_opt(2001, 7, 8)
958 .unwrap()
959 .and_hms_nano_opt(0, 34, 59, 1_026_490_708)
960 .unwrap(),
961 )
962 .unwrap();
963
964 assert_eq!(dt.format("%Y").to_string(), "2001");
966 assert_eq!(dt.format("%C").to_string(), "20");
967 assert_eq!(dt.format("%y").to_string(), "01");
968 assert_eq!(dt.format("%q").to_string(), "3");
969 assert_eq!(dt.format("%m").to_string(), "07");
970 assert_eq!(dt.format("%b").to_string(), "Jul");
971 assert_eq!(dt.format("%B").to_string(), "July");
972 assert_eq!(dt.format("%h").to_string(), "Jul");
973 assert_eq!(dt.format("%d").to_string(), "08");
974 assert_eq!(dt.format("%e").to_string(), " 8");
975 assert_eq!(dt.format("%e").to_string(), dt.format("%_d").to_string());
976 assert_eq!(dt.format("%a").to_string(), "Sun");
977 assert_eq!(dt.format("%A").to_string(), "Sunday");
978 assert_eq!(dt.format("%w").to_string(), "0");
979 assert_eq!(dt.format("%u").to_string(), "7");
980 assert_eq!(dt.format("%U").to_string(), "27");
981 assert_eq!(dt.format("%W").to_string(), "27");
982 assert_eq!(dt.format("%G").to_string(), "2001");
983 assert_eq!(dt.format("%g").to_string(), "01");
984 assert_eq!(dt.format("%V").to_string(), "27");
985 assert_eq!(dt.format("%j").to_string(), "189");
986 assert_eq!(dt.format("%D").to_string(), "07/08/01");
987 assert_eq!(dt.format("%x").to_string(), "07/08/01");
988 assert_eq!(dt.format("%F").to_string(), "2001-07-08");
989 assert_eq!(dt.format("%v").to_string(), " 8-Jul-2001");
990
991 assert_eq!(dt.format("%H").to_string(), "00");
993 assert_eq!(dt.format("%k").to_string(), " 0");
994 assert_eq!(dt.format("%k").to_string(), dt.format("%_H").to_string());
995 assert_eq!(dt.format("%I").to_string(), "12");
996 assert_eq!(dt.format("%l").to_string(), "12");
997 assert_eq!(dt.format("%l").to_string(), dt.format("%_I").to_string());
998 assert_eq!(dt.format("%P").to_string(), "am");
999 assert_eq!(dt.format("%p").to_string(), "AM");
1000 assert_eq!(dt.format("%M").to_string(), "34");
1001 assert_eq!(dt.format("%S").to_string(), "60");
1002 assert_eq!(dt.format("%f").to_string(), "026490708");
1003 assert_eq!(dt.format("%.f").to_string(), ".026490708");
1004 assert_eq!(dt.with_nanosecond(1_026_490_000).unwrap().format("%.f").to_string(), ".026490");
1005 assert_eq!(dt.format("%.3f").to_string(), ".026");
1006 assert_eq!(dt.format("%.6f").to_string(), ".026490");
1007 assert_eq!(dt.format("%.9f").to_string(), ".026490708");
1008 assert_eq!(dt.format("%3f").to_string(), "026");
1009 assert_eq!(dt.format("%6f").to_string(), "026490");
1010 assert_eq!(dt.format("%9f").to_string(), "026490708");
1011 assert_eq!(dt.format("%R").to_string(), "00:34");
1012 assert_eq!(dt.format("%T").to_string(), "00:34:60");
1013 assert_eq!(dt.format("%X").to_string(), "00:34:60");
1014 assert_eq!(dt.format("%r").to_string(), "12:34:60 AM");
1015
1016 assert_eq!(dt.format("%z").to_string(), "+0930");
1019 assert_eq!(dt.format("%:z").to_string(), "+09:30");
1020 assert_eq!(dt.format("%::z").to_string(), "+09:30:00");
1021 assert_eq!(dt.format("%:::z").to_string(), "+09");
1022
1023 assert_eq!(dt.format("%c").to_string(), "Sun Jul 8 00:34:60 2001");
1025 assert_eq!(dt.format("%+").to_string(), "2001-07-08T00:34:60.026490708+09:30");
1026
1027 assert_eq!(
1028 dt.with_timezone(&Utc).format("%+").to_string(),
1029 "2001-07-07T15:04:60.026490708+00:00"
1030 );
1031 assert_eq!(
1032 dt.with_timezone(&Utc),
1033 DateTime::parse_from_str("2001-07-07T15:04:60.026490708Z", "%+").unwrap()
1034 );
1035 assert_eq!(
1036 dt.with_timezone(&Utc),
1037 DateTime::parse_from_str("2001-07-07T15:04:60.026490708UTC", "%+").unwrap()
1038 );
1039 assert_eq!(
1040 dt.with_timezone(&Utc),
1041 DateTime::parse_from_str("2001-07-07t15:04:60.026490708utc", "%+").unwrap()
1042 );
1043
1044 assert_eq!(
1045 dt.with_nanosecond(1_026_490_000).unwrap().format("%+").to_string(),
1046 "2001-07-08T00:34:60.026490+09:30"
1047 );
1048 assert_eq!(dt.format("%s").to_string(), "994518299");
1049
1050 assert_eq!(dt.format("%t").to_string(), "\t");
1052 assert_eq!(dt.format("%n").to_string(), "\n");
1053 assert_eq!(dt.format("%%").to_string(), "%");
1054
1055 assert_eq!(dt.format(" %Y%d%m%%%%%t%H%M%S\t").to_string(), " 20010807%%\t003460\t");
1057 assert_eq!(
1058 dt.format(" %Y%d%m%%%%%t%H:%P:%M%S%:::z\t").to_string(),
1059 " 20010807%%\t00:am:3460+09\t"
1060 );
1061 }
1062
1063 #[test]
1064 #[cfg(all(feature = "unstable-locales", feature = "alloc"))]
1065 fn test_strftime_docs_localized() {
1066 let dt = FixedOffset::east_opt(34200)
1067 .unwrap()
1068 .with_ymd_and_hms(2001, 7, 8, 0, 34, 59)
1069 .unwrap()
1070 .with_nanosecond(1_026_490_708)
1071 .unwrap();
1072
1073 assert_eq!(dt.format_localized("%b", Locale::fr_BE).to_string(), "jui");
1075 assert_eq!(dt.format_localized("%B", Locale::fr_BE).to_string(), "juillet");
1076 assert_eq!(dt.format_localized("%h", Locale::fr_BE).to_string(), "jui");
1077 assert_eq!(dt.format_localized("%a", Locale::fr_BE).to_string(), "dim");
1078 assert_eq!(dt.format_localized("%A", Locale::fr_BE).to_string(), "dimanche");
1079 assert_eq!(dt.format_localized("%D", Locale::fr_BE).to_string(), "07/08/01");
1080 assert_eq!(dt.format_localized("%x", Locale::fr_BE).to_string(), "08/07/01");
1081 assert_eq!(dt.format_localized("%F", Locale::fr_BE).to_string(), "2001-07-08");
1082 assert_eq!(dt.format_localized("%v", Locale::fr_BE).to_string(), " 8-jui-2001");
1083
1084 assert_eq!(dt.format_localized("%P", Locale::fr_BE).to_string(), "");
1086 assert_eq!(dt.format_localized("%p", Locale::fr_BE).to_string(), "");
1087 assert_eq!(dt.format_localized("%R", Locale::fr_BE).to_string(), "00:34");
1088 assert_eq!(dt.format_localized("%T", Locale::fr_BE).to_string(), "00:34:60");
1089 assert_eq!(dt.format_localized("%X", Locale::fr_BE).to_string(), "00:34:60");
1090 assert_eq!(dt.format_localized("%r", Locale::fr_BE).to_string(), "00:34:60");
1091
1092 assert_eq!(
1094 dt.format_localized("%c", Locale::fr_BE).to_string(),
1095 "dim 08 jui 2001 00:34:60 +09:30"
1096 );
1097
1098 let nd = NaiveDate::from_ymd_opt(2001, 7, 8).unwrap();
1099
1100 assert_eq!(nd.format_localized("%b", Locale::de_DE).to_string(), "Jul");
1102 assert_eq!(nd.format_localized("%B", Locale::de_DE).to_string(), "Juli");
1103 assert_eq!(nd.format_localized("%h", Locale::de_DE).to_string(), "Jul");
1104 assert_eq!(nd.format_localized("%a", Locale::de_DE).to_string(), "So");
1105 assert_eq!(nd.format_localized("%A", Locale::de_DE).to_string(), "Sonntag");
1106 assert_eq!(nd.format_localized("%D", Locale::de_DE).to_string(), "07/08/01");
1107 assert_eq!(nd.format_localized("%x", Locale::de_DE).to_string(), "08.07.2001");
1108 assert_eq!(nd.format_localized("%F", Locale::de_DE).to_string(), "2001-07-08");
1109 assert_eq!(nd.format_localized("%v", Locale::de_DE).to_string(), " 8-Jul-2001");
1110 }
1111
1112 #[test]
1117 #[cfg(feature = "alloc")]
1118 fn test_parse_only_timezone_offset_permissive_no_panic() {
1119 use crate::NaiveDate;
1120 use crate::{FixedOffset, TimeZone};
1121 use std::fmt::Write;
1122
1123 let dt = FixedOffset::east_opt(34200)
1124 .unwrap()
1125 .from_local_datetime(
1126 &NaiveDate::from_ymd_opt(2001, 7, 8)
1127 .unwrap()
1128 .and_hms_nano_opt(0, 34, 59, 1_026_490_708)
1129 .unwrap(),
1130 )
1131 .unwrap();
1132
1133 let mut buf = String::new();
1134 let _ = write!(buf, "{}", dt.format("%#z")).expect_err("parse-only formatter should fail");
1135 }
1136
1137 #[test]
1138 #[cfg(all(feature = "unstable-locales", feature = "alloc"))]
1139 fn test_strftime_localized_korean() {
1140 let dt = FixedOffset::east_opt(34200)
1141 .unwrap()
1142 .with_ymd_and_hms(2001, 7, 8, 0, 34, 59)
1143 .unwrap()
1144 .with_nanosecond(1_026_490_708)
1145 .unwrap();
1146
1147 assert_eq!(dt.format_localized("%b", Locale::ko_KR).to_string(), " 7월");
1149 assert_eq!(dt.format_localized("%B", Locale::ko_KR).to_string(), "7월");
1150 assert_eq!(dt.format_localized("%h", Locale::ko_KR).to_string(), " 7월");
1151 assert_eq!(dt.format_localized("%a", Locale::ko_KR).to_string(), "일");
1152 assert_eq!(dt.format_localized("%A", Locale::ko_KR).to_string(), "일요일");
1153 assert_eq!(dt.format_localized("%D", Locale::ko_KR).to_string(), "07/08/01");
1154 assert_eq!(dt.format_localized("%x", Locale::ko_KR).to_string(), "2001년 07월 08일");
1155 assert_eq!(dt.format_localized("%F", Locale::ko_KR).to_string(), "2001-07-08");
1156 assert_eq!(dt.format_localized("%v", Locale::ko_KR).to_string(), " 8- 7월-2001");
1157 assert_eq!(dt.format_localized("%r", Locale::ko_KR).to_string(), "오전 12시 34분 60초");
1158
1159 assert_eq!(
1161 dt.format_localized("%c", Locale::ko_KR).to_string(),
1162 "2001년 07월 08일 (일) 오전 12시 34분 60초"
1163 );
1164 }
1165
1166 #[test]
1167 #[cfg(all(feature = "unstable-locales", feature = "alloc"))]
1168 fn test_strftime_localized_japanese() {
1169 let dt = FixedOffset::east_opt(34200)
1170 .unwrap()
1171 .with_ymd_and_hms(2001, 7, 8, 0, 34, 59)
1172 .unwrap()
1173 .with_nanosecond(1_026_490_708)
1174 .unwrap();
1175
1176 assert_eq!(dt.format_localized("%b", Locale::ja_JP).to_string(), " 7月");
1178 assert_eq!(dt.format_localized("%B", Locale::ja_JP).to_string(), "7月");
1179 assert_eq!(dt.format_localized("%h", Locale::ja_JP).to_string(), " 7月");
1180 assert_eq!(dt.format_localized("%a", Locale::ja_JP).to_string(), "日");
1181 assert_eq!(dt.format_localized("%A", Locale::ja_JP).to_string(), "日曜日");
1182 assert_eq!(dt.format_localized("%D", Locale::ja_JP).to_string(), "07/08/01");
1183 assert_eq!(dt.format_localized("%x", Locale::ja_JP).to_string(), "2001年07月08日");
1184 assert_eq!(dt.format_localized("%F", Locale::ja_JP).to_string(), "2001-07-08");
1185 assert_eq!(dt.format_localized("%v", Locale::ja_JP).to_string(), " 8- 7月-2001");
1186 assert_eq!(dt.format_localized("%r", Locale::ja_JP).to_string(), "午前12時34分60秒");
1187
1188 assert_eq!(
1190 dt.format_localized("%c", Locale::ja_JP).to_string(),
1191 "2001年07月08日 00時34分60秒"
1192 );
1193 }
1194
1195 #[test]
1196 #[cfg(all(feature = "unstable-locales", feature = "alloc"))]
1197 fn test_strftime_localized_time() {
1198 let dt1 = Utc.with_ymd_and_hms(2024, 2, 9, 6, 54, 32).unwrap();
1199 let dt2 = Utc.with_ymd_and_hms(2024, 2, 9, 18, 54, 32).unwrap();
1200 assert_eq!(dt1.format_localized("%X", Locale::nl_NL).to_string(), "06:54:32");
1202 assert_eq!(dt2.format_localized("%X", Locale::nl_NL).to_string(), "18:54:32");
1203 assert_eq!(dt1.format_localized("%X", Locale::en_US).to_string(), "06:54:32 AM");
1204 assert_eq!(dt2.format_localized("%X", Locale::en_US).to_string(), "06:54:32 PM");
1205 assert_eq!(dt1.format_localized("%X", Locale::hy_AM).to_string(), "06:54:32");
1206 assert_eq!(dt2.format_localized("%X", Locale::hy_AM).to_string(), "18:54:32");
1207 assert_eq!(dt1.format_localized("%X", Locale::chr_US).to_string(), "06:54:32 ᏌᎾᎴ");
1208 assert_eq!(dt2.format_localized("%X", Locale::chr_US).to_string(), "06:54:32 ᏒᎯᏱᎢᏗᏢ");
1209 }
1210
1211 #[test]
1212 #[cfg(all(feature = "unstable-locales", target_pointer_width = "64"))]
1213 fn test_type_sizes() {
1214 use core::mem::size_of;
1215 assert_eq!(size_of::<Item>(), 24);
1216 assert_eq!(size_of::<StrftimeItems>(), 56);
1217 assert_eq!(size_of::<Locale>(), 2);
1218 }
1219
1220 #[test]
1221 #[cfg(all(feature = "unstable-locales", target_pointer_width = "32"))]
1222 fn test_type_sizes() {
1223 use core::mem::size_of;
1224 assert_eq!(size_of::<Item>(), 12);
1225 assert_eq!(size_of::<StrftimeItems>(), 28);
1226 assert_eq!(size_of::<Locale>(), 2);
1227 }
1228
1229 #[test]
1230 #[cfg(any(feature = "alloc", feature = "std"))]
1231 fn test_strftime_parse() {
1232 let fmt_str = StrftimeItems::new("%Y-%m-%dT%H:%M:%S%z");
1233 let fmt_items = fmt_str.parse().unwrap();
1234 let dt = Utc.with_ymd_and_hms(2014, 5, 7, 12, 34, 56).unwrap();
1235 assert_eq!(&dt.format_with_items(fmt_items.iter()).to_string(), "2014-05-07T12:34:56+0000");
1236 }
1237
1238 #[test]
1239 #[cfg(any(feature = "alloc", feature = "std"))]
1240 fn test_strftime_parse_lenient() {
1241 let fmt_str = StrftimeItems::new_lenient("%Y-%m-%dT%H:%M:%S%z%Q%.2f%%%");
1242 let fmt_items = fmt_str.parse().unwrap();
1243 let dt = Utc.with_ymd_and_hms(2014, 5, 7, 12, 34, 56).unwrap();
1244 assert_eq!(
1245 &dt.format_with_items(fmt_items.iter()).to_string(),
1246 "2014-05-07T12:34:56+0000%Q%.2f%%"
1247 );
1248 }
1249}