From a396ebd8834d00db0f7c6d36f58312a7041365e2 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sun, 27 Nov 2022 23:08:56 -0500 Subject: [PATCH] touch: add support for some relative times For example, $ touch -d +1 days Fixes #3964. --- src/uu/touch/src/touch.rs | 55 ++++++++++++++++++++++ tests/by-util/test_touch.rs | 92 +++++++++++++++++++++++++++++++++++++ util/build-gnu.sh | 2 +- 3 files changed, 148 insertions(+), 1 deletion(-) diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index aeb221a85..c898d8924 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -198,6 +198,7 @@ pub fn uu_app() -> Command { Arg::new(options::sources::DATE) .short('d') .long(options::sources::DATE) + .allow_hyphen_values(true) .help("parse argument and use it instead of current time") .value_name("STRING"), ) @@ -381,6 +382,60 @@ fn parse_date(s: &str) -> UResult { } } + // Relative day, like "today", "tomorrow", or "yesterday". + match s { + "now" | "today" => { + let now_local = time::OffsetDateTime::now_local().unwrap(); + return Ok(local_dt_to_filetime(now_local)); + } + "tomorrow" => { + let duration = time::Duration::days(1); + let now_local = time::OffsetDateTime::now_local().unwrap(); + let diff = now_local.checked_add(duration).unwrap(); + return Ok(local_dt_to_filetime(diff)); + } + "yesterday" => { + let duration = time::Duration::days(1); + let now_local = time::OffsetDateTime::now_local().unwrap(); + let diff = now_local.checked_sub(duration).unwrap(); + return Ok(local_dt_to_filetime(diff)); + } + _ => {} + } + + // Relative time, like "-1 hour" or "+3 days". + // + // TODO Add support for "year" and "month". + // TODO Add support for times without spaces like "-1hour". + let tokens: Vec<&str> = s.split_whitespace().collect(); + let maybe_duration = match &tokens[..] { + [num_str, "fortnight" | "fortnights"] => num_str + .parse::() + .ok() + .map(|n| time::Duration::weeks(2 * n)), + ["fortnight" | "fortnights"] => Some(time::Duration::weeks(2)), + [num_str, "week" | "weeks"] => num_str.parse::().ok().map(time::Duration::weeks), + ["week" | "weeks"] => Some(time::Duration::weeks(1)), + [num_str, "day" | "days"] => num_str.parse::().ok().map(time::Duration::days), + ["day" | "days"] => Some(time::Duration::days(1)), + [num_str, "hour" | "hours"] => num_str.parse::().ok().map(time::Duration::hours), + ["hour" | "hours"] => Some(time::Duration::hours(1)), + [num_str, "minute" | "minutes" | "min" | "mins"] => { + num_str.parse::().ok().map(time::Duration::minutes) + } + ["minute" | "minutes" | "min" | "mins"] => Some(time::Duration::minutes(1)), + [num_str, "second" | "seconds" | "sec" | "secs"] => { + num_str.parse::().ok().map(time::Duration::seconds) + } + ["second" | "seconds" | "sec" | "secs"] => Some(time::Duration::seconds(1)), + _ => None, + }; + if let Some(duration) = maybe_duration { + let now_local = time::OffsetDateTime::now_local().unwrap(); + let diff = now_local.checked_add(duration).unwrap(); + return Ok(local_dt_to_filetime(diff)); + } + Err(USimpleError::new(1, format!("Unable to parse date: {}", s))) } diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index 2b2cb5266..3585fcf97 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -512,6 +512,98 @@ fn test_touch_set_date7() { assert_eq!(mtime, expected); } +/// Test for setting the date by a relative time unit. +#[test] +fn test_touch_set_date_relative_smoke() { + // From the GNU documentation: + // + // > The unit of time displacement may be selected by the string + // > ‘year’ or ‘month’ for moving by whole years or months. These + // > are fuzzy units, as years and months are not all of equal + // > duration. More precise units are ‘fortnight’ which is worth 14 + // > days, ‘week’ worth 7 days, ‘day’ worth 24 hours, ‘hour’ worth + // > 60 minutes, ‘minute’ or ‘min’ worth 60 seconds, and ‘second’ or + // > ‘sec’ worth one second. An ‘s’ suffix on these units is + // > accepted and ignored. + // + let times = [ + // "-1 year", "+1 year", "-1 years", "+1 years", + // "-1 month", "+1 month", "-1 months", "+1 months", + "-1 fortnight", + "+1 fortnight", + "-1 fortnights", + "+1 fortnights", + "fortnight", + "fortnights", + "-1 week", + "+1 week", + "-1 weeks", + "+1 weeks", + "week", + "weeks", + "-1 day", + "+1 day", + "-1 days", + "+1 days", + "day", + "days", + "-1 hour", + "+1 hour", + "-1 hours", + "+1 hours", + "hour", + "hours", + "-1 minute", + "+1 minute", + "-1 minutes", + "+1 minutes", + "minute", + "minutes", + "-1 min", + "+1 min", + "-1 mins", + "+1 mins", + "min", + "mins", + "-1 second", + "+1 second", + "-1 seconds", + "+1 seconds", + "second", + "seconds", + "-1 sec", + "+1 sec", + "-1 secs", + "+1 secs", + "sec", + "secs", + ]; + for time in times { + let (at, mut ucmd) = at_and_ucmd!(); + at.touch("f"); + ucmd.args(&["-d", time, "f"]) + .succeeds() + .no_stderr() + .no_stdout(); + } + + // From the GNU documentation: + // + // > The string ‘tomorrow’ is worth one day in the future + // > (equivalent to ‘day’), the string ‘yesterday’ is worth one day + // > in the past (equivalent to ‘day ago’). + // + let times = ["yesterday", "tomorrow", "now"]; + for time in times { + let (at, mut ucmd) = at_and_ucmd!(); + at.touch("f"); + ucmd.args(&["-d", time, "f"]) + .succeeds() + .no_stderr() + .no_stdout(); + } +} + #[test] fn test_touch_set_date_wrong_format() { let (_at, mut ucmd) = at_and_ucmd!(); diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 4ef9b4744..dad970866 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -137,7 +137,7 @@ sed -i 's|sort |/usr/bin/sort |' tests/ls/hyperlink.sh tests/misc/test-N.sh sed -i 's|split |/usr/bin/split |' tests/misc/factor-parallel.sh sed -i 's|id -|/usr/bin/id -|' tests/misc/runcon-no-reorder.sh # tests/ls/abmon-align.sh - https://github.com/uutils/coreutils/issues/3505 -sed -i 's|touch |/usr/bin/touch |' tests/cp/preserve-link.sh tests/cp/reflink-perm.sh tests/ls/block-size.sh tests/mv/update.sh tests/misc/ls-time.sh tests/misc/stat-nanoseconds.sh tests/misc/time-style.sh tests/misc/test-N.sh tests/ls/abmon-align.sh +sed -i 's|touch |/usr/bin/touch |' tests/cp/reflink-perm.sh tests/ls/block-size.sh tests/mv/update.sh tests/misc/ls-time.sh tests/misc/stat-nanoseconds.sh tests/misc/time-style.sh tests/misc/test-N.sh tests/ls/abmon-align.sh sed -i 's|ln -|/usr/bin/ln -|' tests/cp/link-deref.sh sed -i 's|cp |/usr/bin/cp |' tests/mv/hard-2.sh sed -i 's|paste |/usr/bin/paste |' tests/misc/od-endian.sh