Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions src/items/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ParsedDateTime, error::Error> {
pub(super) fn build(mut self) -> Result<ParsedDateTime, error::Error> {
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();
Expand Down Expand Up @@ -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::<DateTime>()
.unwrap()
Expand All @@ -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));
}

Expand Down
45 changes: 45 additions & 0 deletions tests/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<DateTime>()
.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::<DateTime>().unwrap().to_string(),
"`{input}` should resolve to {expected}"
);
}
Loading