Skip to content

Commit a50d425

Browse files
committed
date: fix -d with relative dates and timezone abbreviations
`try_parse_with_abbreviation` resolved relative dates like "yesterday" against the system clock instead of the caller-provided reference time. Pass `now` through so both code paths use the same reference. Fixes #10788
1 parent 5d03f63 commit a50d425

File tree

2 files changed

+36
-3
lines changed

2 files changed

+36
-3
lines changed

src/uu/date/src/date.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -828,7 +828,7 @@ fn tz_abbrev_to_iana(abbrev: &str) -> Option<&str> {
828828
/// If an abbreviation is found and the date is parsable, returns `Some(Zoned)`.
829829
/// Returns `None` if no abbreviation is detected or if parsing fails, indicating
830830
/// that standard parsing should be attempted.
831-
fn try_parse_with_abbreviation<S: AsRef<str>>(date_str: S) -> Option<Zoned> {
831+
fn try_parse_with_abbreviation<S: AsRef<str>>(date_str: S, now: &Zoned) -> Option<Zoned> {
832832
let s = date_str.as_ref();
833833

834834
// Look for timezone abbreviation at the end of the string
@@ -845,7 +845,9 @@ fn try_parse_with_abbreviation<S: AsRef<str>>(date_str: S) -> Option<Zoned> {
845845
// Parse the date part (everything before the TZ abbreviation)
846846
let date_part = s.trim_end_matches(last_word).trim();
847847
// Parse in the target timezone so "10:30 EDT" means 10:30 in EDT
848-
if let Ok(parsed) = parse_datetime::parse_datetime(date_part) {
848+
if let Ok(parsed) =
849+
parse_datetime::parse_datetime_at_date(now.clone(), date_part)
850+
{
849851
let dt = parsed.datetime();
850852
if let Ok(zoned) = dt.to_zoned(tz) {
851853
return Some(zoned);
@@ -898,7 +900,7 @@ fn parse_date<S: AsRef<str> + Clone>(
898900
}
899901

900902
// First, try to parse any timezone abbreviations
901-
if let Some(zoned) = try_parse_with_abbreviation(input_str) {
903+
if let Some(zoned) = try_parse_with_abbreviation(input_str, now) {
902904
if dbg_opts.debug {
903905
eprintln!(
904906
"date: parsed date part: (Y-M-D) {}",
@@ -1099,6 +1101,14 @@ mod tests {
10991101
assert_eq!(parse_military_timezone_with_offset("9m"), None); // Starts with digit
11001102
}
11011103

1104+
#[test]
1105+
fn test_abbreviation_resolves_relative_date_against_now() {
1106+
let now = "2025-03-15T20:00:00+00:00[UTC]".parse::<Zoned>().unwrap();
1107+
let result =
1108+
parse_date("yesterday 10:00 GMT", &now, DebugOptions::new(false, false)).unwrap();
1109+
assert_eq!(result.date(), jiff::civil::date(2025, 3, 14));
1110+
}
1111+
11021112
#[test]
11031113
fn test_utc_conversion_preserves_offset() {
11041114
let now = Zoned::now();

tests/by-util/test_date.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1124,6 +1124,29 @@ fn test_date_tz_abbreviation_with_day_of_week() {
11241124
.no_stderr();
11251125
}
11261126

1127+
#[test]
1128+
fn test_date_tz_abbreviation_with_relative_date() {
1129+
// Verify that "yesterday" in "-u -d yesterday 10:00 GMT" is resolved
1130+
// relative to UTC, not the local TZ.
1131+
let expected = new_ucmd!()
1132+
.env("TZ", "UTC")
1133+
.arg("-u")
1134+
.arg("-d")
1135+
.arg("yesterday 10:00 GMT")
1136+
.arg("+%F %T %Z")
1137+
.succeeds()
1138+
.stdout_str()
1139+
.to_string();
1140+
new_ucmd!()
1141+
.env("TZ", "Australia/Sydney")
1142+
.arg("-u")
1143+
.arg("-d")
1144+
.arg("yesterday 10:00 GMT")
1145+
.arg("+%F %T %Z")
1146+
.succeeds()
1147+
.stdout_is(expected);
1148+
}
1149+
11271150
#[test]
11281151
fn test_date_tz_abbreviation_unknown() {
11291152
// Test that unknown timezone abbreviations fall back gracefully

0 commit comments

Comments
 (0)