mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
Merge pull request #8630 from yuankunzhang/date-display-file-mtime
date: use --reference=file to display the file modification time
This commit is contained in:
+28
-43
@@ -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,10 @@ enum Format {
|
||||
/// Various places that dates can come from
|
||||
enum DateSource {
|
||||
Now,
|
||||
Custom(String),
|
||||
File(PathBuf),
|
||||
FileMtime(PathBuf),
|
||||
Stdin,
|
||||
Human(SignedDuration),
|
||||
Human(String),
|
||||
}
|
||||
|
||||
enum Iso8601Format {
|
||||
@@ -141,16 +141,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
};
|
||||
|
||||
let date_source = if let Some(date) = matches.get_one::<String>(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::<String>(OPT_FILE) {
|
||||
match file.as_ref() {
|
||||
"-" => DateSource::Stdin,
|
||||
_ => DateSource::File(file.into()),
|
||||
}
|
||||
} else if let Some(file) = matches.get_one::<String>(OPT_REFERENCE) {
|
||||
DateSource::FileMtime(file.into())
|
||||
} else {
|
||||
DateSource::Now
|
||||
};
|
||||
@@ -176,7 +174,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 +193,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<dyn Iterator<Item = _>> = 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);
|
||||
@@ -232,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)
|
||||
@@ -391,28 +389,15 @@ fn parse_date<S: AsRef<str> + 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<SignedDuration, ()> {
|
||||
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)");
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user