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