diff --git a/src/items/builder.rs b/src/items/builder.rs index dc74cfe..2b14ecf 100644 --- a/src/items/builder.rs +++ b/src/items/builder.rs @@ -212,10 +212,15 @@ impl DateTimeBuilder { /// from the base instant. /// - b. Apply time. If time carries an explicit numeric offset, apply the /// offset before setting time. - /// - c. Apply weekday (e.g., "next Friday" or "last Monday"). + /// - c. Apply weekday (e.g., "next Friday" or "last Monday"). A weekday + /// is ignored when an explicit calendar date is given. /// - d. Apply fixed offset if present (anchors the instant). /// - e. Apply relative adjustments (e.g., "+3 days", "-2 months"). - pub(super) fn build(self) -> Result { + pub(super) fn build(mut self) -> Result { + if self.date.is_some() { + self.weekday = None; + } + if let Some(date) = self.date.as_ref() { if date.year.unwrap_or(0) > 9999 { return self.build_extended(); @@ -902,7 +907,7 @@ mod tests { } #[test] - fn build_extended_applies_relative_units_and_weekday() { + fn build_extended_applies_relative_units_and_ignores_weekday_with_date() { let base = "2000-01-01 00:00:00" .parse::() .unwrap() @@ -918,7 +923,9 @@ mod tests { ]) .unwrap(); let dt = expect_extended_datetime(builder.set_base(base).build().unwrap()); - assert_eq!((dt.year, dt.month, dt.day), (10000, 1, 4)); + // The weekday is ignored because a date is given, so only the relative + // items move it (GNU: `next mon 10000-01-01 +1 day` is 10000-01-02). + assert_eq!((dt.year, dt.month, dt.day), (10000, 1, 2)); assert_eq!((dt.hour, dt.minute, dt.second), (2, 3, 4)); } diff --git a/tests/date.rs b/tests/date.rs index e410051..5c745e5 100644 --- a/tests/date.rs +++ b/tests/date.rs @@ -434,3 +434,48 @@ fn test_zone_item_keeps_time_of_day_with_relative(#[case] input: &str, #[case] e "`{input}` should resolve to {expected} in its own zone" ); } + +// A weekday is ignored when an explicit date is given, as in GNU date +// (checked against coreutils 9.12, base Thursday 2026-10-01). +#[rstest] +#[case::mismatching_named_month("Wednesday August 17 2026", "2026-08-17 00:00:00")] +#[case::matching_named_month("Monday August 17 2026", "2026-08-17 00:00:00")] +#[case::abbreviated_with_comma("Wed, Aug 17 2026", "2026-08-17 00:00:00")] +#[case::day_month_with_comma("Wednesday, 17 August 2026", "2026-08-17 00:00:00")] +#[case::named_month_without_year("Wed Aug 17", "2026-08-17 00:00:00")] +#[case::iso_date("Wed 2026-08-17", "2026-08-17 00:00:00")] +#[case::iso_date_then_weekday("2026-08-17 Wed", "2026-08-17 00:00:00")] +#[case::us_date("Wed 08/17/2026", "2026-08-17 00:00:00")] +#[case::date_and_time("Wed 2026-08-17 10:30", "2026-08-17 10:30:00")] +#[case::time_before_date("Wed 10:30 2026-08-17", "2026-08-17 10:30:00")] +#[case::iso_datetime("2026-08-17T10:30:00 Wed", "2026-08-17 10:30:00")] +#[case::next_weekday("next Friday Sep 25 2026", "2026-09-25 00:00:00")] +#[case::next_other_weekday("next Sunday Sep 25 2026", "2026-09-25 00:00:00")] +#[case::last_weekday("last Wed 2026-08-17", "2026-08-17 00:00:00")] +#[case::ordinal_weekday("third Wed 2026-08-17", "2026-08-17 00:00:00")] +#[case::numeric_ordinal_weekday("2 Wed 2026-08-17", "2026-08-17 00:00:00")] +#[case::date_output_format("Sun Sep 25 12:00:00 AM UTC 2026", "2026-09-25 00:00:00")] +// Relative items still apply on top of the given date. +#[case::relative_day("Wed 2026-08-17 +1 day", "2026-08-18 00:00:00")] +#[case::relative_week("Wed 2026-08-17 next week", "2026-08-24 00:00:00")] +// Without a date, the weekday is still honoured. +#[case::weekday_alone("Wed", "2026-10-07 00:00:00")] +#[case::weekday_and_time("Wed 10:30", "2026-10-07 10:30:00")] +#[case::weekday_and_relative("Wed +1 day", "2026-10-08 00:00:00")] +fn test_weekday_ignored_with_explicit_date(#[case] input: &str, #[case] expected: &str) { + let base = "2026-10-01 12:00:00" + .parse::() + .unwrap() + .to_zoned(TimeZone::UTC) + .unwrap(); + + let parsed = parse_datetime::parse_datetime_at_date(base, input) + .unwrap() + .expect_in_range(); + + assert_eq!( + parsed.datetime().to_string(), + expected.parse::().unwrap().to_string(), + "`{input}` should resolve to {expected}" + ); +}