use core::num::NonZero; use rstest::rstest; use rstest_reuse::{apply, template}; use time::error::InvalidFormatDescription; use time::format_description::modifier::*; use time::format_description::{ self, BorrowedFormatItem, Component, OwnedFormatItem, StaticFormatDescription, }; use time::macros::format_description; /// A modifier with its value and string representation. /// /// This alias is used to avoid repeating the tuple in countless locations. type M = (T, &'static str); #[rustfmt::skip] // does not format well #[template] #[rstest] fn modifiers( #[values( (Padding::Space, "padding:space"), (Padding::Zero, "padding:zero"), (Padding::None, "padding:none"), )] padding: _, #[values( (false, "repr:24"), (true, "repr:12"), )] hour_is_12_hour_clock: _, #[values( (true, "case:upper"), (false, "case:lower"), )] period_is_uppercase: _, #[values( (MonthRepr::Numerical, "repr:numerical"), (MonthRepr::Long, "repr:long"), (MonthRepr::Short, "repr:short"), )] month_repr: _, #[values( (SubsecondDigits::One, "digits:1"), (SubsecondDigits::Two, "digits:2"), (SubsecondDigits::Three, "digits:3"), (SubsecondDigits::Four, "digits:4"), (SubsecondDigits::Five, "digits:5"), (SubsecondDigits::Six, "digits:6"), (SubsecondDigits::Seven, "digits:7"), (SubsecondDigits::Eight, "digits:8"), (SubsecondDigits::Nine, "digits:9"), (SubsecondDigits::OneOrMore, "digits:1+"), )] subsecond_digits: _, #[values( (WeekdayRepr::Short, "repr:short"), (WeekdayRepr::Long, "repr:long"), (WeekdayRepr::Sunday, "repr:sunday"), (WeekdayRepr::Monday, "repr:monday"), )] weekday_repr: _, #[values( (WeekNumberRepr::Iso, "repr:iso"), (WeekNumberRepr::Sunday, "repr:sunday"), (WeekNumberRepr::Monday, "repr:monday"), )] week_number_repr: _, #[values( (YearRepr::Full, "repr:full"), (YearRepr::Century, "repr:century"), (YearRepr::LastTwo, "repr:last_two"), )] year_repr: _, #[values( (YearRange::Standard, "range:standard"), (YearRange::Extended, "range:extended"), )] year_range: _, #[values( (false, "base:calendar"), (true, "base:iso_week"), )] year_is_iso_week_based: _, #[values( (false, "sign:automatic"), (true, "sign:mandatory"), )] sign_is_mandatory: _, #[values( (true, "one_indexed:true"), (false, "one_indexed:false"), )] weekday_is_one_indexed: _, #[values( (true, "case_sensitive:true"), (false, "case_sensitive:false"), )] case_sensitive: _, #[values( (NonZero::new(1).unwrap(), "count:1"), (NonZero::new(2).unwrap(), "count:2"), (NonZero::new(3).unwrap(), "count:3"), (NonZero::new(10).unwrap(), "count:10"), (NonZero::new(100).unwrap(), "count:100"), (NonZero::new(1_000).unwrap(), "count:1000"), )] ignore_count: _, #[values( (UnixTimestampPrecision::Second, "precision:second"), (UnixTimestampPrecision::Millisecond, "precision:millisecond"), (UnixTimestampPrecision::Microsecond, "precision:microsecond"), (UnixTimestampPrecision::Nanosecond, "precision:nanosecond"), )] unix_timestamp_precision: _, #[values( (TrailingInput::Prohibit, "trailing_input:prohibit"), (TrailingInput::Discard, "trailing_input:discard"), )] trailing_input: _, ) {} #[rstest] fn empty() { assert_eq!(format_description::parse_borrowed::<2>(""), Ok(vec![])); assert_eq!( format_description::parse_owned::<2>(""), Ok(OwnedFormatItem::Compound(Box::new([]))) ); } #[rstest] #[case("foo bar", [b"foo bar".as_slice()])] #[case(" leading spaces", [b" leading spaces".as_slice()])] #[case("trailing spaces ", [b"trailing spaces ".as_slice()])] #[case(" ", [b" ".as_slice()])] #[case("[[", [b"[".as_slice()])] #[case("foo[[bar", [b"foo".as_slice(), b"[".as_slice(), b"bar".as_slice()])] fn only_literal(#[case] format_description: &str, #[case] expected: [&[u8]; N]) { assert_eq!( format_description::parse(format_description), Ok(expected .into_iter() .map(BorrowedFormatItem::Literal) .collect()) ); } #[rstest] #[case("[day]", Component::Day(Day::default()))] #[case("[end]", Component::End(End::default()))] #[case("[hour]", Component::Hour(Hour::default()))] #[case("[minute]", Component::Minute(Minute::default()))] #[case("[month]", Component::Month(Month::default()))] #[case("[offset_hour]", Component::OffsetHour(OffsetHour::default()))] #[case("[offset_minute]", Component::OffsetMinute(OffsetMinute::default()))] #[case("[offset_second]", Component::OffsetSecond(OffsetSecond::default()))] #[case("[ordinal]", Component::Ordinal(Ordinal::default()))] #[case("[period]", Component::Period(Period::default()))] #[case("[second]", Component::Second(Second::default()))] #[case("[subsecond]", Component::Subsecond(Subsecond::default()))] #[case("[unix_timestamp]", Component::UnixTimestamp(UnixTimestamp::default()))] #[case("[weekday]", Component::Weekday(Weekday::default()))] #[case("[week_number]", Component::WeekNumber(WeekNumber::default()))] #[case("[year]", Component::Year(Year::default()))] fn simple_component(#[case] format_description: &str, #[case] component: Component) { assert_eq!( format_description::parse(format_description), Ok(vec![BorrowedFormatItem::Component(component)]) ); } #[rstest] fn errors() { use InvalidFormatDescription::*; macro_rules! assert_errs { ($($format_description:literal, $error:pat $(if $condition:expr)?,)*) => {$( assert!(matches!( format_description::parse($format_description), Err($error) $(if $condition)? )); assert!(matches!( format_description::parse_owned::<2>($format_description), Err($error) $(if $condition)? )); )*}; } assert_errs! { "[ invalid ]", InvalidComponentName { name, index: 2, .. } if name == "invalid", "[", MissingComponentName { index: 0, .. }, "[ ", MissingComponentName { index: 1, .. }, "[]", MissingComponentName { index: 0, .. }, "[day sign:mandatory]", InvalidModifier { value, index: 5, .. } if value == "sign", "[day sign:]", InvalidModifier { value, index: 9,.. } if value.is_empty(), "[day :mandatory]", InvalidModifier { value, index: 5,.. } if value.is_empty(), "[day sign:mandatory", UnclosedOpeningBracket { index: 0, .. }, "[day padding:invalid]", InvalidModifier { value, index: 13, .. } if value == "invalid", "[ignore]", MissingRequiredModifier { name: "count", index: 1, .. }, "[ignore count:70000]", InvalidModifier { value, index: 14, .. } if value == "70000", } } macro_rules! placeholder { ($($x:tt)*) => { " {}" }; } macro_rules! parse_with_modifiers { ($modifier_name:literal, $($modifier:ident),+) => { format_description::parse( &format!( concat!( "[", $modifier_name, $(placeholder!($modifier),)+ "]", ), $($modifier.1),+ ) ) }; } #[apply(modifiers)] fn day_component(padding: M) { assert_eq!( parse_with_modifiers!("day", padding), Ok(vec![BorrowedFormatItem::Component(Component::Day( Day::default().with_padding(padding.0) ))]) ); } #[apply(modifiers)] fn end_component(trailing_input: M) { assert_eq!( parse_with_modifiers!("end", trailing_input), Ok(vec![BorrowedFormatItem::Component(Component::End( End::default().with_trailing_input(trailing_input.0) ))]) ); } #[apply(modifiers)] fn minute_component(padding: M) { assert_eq!( parse_with_modifiers!("minute", padding), Ok(vec![BorrowedFormatItem::Component(Component::Minute( Minute::default().with_padding(padding.0) ))]) ); } #[apply(modifiers)] fn offset_minute_component(padding: M) { assert_eq!( parse_with_modifiers!("offset_minute", padding), Ok(vec![BorrowedFormatItem::Component( Component::OffsetMinute(OffsetMinute::default().with_padding(padding.0)) )]) ); } #[apply(modifiers)] fn offset_second_component(padding: M) { assert_eq!( parse_with_modifiers!("offset_second", padding), Ok(vec![BorrowedFormatItem::Component( Component::OffsetSecond(OffsetSecond::default().with_padding(padding.0)) )]) ); } #[apply(modifiers)] fn ordinal_component(padding: M) { assert_eq!( parse_with_modifiers!("ordinal", padding), Ok(vec![BorrowedFormatItem::Component(Component::Ordinal( Ordinal::default().with_padding(padding.0) ))]) ); } #[apply(modifiers)] fn second_component(padding: M) { assert_eq!( parse_with_modifiers!("second", padding), Ok(vec![BorrowedFormatItem::Component(Component::Second( Second::default().with_padding(padding.0) ))]) ); } #[apply(modifiers)] fn hour_component(padding: M, hour_is_12_hour_clock: M) { assert_eq!( parse_with_modifiers!("hour", padding, hour_is_12_hour_clock), Ok(vec![BorrowedFormatItem::Component(Component::Hour( Hour::default() .with_padding(padding.0) .with_is_12_hour_clock(hour_is_12_hour_clock.0) ))]) ); } #[apply(modifiers)] fn month_component(padding: M, case_sensitive: M, month_repr: M) { assert_eq!( parse_with_modifiers!("month", padding, case_sensitive, month_repr), Ok(vec![BorrowedFormatItem::Component(Component::Month( Month::default() .with_padding(padding.0) .with_repr(month_repr.0) .with_case_sensitive(case_sensitive.0) ))]) ); } #[apply(modifiers)] fn period_component(case_sensitive: M, period_is_uppercase: M) { assert_eq!( parse_with_modifiers!("period", period_is_uppercase, case_sensitive), Ok(vec![BorrowedFormatItem::Component(Component::Period( Period::default() .with_is_uppercase(period_is_uppercase.0) .with_case_sensitive(case_sensitive.0) ))]) ); } #[apply(modifiers)] fn weekday_component( case_sensitive: M, weekday_is_one_indexed: M, weekday_repr: M, ) { assert_eq!( parse_with_modifiers!( "weekday", case_sensitive, weekday_is_one_indexed, weekday_repr ), Ok(vec![BorrowedFormatItem::Component(Component::Weekday( Weekday::default() .with_repr(weekday_repr.0) .with_one_indexed(weekday_is_one_indexed.0) .with_case_sensitive(case_sensitive.0) ))]) ); } #[apply(modifiers)] fn week_number_component(padding: M, week_number_repr: M) { assert_eq!( parse_with_modifiers!("week_number", padding, week_number_repr), Ok(vec![BorrowedFormatItem::Component(Component::WeekNumber( WeekNumber::default() .with_padding(padding.0) .with_repr(week_number_repr.0) ))]) ); } #[apply(modifiers)] fn offset_hour_component(padding: M, sign_is_mandatory: M) { assert_eq!( parse_with_modifiers!("offset_hour", padding, sign_is_mandatory), Ok(vec![BorrowedFormatItem::Component(Component::OffsetHour( OffsetHour::default() .with_padding(padding.0) .with_sign_is_mandatory(sign_is_mandatory.0) ))]) ); } #[apply(modifiers)] fn year_component( padding: M, year_repr: M, year_range: M, year_is_iso_week_based: M, sign_is_mandatory: M, ) { assert_eq!( parse_with_modifiers!( "year", padding, year_repr, year_range, year_is_iso_week_based, sign_is_mandatory ), Ok(vec![BorrowedFormatItem::Component(Component::Year( Year::default() .with_padding(padding.0) .with_repr(year_repr.0) .with_range(year_range.0) .with_iso_week_based(year_is_iso_week_based.0) .with_sign_is_mandatory(sign_is_mandatory.0) ))]) ); } #[apply(modifiers)] fn unix_timestamp_component( sign_is_mandatory: M, unix_timestamp_precision: M, ) { assert_eq!( parse_with_modifiers!( "unix_timestamp", sign_is_mandatory, unix_timestamp_precision ), Ok(vec![BorrowedFormatItem::Component( Component::UnixTimestamp( UnixTimestamp::default() .with_sign_is_mandatory(sign_is_mandatory.0) .with_precision(unix_timestamp_precision.0) ) )]) ); } #[apply(modifiers)] fn subsecond_component(subsecond_digits: M) { assert_eq!( parse_with_modifiers!("subsecond", subsecond_digits), Ok(vec![BorrowedFormatItem::Component(Component::Subsecond( Subsecond::default().with_digits(subsecond_digits.0) ))]), ); } #[apply(modifiers)] fn ignore_component(ignore_count: M>) { assert_eq!( parse_with_modifiers!("ignore", ignore_count), Ok(vec![BorrowedFormatItem::Component(Component::Ignore( Ignore::count(ignore_count.0) ))]) ); } #[rstest] fn optional() { assert_eq!( format_description::parse_owned::<2>("[optional [:[year]]]"), Ok(OwnedFormatItem::Optional(Box::new( OwnedFormatItem::Compound(Box::new([ OwnedFormatItem::Literal(Box::new(*b":")), OwnedFormatItem::Component(Component::Year(Default::default())) ])) ))) ); assert_eq!( format_description::parse_owned::<2>("[optional [[year]]]"), Ok(OwnedFormatItem::Optional(Box::new( OwnedFormatItem::Component(Component::Year(Default::default())) ))) ); assert_eq!( format_description::parse_owned::<2>(r"[optional [\[]]"), Ok(OwnedFormatItem::Optional(Box::new( OwnedFormatItem::Literal(Box::new(*b"[")) ))) ); assert_eq!( format_description::parse_owned::<2>(r"[optional [ \[ ]]"), Ok(OwnedFormatItem::Optional(Box::new( OwnedFormatItem::Compound(Box::new([ OwnedFormatItem::Literal(Box::new(*b" ")), OwnedFormatItem::Literal(Box::new(*b"[")), OwnedFormatItem::Literal(Box::new(*b" ")), ])) ))) ); } #[rstest] fn first() { assert_eq!( format_description::parse_owned::<2>("[first [a]]"), Ok(OwnedFormatItem::First(Box::new([ OwnedFormatItem::Literal(Box::new(*b"a")) ]))) ); assert_eq!( format_description::parse_owned::<2>("[first [a] [b]]"), Ok(OwnedFormatItem::First(Box::new([ OwnedFormatItem::Literal(Box::new(*b"a")), OwnedFormatItem::Literal(Box::new(*b"b")), ]))) ); assert_eq!( format_description::parse_owned::<2>("[first [a][b]]"), Ok(OwnedFormatItem::First(Box::new([ OwnedFormatItem::Literal(Box::new(*b"a")), OwnedFormatItem::Literal(Box::new(*b"b")), ]))) ); assert_eq!( format_description::parse_owned::<2>(r"[first [a][\[]]"), Ok(OwnedFormatItem::First(Box::new([ OwnedFormatItem::Literal(Box::new(*b"a")), OwnedFormatItem::Literal(Box::new(*b"[")), ]))) ); assert_eq!( format_description::parse_owned::<2>(r"[first [a][\[\[]]"), Ok(OwnedFormatItem::First(Box::new([ OwnedFormatItem::Literal(Box::new(*b"a")), OwnedFormatItem::Compound(Box::new([ OwnedFormatItem::Literal(Box::new(*b"[")), OwnedFormatItem::Literal(Box::new(*b"[")), ])) ]))) ); assert_eq!( format_description::parse_owned::<2>( "[first [[period case:upper]] [[period case:lower]] ]" ), Ok(OwnedFormatItem::First(Box::new([ OwnedFormatItem::Component(Component::Period( Period::default() .with_is_uppercase(true) .with_case_sensitive(true) )), OwnedFormatItem::Component(Component::Period( Period::default() .with_is_uppercase(false) .with_case_sensitive(true) )), ]))) ); } #[rstest] fn backslash_escape() { assert_eq!( format_description::parse_owned::<2>(r"[optional [\]]]"), Ok(OwnedFormatItem::Optional(Box::new( OwnedFormatItem::Literal(Box::new(*b"]")) ))) ); assert_eq!( format_description::parse_owned::<2>(r"[optional [\[]]"), Ok(OwnedFormatItem::Optional(Box::new( OwnedFormatItem::Literal(Box::new(*b"[")) ))) ); assert_eq!( format_description::parse_owned::<2>(r"[optional [\\]]"), Ok(OwnedFormatItem::Optional(Box::new( OwnedFormatItem::Literal(Box::new(*br"\")) ))) ); assert_eq!( format_description::parse_owned::<2>(r"\\"), Ok(OwnedFormatItem::Literal(Box::new(*br"\"))) ); assert_eq!( format_description::parse_owned::<2>(r"\["), Ok(OwnedFormatItem::Literal(Box::new(*br"["))) ); assert_eq!( format_description::parse_owned::<2>(r"\]"), Ok(OwnedFormatItem::Literal(Box::new(*br"]"))) ); assert_eq!( format_description::parse_owned::<2>(r"foo\\"), Ok(OwnedFormatItem::Compound(Box::new([ OwnedFormatItem::Literal(Box::new(*b"foo")), OwnedFormatItem::Literal(Box::new(*br"\")), ]))) ); assert_eq!( format_description::parse_borrowed::<2>(r"\\"), Ok(vec![BorrowedFormatItem::Literal(br"\")]) ); assert_eq!( format_description::parse_borrowed::<2>(r"\["), Ok(vec![BorrowedFormatItem::Literal(br"[")]) ); assert_eq!( format_description::parse_borrowed::<2>(r"\]"), Ok(vec![BorrowedFormatItem::Literal(br"]")]) ); assert_eq!( format_description::parse_borrowed::<2>(r"foo\\"), Ok(vec![ BorrowedFormatItem::Literal(b"foo"), BorrowedFormatItem::Literal(br"\"), ]) ); } #[rstest] #[case(r"\a", 1)] #[case(r"\", 0)] fn backslash_escape_error(#[case] format_description: &str, #[case] expected_index: usize) { assert!(matches!( format_description::parse_owned::<2>(format_description), Err(InvalidFormatDescription::Expected { what: "valid escape sequence", index, .. }) if index == expected_index )); assert!(matches!( format_description::parse_borrowed::<2>(format_description), Err(InvalidFormatDescription::Expected { what: "valid escape sequence", index, .. }) if index == expected_index )); } #[rstest] fn nested_v1_error() { assert!(matches!( format_description::parse_owned::<2>("[optional [[[]]"), Err(InvalidFormatDescription::MissingComponentName { index: 11, .. }) )); assert!(matches!( format_description::parse_owned::<2>("[optional [ [[ ]]"), Err(InvalidFormatDescription::MissingComponentName { index: 12, .. }) )); assert!(matches!( format_description::parse_owned::<2>("[first [a][[[]]"), Err(InvalidFormatDescription::UnclosedOpeningBracket { index: 0, .. }) )); } #[rstest] fn nested_error() { use InvalidFormatDescription::*; assert!(matches!( format_description::parse("[optional []]"), Err(NotSupported { what: "optional item", context: "runtime-parsed format descriptions", index: 0, .. }) )); assert!(matches!( format_description::parse("[first []]"), Err(NotSupported { what: "'first' item", context: "runtime-parsed format descriptions", index: 0, .. }) )); assert!(matches!( format_description::parse_owned::<2>("[year [month]]"), Err(InvalidModifier { value, index: 6, .. }) if value == "[" )); assert!(matches!( format_description::parse_owned::<2>("[optional[]]"), Err(Expected { what: "whitespace after `optional`", index: 8, .. }) )); assert!(matches!( format_description::parse_owned::<2>("[first[]]"), Err(Expected { what: "whitespace after `first`", index: 5, .. }) )); assert!(matches!( format_description::parse_owned::<2>("[optional []"), Err(UnclosedOpeningBracket { index: 0, .. }) )); assert!(matches!( format_description::parse_owned::<2>("[first []"), Err(UnclosedOpeningBracket { index: 0, .. }) )); assert!(matches!( format_description::parse_owned::<2>("[optional ["), Err(UnclosedOpeningBracket { index: 10, .. }) )); assert!(matches!( format_description::parse_owned::<2>("[optional [[year"), Err(UnclosedOpeningBracket { index: 11, .. }) )); assert!(matches!( format_description::parse_owned::<2>("[optional "), Err(Expected { what: "opening bracket", index: 9, .. }) )); } #[rstest] #[case("[", "missing component name at byte index 0")] #[case("[foo", "unclosed opening bracket at byte index 0")] #[case("[foo]", "invalid component name `foo` at byte index 1")] #[case("[day bar]", "invalid modifier `bar` at byte index 5")] #[case("[]", "missing component name at byte index 0")] #[case( "[optional []]", "optional item is not supported in runtime-parsed format descriptions at byte index 0" )] #[case( "[ignore]", "missing required modifier `count` for component at byte index 1" )] fn error_display(#[case] format_description: &str, #[case] error: &str) { // la10736/rstest#217 #[expect(clippy::unwrap_used, reason = "purpose of the test")] let test = || { assert_eq!( format_description::parse(format_description) .unwrap_err() .to_string(), error ); }; test(); } #[rstest] #[case("[optional ", "expected opening bracket at byte index 9")] fn error_display_owned(#[case] format_description: &str, #[case] error: &str) { // la10736/rstest#217 #[expect(clippy::unwrap_used, reason = "purpose of the test")] let test = || { assert_eq!( format_description::parse_owned::<2>(format_description) .unwrap_err() .to_string(), error ) }; test(); } #[rstest] fn rfc_3339() { assert_eq!( format_description::parse( "[year]-[month repr:numerical]-[day]T[hour]:[minute]:[second].[subsecond][offset_hour \ sign:mandatory]:[offset_minute]" ), Ok(vec![ BorrowedFormatItem::Component(Component::Year( Year::default() .with_padding(Padding::Zero) .with_repr(YearRepr::Full) .with_iso_week_based(false) .with_sign_is_mandatory(false) )), BorrowedFormatItem::Literal(b"-"), BorrowedFormatItem::Component(Component::Month( Month::default().with_padding(Padding::Zero) )), BorrowedFormatItem::Literal(b"-"), BorrowedFormatItem::Component(Component::Day( Day::default().with_padding(Padding::Zero) )), BorrowedFormatItem::Literal(b"T"), BorrowedFormatItem::Component(Component::Hour( Hour::default() .with_padding(Padding::Zero) .with_is_12_hour_clock(false) )), BorrowedFormatItem::Literal(b":"), BorrowedFormatItem::Component(Component::Minute( Minute::default().with_padding(Padding::Zero) )), BorrowedFormatItem::Literal(b":"), BorrowedFormatItem::Component(Component::Second( Second::default().with_padding(Padding::Zero) )), BorrowedFormatItem::Literal(b"."), BorrowedFormatItem::Component(Component::Subsecond( Subsecond::default().with_digits(SubsecondDigits::OneOrMore) )), BorrowedFormatItem::Component(Component::OffsetHour( OffsetHour::default() .with_padding(Padding::Zero) .with_sign_is_mandatory(true) )), BorrowedFormatItem::Literal(b":"), BorrowedFormatItem::Component(Component::OffsetMinute( OffsetMinute::default().with_padding(Padding::Zero) )) ]) ); } #[rstest] #[case("foo", format_description!("foo"))] #[case("%a", format_description!("[weekday repr:short]"))] #[case("%A", format_description!("[weekday]"))] #[case("%b", format_description!("[month repr:short]"))] #[case("%B", format_description!("[month repr:long]"))] #[case("%C", format_description!("[year repr:century]"))] #[case("%d", format_description!("[day]"))] #[case("%e", format_description!("[day padding:space]"))] #[case("%g", format_description!("[year repr:last_two base:iso_week]"))] #[case("%G", format_description!("[year base:iso_week]"))] #[case("%h", format_description!("[month repr:short]"))] #[case("%H", format_description!("[hour]"))] #[case("%I", format_description!("[hour repr:12]"))] #[case("%j", format_description!("[ordinal]"))] #[case("%k", format_description!("[hour padding:space]"))] #[case("%l", format_description!("[hour repr:12 padding:space]"))] #[case("%m", format_description!("[month]"))] #[case("%M", format_description!("[minute]"))] #[case("%n", format_description!("\n"))] #[case("%p", format_description!("[period]"))] #[case("%P", format_description!("[period case:lower]"))] #[case("%s", format_description!("[unix_timestamp]"))] #[case("%S", format_description!("[second]"))] #[case("%t", format_description!("\t"))] #[case("%u", format_description!("[weekday repr:monday]"))] #[case("%U", format_description!("[week_number repr:sunday]"))] #[case("%V", format_description!("[week_number]"))] #[case("%w", format_description!("[weekday repr:sunday]"))] #[case("%W", format_description!("[week_number repr:monday]"))] #[case("%y", format_description!("[year repr:last_two]"))] #[case("%Y", format_description!("[year]"))] #[case("%%", format_description!("%"))] fn strftime_equivalence( #[case] strftime: &str, #[case] custom: StaticFormatDescription, ) -> time::Result<()> { let borrowed = format_description::parse_strftime_borrowed(strftime)?; let owned = format_description::parse_strftime_owned(strftime)?; assert_eq!(borrowed, custom); assert_eq!(owned, OwnedFormatItem::from(custom)); Ok(()) } #[rstest] #[case( "%c", "[weekday repr:short] [month repr:short] [day padding:space] [hour]:[minute]:[second] [year]" )] #[case("%D", "[month]/[day]/[year repr:last_two]")] #[case("%F", "[year]-[month repr:numerical]-[day]")] #[case("%r", "[hour repr:12]:[minute]:[second] [period]")] #[case("%R", "[hour]:[minute]")] #[case("%T", "[hour]:[minute]:[second]")] #[case("%x", "[month]/[day]/[year repr:last_two]")] #[case("%X", "[hour]:[minute]:[second]")] #[case("%z", "[offset_hour sign:mandatory][offset_minute]")] fn strftime_compound_equivalence(#[case] strftime: &str, #[case] custom: &str) -> time::Result<()> { let borrowed = format_description::parse_strftime_borrowed(strftime)?; let owned = format_description::parse_strftime_owned(strftime)?; let custom = format_description::parse(custom)?; // Until equality is implemented better, we need to convert to a compound. let custom = vec![BorrowedFormatItem::Compound(&custom)]; assert_eq!(borrowed, custom); assert_eq!(owned, OwnedFormatItem::from(custom)); Ok(()) }