diff --git a/Cargo.lock b/Cargo.lock index 90af50e20b9..a74acc33a54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2538,7 +2538,9 @@ version = "0.0.18" dependencies = [ "chrono", "clap", + "humantime_to_duration", "libc", + "time", "uucore", "windows-sys 0.45.0", ] diff --git a/src/uu/date/Cargo.toml b/src/uu/date/Cargo.toml index fab98f20d95..923ca4aa746 100644 --- a/src/uu/date/Cargo.toml +++ b/src/uu/date/Cargo.toml @@ -1,3 +1,4 @@ +# spell-checker:ignore humantime [package] name = "uu_date" version = "0.0.18" @@ -16,8 +17,11 @@ path = "src/date.rs" [dependencies] chrono = { workspace=true } +#/ TODO: check if we can avoid chrono+time +time = { workspace=true } clap = { workspace=true } uucore = { workspace=true } +humantime_to_duration = { workspace=true } [target.'cfg(unix)'.dependencies] libc = { workspace=true } diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index ab874cb32ea..8b43386203e 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -6,10 +6,10 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (chrono) Datelike Timelike ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes +// spell-checker:ignore (chrono) Datelike Timelike ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes humantime use chrono::format::{Item, StrftimeItems}; -use chrono::{DateTime, FixedOffset, Local, Offset, Utc}; +use chrono::{DateTime, Duration as ChronoDuration, FixedOffset, Local, Offset, Utc}; #[cfg(windows)] use chrono::{Datelike, Timelike}; use clap::{crate_version, Arg, ArgAction, Command}; @@ -18,6 +18,7 @@ use libc::{clock_settime, timespec, CLOCK_REALTIME}; use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::PathBuf; +use time::Duration; use uucore::display::Quotable; #[cfg(not(any(target_os = "redox")))] use uucore::error::FromIo; @@ -96,6 +97,7 @@ enum DateSource { Now, Custom(String), File(PathBuf), + Human(Duration), } enum Iso8601Format { @@ -168,7 +170,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; let date_source = if let Some(date) = matches.get_one::(OPT_DATE) { - DateSource::Custom(date.into()) + if let Ok(duration) = humantime_to_duration::from_str(date.as_str()) { + DateSource::Human(duration) + } else { + DateSource::Custom(date.into()) + } } else if let Some(file) = matches.get_one::(OPT_FILE) { DateSource::File(file.into()) } else { @@ -219,6 +225,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let iter = std::iter::once(date); Box::new(iter) } + DateSource::Human(ref input) => { + // Get the current DateTime and convert the input time::Duration to chrono::Duration + // for things like "1 year ago" + let current_time = DateTime::::from(Local::now()); + let input_chrono = ChronoDuration::seconds(input.as_seconds_f32() as i64) + + ChronoDuration::nanoseconds(input.subsec_nanoseconds() as i64); + let iter = std::iter::once(Ok(current_time + input_chrono)); + Box::new(iter) + } DateSource::File(ref path) => { if path.is_dir() { return Err(USimpleError::new( diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs index a1bdda65181..edad87bd6c0 100644 --- a/tests/by-util/test_date.rs +++ b/tests/by-util/test_date.rs @@ -334,6 +334,29 @@ fn test_invalid_format_string() { assert!(result.stderr_str().starts_with("date: invalid format ")); } +#[test] +fn test_date_string_human() { + let date_formats = vec![ + "1 year ago", + "1 year", + "2 months ago", + "15 days ago", + "1 week ago", + "5 hours ago", + "30 minutes ago", + "10 seconds", + ]; + let re = Regex::new(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}\n$").unwrap(); + for date_format in date_formats { + new_ucmd!() + .arg("-d") + .arg(date_format) + .arg("+%Y-%m-%d %S:%M") + .succeeds() + .stdout_matches(&re); + } +} + #[test] fn test_invalid_date_string() { new_ucmd!()