mirror of
https://github.com/uutils/coreutils
synced 2024-12-13 14:52:41 +00:00
touch: add support for --ref and --date together
This commit resolves issue #4608. To make it relatively straightforward to implement I have taken some code from parse_date and put it inside a function parse_relative_time. This commit changes and adds some test as well to work with the new functionality.
This commit is contained in:
parent
da15928d71
commit
19e456fe2a
2 changed files with 101 additions and 47 deletions
|
@ -77,19 +77,57 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
),
|
||||
)
|
||||
})?;
|
||||
let (mut atime, mut mtime) =
|
||||
if let Some(reference) = matches.get_one::<OsString>(options::sources::REFERENCE) {
|
||||
stat(Path::new(reference), !matches.get_flag(options::NO_DEREF))?
|
||||
} else {
|
||||
let timestamp = if let Some(date) = matches.get_one::<String>(options::sources::DATE) {
|
||||
parse_date(date)?
|
||||
} else if let Some(current) = matches.get_one::<String>(options::sources::CURRENT) {
|
||||
parse_timestamp(current)?
|
||||
let (mut atime, mut mtime) = match (
|
||||
matches.get_one::<OsString>(options::sources::REFERENCE),
|
||||
matches.get_one::<String>(options::sources::DATE),
|
||||
) {
|
||||
(Some(reference), Some(date)) => {
|
||||
let (atime, mtime) = stat(Path::new(reference), !matches.get_flag(options::NO_DEREF))?;
|
||||
if let Some(offset) = parse_relative_time(date) {
|
||||
let mut seconds = offset.whole_seconds();
|
||||
let mut nanos = offset.subsec_nanoseconds();
|
||||
if nanos < 0 {
|
||||
nanos = 1_000_000_000 + nanos;
|
||||
seconds -= 1;
|
||||
}
|
||||
|
||||
let ref_atime_secs = atime.unix_seconds();
|
||||
let ref_atime_nanos = atime.nanoseconds();
|
||||
let atime = FileTime::from_unix_time(
|
||||
ref_atime_secs + seconds,
|
||||
ref_atime_nanos + nanos as u32,
|
||||
);
|
||||
|
||||
let ref_mtime_secs = mtime.unix_seconds();
|
||||
let ref_mtime_nanos = mtime.nanoseconds();
|
||||
let mtime = FileTime::from_unix_time(
|
||||
ref_mtime_secs + seconds,
|
||||
ref_mtime_nanos + nanos as u32,
|
||||
);
|
||||
|
||||
(atime, mtime)
|
||||
} else {
|
||||
local_dt_to_filetime(time::OffsetDateTime::now_local().unwrap())
|
||||
};
|
||||
let timestamp = parse_date(date)?;
|
||||
(timestamp, timestamp)
|
||||
}
|
||||
}
|
||||
(Some(reference), None) => {
|
||||
stat(Path::new(reference), !matches.get_flag(options::NO_DEREF))?
|
||||
}
|
||||
(None, Some(date)) => {
|
||||
let timestamp = parse_date(date)?;
|
||||
(timestamp, timestamp)
|
||||
};
|
||||
}
|
||||
(None, None) => {
|
||||
let timestamp =
|
||||
if let Some(current) = matches.get_one::<String>(options::sources::CURRENT) {
|
||||
parse_timestamp(current)?
|
||||
} else {
|
||||
local_dt_to_filetime(time::OffsetDateTime::now_local().unwrap())
|
||||
};
|
||||
(timestamp, timestamp)
|
||||
}
|
||||
};
|
||||
|
||||
for filename in files {
|
||||
// FIXME: find a way to avoid having to clone the path
|
||||
|
@ -202,7 +240,8 @@ pub fn uu_app() -> Command {
|
|||
.long(options::sources::DATE)
|
||||
.allow_hyphen_values(true)
|
||||
.help("parse argument and use it instead of current time")
|
||||
.value_name("STRING"),
|
||||
.value_name("STRING")
|
||||
.conflicts_with(options::sources::CURRENT),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::MODIFICATION)
|
||||
|
@ -234,7 +273,8 @@ pub fn uu_app() -> Command {
|
|||
.help("use this file's times instead of the current time")
|
||||
.value_name("FILE")
|
||||
.value_parser(ValueParser::os_string())
|
||||
.value_hint(clap::ValueHint::AnyPath),
|
||||
.value_hint(clap::ValueHint::AnyPath)
|
||||
.conflicts_with(options::sources::CURRENT),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::TIME)
|
||||
|
@ -254,11 +294,15 @@ pub fn uu_app() -> Command {
|
|||
.value_parser(ValueParser::os_string())
|
||||
.value_hint(clap::ValueHint::AnyPath),
|
||||
)
|
||||
.group(ArgGroup::new(options::SOURCES).args([
|
||||
options::sources::CURRENT,
|
||||
options::sources::DATE,
|
||||
options::sources::REFERENCE,
|
||||
]))
|
||||
.group(
|
||||
ArgGroup::new(options::SOURCES)
|
||||
.args([
|
||||
options::sources::CURRENT,
|
||||
options::sources::DATE,
|
||||
options::sources::REFERENCE,
|
||||
])
|
||||
.multiple(true),
|
||||
)
|
||||
}
|
||||
|
||||
fn stat(path: &Path, follow: bool) -> UResult<(FileTime, FileTime)> {
|
||||
|
@ -384,33 +428,22 @@ 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));
|
||||
}
|
||||
_ => {}
|
||||
if let Some(duration) = parse_relative_time(s) {
|
||||
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}")))
|
||||
}
|
||||
|
||||
fn parse_relative_time(s: &str) -> Option<Duration> {
|
||||
// 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[..] {
|
||||
match &tokens[..] {
|
||||
[num_str, "fortnight" | "fortnights"] => num_str
|
||||
.parse::<i64>()
|
||||
.ok()
|
||||
|
@ -430,15 +463,11 @@ fn parse_date(s: &str) -> UResult<FileTime> {
|
|||
num_str.parse::<i64>().ok().map(time::Duration::seconds)
|
||||
}
|
||||
["second" | "seconds" | "sec" | "secs"] => Some(time::Duration::seconds(1)),
|
||||
["now" | "today"] => Some(time::Duration::ZERO),
|
||||
["yesterday"] => Some(time::Duration::days(-1)),
|
||||
["tomorrow"] => Some(time::Duration::days(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}")))
|
||||
}
|
||||
|
||||
fn parse_timestamp(s: &str) -> UResult<FileTime> {
|
||||
|
|
|
@ -251,14 +251,39 @@ fn test_touch_set_both_date_and_reference() {
|
|||
let ref_file = "test_touch_reference";
|
||||
let file = "test_touch_set_both_date_and_reference";
|
||||
|
||||
let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000");
|
||||
let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501011234");
|
||||
|
||||
at.touch(ref_file);
|
||||
set_file_times(&at, ref_file, start_of_year, start_of_year);
|
||||
assert!(at.file_exists(ref_file));
|
||||
|
||||
ucmd.args(&["-d", "Thu Jan 01 12:34:00 2015", "-r", ref_file, file])
|
||||
.fails();
|
||||
.succeeds()
|
||||
.no_stderr();
|
||||
let (atime, mtime) = get_file_times(&at, file);
|
||||
assert_eq!(atime, start_of_year);
|
||||
assert_eq!(mtime, start_of_year);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_touch_set_both_offset_date_and_reference() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let ref_file = "test_touch_reference";
|
||||
let file = "test_touch_set_both_date_and_reference";
|
||||
|
||||
let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501011234");
|
||||
let five_days_later = str_to_filetime("%Y%m%d%H%M", "201501061234");
|
||||
|
||||
at.touch(ref_file);
|
||||
set_file_times(&at, ref_file, start_of_year, start_of_year);
|
||||
assert!(at.file_exists(ref_file));
|
||||
|
||||
ucmd.args(&["-d", "+5 days", "-r", ref_file, file])
|
||||
.succeeds()
|
||||
.no_stderr();
|
||||
let (atime, mtime) = get_file_times(&at, file);
|
||||
assert_eq!(atime, five_days_later);
|
||||
assert_eq!(mtime, five_days_later);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
Loading…
Reference in a new issue