From 0047c7e66ffb579713d0673dec17875d7e6fbfeb Mon Sep 17 00:00:00 2001 From: naoNao89 <90588855+naoNao89@users.noreply.github.com> Date: Tue, 14 Oct 2025 11:33:21 +0700 Subject: [PATCH] fix(date): align pure-digit -d parsing with GNU semantics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement GNU 'Pure numbers in date strings' for time-of-day tokens: - 1–2 digits => HH:00 today - 3–4 digits => HHMM today - Validate ranges; reject invalid times (e.g., 2400, 2360) Also: - Add tests for -d0, -d7, -d0700 under TZ=UTC0 and invalid numeric inputs - Reference GNU manual section for pure numbers --- src/uu/date/src/date.rs | 43 +++++++++++++++++++++++++++- tests/by-util/test_date.rs | 57 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index c3be8ba9d..7a7cebefe 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -203,7 +203,48 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // Iterate over all dates - whether it's a single date or a file. let dates: Box> = match settings.date_source { DateSource::Human(ref input) => { - let date = parse_date(input); + // GNU compatibility (Pure numbers in date strings): + // - Manual: https://www.gnu.org/software/coreutils/manual/html_node/Pure-numbers-in-date-strings.html + // - Semantics: a pure decimal number denotes today’s time-of-day (HH or HHMM). + // Examples: "0"/"00" => 00:00 today; "7"/"07" => 07:00 today; "0700" => 07:00 today. + // For all other forms, fall back to the general parser. + let is_pure_digits = + !input.is_empty() && input.len() <= 4 && input.chars().all(|c| c.is_ascii_digit()); + + let date = if is_pure_digits { + // Derive HH and MM from the input + let (hh_opt, mm_opt) = if input.len() <= 2 { + (input.parse::().ok(), Some(0u32)) + } else { + let (h, m) = input.split_at(input.len() - 2); + (h.parse::().ok(), m.parse::().ok()) + }; + + if let (Some(hh), Some(mm)) = (hh_opt, mm_opt) { + // Compose a concrete datetime string for today with zone offset. + // Use the already-determined 'now' and settings.utc to select offset. + let date_part = + strtime::format("%F", &now).unwrap_or_else(|_| String::from("1970-01-01")); + // If -u, force +00:00; otherwise use the local offset of 'now'. + let offset = if settings.utc { + String::from("+00:00") + } else { + strtime::format("%:z", &now).unwrap_or_default() + }; + let composed = if offset.is_empty() { + format!("{date_part} {hh:02}:{mm:02}") + } else { + format!("{date_part} {hh:02}:{mm:02} {offset}") + }; + parse_date(composed) + } else { + // Fallback on parse failure of digits + parse_date(input) + } + } else { + parse_date(input) + }; + let iter = std::iter::once(date); Box::new(iter) } diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs index 49514eb58..1b63dff26 100644 --- a/tests/by-util/test_date.rs +++ b/tests/by-util/test_date.rs @@ -778,3 +778,60 @@ fn test_date_resolution_no_combine() { .arg("2025-01-01") .fails(); } + +#[test] +fn test_date_numeric_d_basic_utc() { + // Verify GNU-compatible pure-digit parsing for -d STRING under UTC + // 0/00 -> today at 00:00; 7/07 -> today at 07:00; 0700 -> today at 07:00 + let today = Utc::now().date_naive(); + let yyyy = today.year(); + let mm = today.month(); + let dd = today.day(); + + let mk = + |h: u32, m: u32| -> String { format!("{yyyy:04}-{mm:02}-{dd:02} {h:02}:{m:02}:00 UTC\n") }; + + new_ucmd!() + .env("TZ", "UTC0") + .arg("-d") + .arg("0") + .arg("+%F %T %Z") + .succeeds() + .stdout_only(mk(0, 0)); + + new_ucmd!() + .env("TZ", "UTC0") + .arg("-d") + .arg("7") + .arg("+%F %T %Z") + .succeeds() + .stdout_only(mk(7, 0)); + + new_ucmd!() + .env("TZ", "UTC0") + .arg("-d") + .arg("0700") + .arg("+%F %T %Z") + .succeeds() + .stdout_only(mk(7, 0)); +} + +#[test] +fn test_date_numeric_d_invalid_numbers() { + // Ensure invalid HHMM values are rejected (GNU-compatible) + new_ucmd!() + .env("TZ", "UTC0") + .arg("-d") + .arg("2400") + .arg("+%F %T %Z") + .fails() + .stderr_contains("invalid date"); + + new_ucmd!() + .env("TZ", "UTC0") + .arg("-d") + .arg("2360") + .arg("+%F %T %Z") + .fails() + .stderr_contains("invalid date"); +}