touch: add support for some relative times

For example,

    $ touch -d +1 days

Fixes #3964.
This commit is contained in:
Jeffrey Finkelstein 2022-11-27 23:08:56 -05:00 committed by Sylvestre Ledru
parent e307f624e8
commit a396ebd883
3 changed files with 148 additions and 1 deletions

View file

@ -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<FileTime> {
}
}
// 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::<i64>()
.ok()
.map(|n| time::Duration::weeks(2 * n)),
["fortnight" | "fortnights"] => Some(time::Duration::weeks(2)),
[num_str, "week" | "weeks"] => num_str.parse::<i64>().ok().map(time::Duration::weeks),
["week" | "weeks"] => Some(time::Duration::weeks(1)),
[num_str, "day" | "days"] => num_str.parse::<i64>().ok().map(time::Duration::days),
["day" | "days"] => Some(time::Duration::days(1)),
[num_str, "hour" | "hours"] => num_str.parse::<i64>().ok().map(time::Duration::hours),
["hour" | "hours"] => Some(time::Duration::hours(1)),
[num_str, "minute" | "minutes" | "min" | "mins"] => {
num_str.parse::<i64>().ok().map(time::Duration::minutes)
}
["minute" | "minutes" | "min" | "mins"] => Some(time::Duration::minutes(1)),
[num_str, "second" | "seconds" | "sec" | "secs"] => {
num_str.parse::<i64>().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)))
}

View file

@ -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!();

View file

@ -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