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