From b0c0424b6d7d9871775b7fc9ba5563550f51d252 Mon Sep 17 00:00:00 2001 From: sap1110 <238814652+sap1110@users.noreply.github.com> Date: Thu, 1 Oct 2026 18:44:50 +0530 Subject: [PATCH 1/2] builder: ignore weekday when an explicit date is given When the input carries both a weekday and a calendar date, the date is now authoritative and the weekday is dropped, matching GNU date. This also covers ordinal forms such as "next friday sep 25 2026". Relative items are still applied on top of the date, and a weekday without a date behaves as before. Closes #317 --- src/items/builder.rs | 18 +++++++++++---- tests/date.rs | 54 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/items/builder.rs b/src/items/builder.rs index dc74cfe..8bfbce9 100644 --- a/src/items/builder.rs +++ b/src/items/builder.rs @@ -212,10 +212,18 @@ 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 { + // An explicit calendar date wins over a weekday, even a mismatching + // one or one with an ordinal: `wed 2026-08-17` and `next fri sep 25` + // both resolve to the given date, as in GNU date. + 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 +910,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 +926,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..c7995f4 100644 --- a/tests/date.rs +++ b/tests/date.rs @@ -434,3 +434,57 @@ 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 calendar date is given, whether or +// not it matches the date and whatever ordinal it carries. A weekday on its +// own (or with only a time or relative items) still moves to that day. +// +// $ TZ=UTC date -d 'wed 2026-08-17' # Mon 2026-08-17 +// $ TZ=UTC date -d 'next friday sep 25 2026' # Fri 2026-09-25 +// $ TZ=UTC date -d 'wed 10:30' # next wednesday at 10:30 +// +// `date --debug` warns that the day is ignored when explicit dates are +// given. Verified against GNU coreutils 9.7, with the base below being +// 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}" + ); +} From 7ab830e8d291697665d993fbea3ab12e01906ee1 Mon Sep 17 00:00:00 2001 From: sap1110 <238814652+sap1110@users.noreply.github.com> Date: Mon, 5 Oct 2026 16:55:05 +0530 Subject: [PATCH 2/2] fixup! builder: ignore weekday when an explicit date is given --- src/items/builder.rs | 3 --- tests/date.rs | 13 ++----------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/src/items/builder.rs b/src/items/builder.rs index 8bfbce9..2b14ecf 100644 --- a/src/items/builder.rs +++ b/src/items/builder.rs @@ -217,9 +217,6 @@ impl DateTimeBuilder { /// - d. Apply fixed offset if present (anchors the instant). /// - e. Apply relative adjustments (e.g., "+3 days", "-2 months"). pub(super) fn build(mut self) -> Result { - // An explicit calendar date wins over a weekday, even a mismatching - // one or one with an ordinal: `wed 2026-08-17` and `next fri sep 25` - // both resolve to the given date, as in GNU date. if self.date.is_some() { self.weekday = None; } diff --git a/tests/date.rs b/tests/date.rs index c7995f4..5c745e5 100644 --- a/tests/date.rs +++ b/tests/date.rs @@ -435,17 +435,8 @@ fn test_zone_item_keeps_time_of_day_with_relative(#[case] input: &str, #[case] e ); } -// A weekday is ignored when an explicit calendar date is given, whether or -// not it matches the date and whatever ordinal it carries. A weekday on its -// own (or with only a time or relative items) still moves to that day. -// -// $ TZ=UTC date -d 'wed 2026-08-17' # Mon 2026-08-17 -// $ TZ=UTC date -d 'next friday sep 25 2026' # Fri 2026-09-25 -// $ TZ=UTC date -d 'wed 10:30' # next wednesday at 10:30 -// -// `date --debug` warns that the day is ignored when explicit dates are -// given. Verified against GNU coreutils 9.7, with the base below being -// Thursday 2026-10-01. +// 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")]