From bca02e45f223e5a3b0cb0fd9e1c7b5682095adcb Mon Sep 17 00:00:00 2001 From: yuankunzhang Date: Sat, 9 Aug 2025 22:18:56 +0800 Subject: [PATCH 1/2] date: consolidate date parsing logic Removed the `parse_offset` function and rely on `parse_date` only. --- src/uu/date/src/date.rs | 54 +++++++++-------------------------------- 1 file changed, 11 insertions(+), 43 deletions(-) diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index ea76a631e..19e0a8712 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -8,7 +8,7 @@ use clap::{Arg, ArgAction, Command}; use jiff::fmt::strtime; use jiff::tz::TimeZone; -use jiff::{SignedDuration, Timestamp, Zoned}; +use jiff::{Timestamp, Zoned}; #[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))] use libc::{CLOCK_REALTIME, clock_settime, timespec}; use std::fs::File; @@ -64,10 +64,9 @@ enum Format { /// Various places that dates can come from enum DateSource { Now, - Custom(String), File(PathBuf), Stdin, - Human(SignedDuration), + Human(String), } enum Iso8601Format { @@ -141,11 +140,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; let date_source = if let Some(date) = matches.get_one::(OPT_DATE) { - if let Ok(duration) = parse_offset(date.as_str()) { - DateSource::Human(duration) - } else { - DateSource::Custom(date.into()) - } + DateSource::Human(date.into()) } else if let Some(file) = matches.get_one::(OPT_FILE) { match file.as_ref() { "-" => DateSource::Stdin, @@ -176,7 +171,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { if let Some(date) = settings.set_to { // All set time functions expect UTC datetimes. let date = if settings.utc { - date.with_time_zone(TimeZone::UTC) + date.datetime().to_zoned(TimeZone::UTC).map_err(|e| { + USimpleError::new(1, translate!("date-error-invalid-date", "error" => e)) + })? } else { date }; @@ -193,27 +190,11 @@ 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::Custom(ref input) => { + DateSource::Human(ref input) => { let date = parse_date(input); let iter = std::iter::once(date); Box::new(iter) } - DateSource::Human(relative_time) => { - // Double check the result is overflow or not of the current_time + relative_time - // it may cause a panic of chrono::datetime::DateTime add - match now.checked_add(relative_time) { - Ok(date) => { - let iter = std::iter::once(Ok(date)); - Box::new(iter) - } - Err(_) => { - return Err(USimpleError::new( - 1, - translate!("date-error-date-overflow", "date" => relative_time), - )); - } - } - } DateSource::Stdin => { let lines = BufReader::new(std::io::stdin()).lines(); let iter = lines.map_while(Result::ok).map(parse_date); @@ -391,28 +372,15 @@ fn parse_date + Clone>( Ok(date) => { let timestamp = Timestamp::new(date.timestamp(), date.timestamp_subsec_nanos() as i32).unwrap(); - Ok(Zoned::new(timestamp, TimeZone::UTC)) + Ok(Zoned::new( + timestamp, + TimeZone::try_system().unwrap_or(TimeZone::UTC), + )) } Err(e) => Err((s.as_ref().into(), e)), } } -// TODO: Convert `parse_datetime` to jiff and remove wrapper from chrono to jiff structures. -// Also, consider whether parse_datetime::parse_datetime_at_date can be renamed to something -// like parse_datetime::parse_offset, instead of doing some addition/subtraction. -fn parse_offset(date: &str) -> Result { - let ref_time = chrono::Local::now(); - if let Ok(new_time) = parse_datetime::parse_datetime_at_date(ref_time, date) { - let duration = new_time.signed_duration_since(ref_time); - Ok(SignedDuration::new( - duration.num_seconds(), - duration.subsec_nanos(), - )) - } else { - Err(()) - } -} - #[cfg(not(any(unix, windows)))] fn set_system_datetime(_date: Zoned) -> UResult<()> { unimplemented!("setting date not implemented (unsupported target)"); From 88a7fa7adfa048dabdffc99451d7aba1d9e6a9b6 Mon Sep 17 00:00:00 2001 From: yuankunzhang Date: Fri, 12 Sep 2025 23:35:44 +0800 Subject: [PATCH 2/2] date: use --reference=file to display the file modification time --- src/uu/date/src/date.rs | 17 +++++++++++++++++ tests/by-util/test_date.rs | 16 ++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index 19e0a8712..6daf89f33 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -65,6 +65,7 @@ enum Format { enum DateSource { Now, File(PathBuf), + FileMtime(PathBuf), Stdin, Human(String), } @@ -146,6 +147,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { "-" => DateSource::Stdin, _ => DateSource::File(file.into()), } + } else if let Some(file) = matches.get_one::(OPT_REFERENCE) { + DateSource::FileMtime(file.into()) } else { DateSource::Now }; @@ -213,6 +216,20 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let iter = lines.map_while(Result::ok).map(parse_date); Box::new(iter) } + DateSource::FileMtime(ref path) => { + let metadata = std::fs::metadata(path) + .map_err_context(|| path.as_os_str().to_string_lossy().to_string())?; + let mtime = metadata.modified()?; + let ts = Timestamp::try_from(mtime).map_err(|e| { + USimpleError::new( + 1, + translate!("date-error-cannot-set-date", "path" => path.to_string_lossy(), "error" => e), + ) + })?; + let date = ts.to_zoned(TimeZone::try_system().unwrap_or(TimeZone::UTC)); + let iter = std::iter::once(Ok(date)); + Box::new(iter) + } DateSource::Now => { let iter = std::iter::once(Ok(now)); Box::new(iter) diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs index 28b98c84b..f9b4caeba 100644 --- a/tests/by-util/test_date.rs +++ b/tests/by-util/test_date.rs @@ -352,6 +352,22 @@ fn test_date_for_file() { ucmd.arg("--file").arg(file).succeeds(); } +#[test] +fn test_date_for_file_mtime() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "reference_file"; + at.touch(file); + std::thread::sleep(std::time::Duration::from_millis(100)); + let result = ucmd.arg("--reference").arg(file).arg("+%s%N").succeeds(); + let mtime = at.metadata(file).modified().unwrap(); + let mtime_nanos = mtime + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos() + .to_string(); + assert_eq!(result.stdout_str().trim(), &mtime_nanos[..]); +} + #[test] #[cfg(all(unix, not(target_os = "macos")))] /// TODO: expected to fail currently; change to `succeeds()` when required.