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