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