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