mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
Merge pull request #8398 from drinkcat/stat-bigtime
`stat`: Convert to use `uucore` functions
This commit is contained in:
Generated
-1
@@ -3773,7 +3773,6 @@ dependencies = [
|
||||
name = "uu_stat"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"fluent",
|
||||
"thiserror 2.0.12",
|
||||
|
||||
+7
-1
@@ -28,6 +28,7 @@ use uucore::translate;
|
||||
use uucore::parser::parse_glob;
|
||||
use uucore::parser::parse_size::{ParseSizeError, parse_size_u64};
|
||||
use uucore::parser::shortcut_value_parser::ShortcutValueParser;
|
||||
use uucore::time::{FormatSystemTimeFallback, format_system_time};
|
||||
use uucore::{format_usage, show, show_error, show_warning};
|
||||
#[cfg(windows)]
|
||||
use windows_sys::Win32::Foundation::HANDLE;
|
||||
@@ -508,7 +509,12 @@ impl StatPrinter {
|
||||
|
||||
if let Some(md_time) = &self.time {
|
||||
if let Some(time) = metadata_get_time(&stat.metadata, *md_time) {
|
||||
uucore::time::format_system_time(&mut stdout(), time, &self.time_format, true)?;
|
||||
format_system_time(
|
||||
&mut stdout(),
|
||||
time,
|
||||
&self.time_format,
|
||||
FormatSystemTimeFallback::IntegerError,
|
||||
)?;
|
||||
print!("\t");
|
||||
} else {
|
||||
print!("???\t");
|
||||
|
||||
+2
-1
@@ -59,6 +59,7 @@ use uucore::line_ending::LineEnding;
|
||||
use uucore::translate;
|
||||
|
||||
use uucore::quoting_style::{QuotingStyle, locale_aware_escape_dir_name, locale_aware_escape_name};
|
||||
use uucore::time::{FormatSystemTimeFallback, format_system_time};
|
||||
use uucore::{
|
||||
display::Quotable,
|
||||
error::{UError, UResult, set_exit_code},
|
||||
@@ -2946,7 +2947,7 @@ fn display_date(
|
||||
_ => &config.time_format_recent,
|
||||
};
|
||||
|
||||
uucore::time::format_system_time(out, time, fmt, false)
|
||||
format_system_time(out, time, fmt, FormatSystemTimeFallback::Integer)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
||||
@@ -19,8 +19,13 @@ path = "src/stat.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = { workspace = true }
|
||||
uucore = { workspace = true, features = ["entries", "libc", "fs", "fsext"] }
|
||||
chrono = { workspace = true }
|
||||
uucore = { workspace = true, features = [
|
||||
"entries",
|
||||
"libc",
|
||||
"fs",
|
||||
"fsext",
|
||||
"time",
|
||||
] }
|
||||
thiserror = { workspace = true }
|
||||
fluent = { workspace = true }
|
||||
|
||||
|
||||
+30
-33
@@ -11,12 +11,12 @@ use clap::builder::ValueParser;
|
||||
use uucore::display::Quotable;
|
||||
use uucore::fs::display_permissions;
|
||||
use uucore::fsext::{
|
||||
BirthTime, FsMeta, StatFs, pretty_filetype, pretty_fstype, read_fs_list, statfs,
|
||||
FsMeta, MetadataTimeField, StatFs, metadata_get_time, pretty_filetype, pretty_fstype,
|
||||
read_fs_list, statfs,
|
||||
};
|
||||
use uucore::libc::mode_t;
|
||||
use uucore::{entries, format_usage, show_error, show_warning};
|
||||
|
||||
use chrono::{DateTime, Local};
|
||||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
@@ -27,6 +27,7 @@ use std::path::Path;
|
||||
use std::{env, fs};
|
||||
|
||||
use thiserror::Error;
|
||||
use uucore::time::{FormatSystemTimeFallback, format_system_time, system_time_to_sec};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
enum StatError {
|
||||
@@ -1022,41 +1023,28 @@ impl Stater {
|
||||
}
|
||||
|
||||
// time of file birth, human-readable; - if unknown
|
||||
'w' => {
|
||||
OutputType::Str(meta.birth().map_or(String::from("-"), |(sec, nsec)| {
|
||||
pretty_time(sec as i64, nsec as i64)
|
||||
}))
|
||||
}
|
||||
'w' => OutputType::Str(pretty_time(meta, MetadataTimeField::Birth)),
|
||||
|
||||
// time of file birth, seconds since Epoch; 0 if unknown
|
||||
'W' => OutputType::Unsigned(meta.birth().unwrap_or_default().0),
|
||||
'W' => OutputType::Integer(
|
||||
metadata_get_time(meta, MetadataTimeField::Birth)
|
||||
.map_or(0, |x| system_time_to_sec(x).0),
|
||||
),
|
||||
|
||||
// time of last access, human-readable
|
||||
'x' => OutputType::Str(pretty_time(meta.atime(), meta.atime_nsec())),
|
||||
'x' => OutputType::Str(pretty_time(meta, MetadataTimeField::Access)),
|
||||
// time of last access, seconds since Epoch
|
||||
'X' => OutputType::Integer(meta.atime()),
|
||||
// time of last data modification, human-readable
|
||||
'y' => OutputType::Str(pretty_time(meta.mtime(), meta.mtime_nsec())),
|
||||
'y' => OutputType::Str(pretty_time(meta, MetadataTimeField::Modification)),
|
||||
// time of last data modification, seconds since Epoch
|
||||
'Y' => {
|
||||
let sec = meta.mtime();
|
||||
let nsec = meta.mtime_nsec();
|
||||
let tm = DateTime::from_timestamp(sec, nsec as u32).unwrap_or_default();
|
||||
let tm: DateTime<Local> = tm.into();
|
||||
match tm.timestamp_nanos_opt() {
|
||||
None => {
|
||||
let micros = tm.timestamp_micros();
|
||||
let secs = micros as f64 / 1_000_000.0;
|
||||
OutputType::Float(secs)
|
||||
}
|
||||
Some(ns) => {
|
||||
let secs = ns as f64 / 1_000_000_000.0;
|
||||
OutputType::Float(secs)
|
||||
}
|
||||
}
|
||||
let (sec, nsec) = metadata_get_time(meta, MetadataTimeField::Modification)
|
||||
.map_or((0, 0), system_time_to_sec);
|
||||
OutputType::Float(sec as f64 + nsec as f64 / 1_000_000_000.0)
|
||||
}
|
||||
// time of last status change, human-readable
|
||||
'z' => OutputType::Str(pretty_time(meta.ctime(), meta.ctime_nsec())),
|
||||
'z' => OutputType::Str(pretty_time(meta, MetadataTimeField::Change)),
|
||||
// time of last status change, seconds since Epoch
|
||||
'Z' => OutputType::Integer(meta.ctime()),
|
||||
'R' => {
|
||||
@@ -1291,14 +1279,23 @@ pub fn uu_app() -> Command {
|
||||
)
|
||||
}
|
||||
|
||||
const PRETTY_DATETIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S.%f %z";
|
||||
const PRETTY_DATETIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S.%N %z";
|
||||
|
||||
fn pretty_time(sec: i64, nsec: i64) -> String {
|
||||
// Return the date in UTC
|
||||
let tm = DateTime::from_timestamp(sec, nsec as u32).unwrap_or_default();
|
||||
let tm: DateTime<Local> = tm.into();
|
||||
|
||||
tm.format(PRETTY_DATETIME_FORMAT).to_string()
|
||||
fn pretty_time(meta: &Metadata, md_time_field: MetadataTimeField) -> String {
|
||||
if let Some(time) = metadata_get_time(meta, md_time_field) {
|
||||
let mut tmp = Vec::new();
|
||||
if format_system_time(
|
||||
&mut tmp,
|
||||
time,
|
||||
PRETTY_DATETIME_FORMAT,
|
||||
FormatSystemTimeFallback::Float,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
return String::from_utf8(tmp).unwrap();
|
||||
}
|
||||
}
|
||||
"-".to_string()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -69,7 +69,9 @@ use std::io::Error as IOError;
|
||||
use std::mem;
|
||||
#[cfg(windows)]
|
||||
use std::path::Path;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use std::time::SystemTime;
|
||||
#[cfg(not(windows))]
|
||||
use std::time::UNIX_EPOCH;
|
||||
use std::{borrow::Cow, ffi::OsString};
|
||||
|
||||
use std::fs::Metadata;
|
||||
@@ -114,19 +116,6 @@ pub use libc::statfs as statfs_fn;
|
||||
))]
|
||||
pub use libc::statvfs as statfs_fn;
|
||||
|
||||
pub trait BirthTime {
|
||||
fn birth(&self) -> Option<(u64, u32)>;
|
||||
}
|
||||
|
||||
impl BirthTime for Metadata {
|
||||
fn birth(&self) -> Option<(u64, u32)> {
|
||||
self.created()
|
||||
.ok()
|
||||
.and_then(|t| t.duration_since(UNIX_EPOCH).ok())
|
||||
.map(|e| (e.as_secs(), e.subsec_nanos()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum MetadataTimeField {
|
||||
Modification,
|
||||
@@ -153,8 +142,20 @@ impl From<&str> for MetadataTimeField {
|
||||
|
||||
#[cfg(unix)]
|
||||
fn metadata_get_change_time(md: &Metadata) -> Option<SystemTime> {
|
||||
// TODO: This is incorrect for negative timestamps.
|
||||
Some(UNIX_EPOCH + Duration::new(md.ctime() as u64, md.ctime_nsec() as u32))
|
||||
let mut st = UNIX_EPOCH;
|
||||
let (secs, nsecs) = (md.ctime(), md.ctime_nsec());
|
||||
if secs >= 0 {
|
||||
st += Duration::from_secs(secs as u64);
|
||||
} else {
|
||||
st -= Duration::from_secs(-secs as u64);
|
||||
}
|
||||
if nsecs >= 0 {
|
||||
st += Duration::from_nanos(nsecs as u64);
|
||||
} else {
|
||||
// Probably never the case, but cover just in case.
|
||||
st -= Duration::from_nanos(-nsecs as u64);
|
||||
}
|
||||
Some(st)
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
|
||||
@@ -25,12 +25,30 @@ fn format_zoned<W: Write>(out: &mut W, zoned: Zoned, fmt: &str) -> UResult<()> {
|
||||
.map_err(|x| USimpleError::new(1, x.to_string()))
|
||||
}
|
||||
|
||||
/// Convert a SystemTime` to a number of seconds since UNIX_EPOCH
|
||||
pub fn system_time_to_sec(time: SystemTime) -> (i64, u32) {
|
||||
if time > UNIX_EPOCH {
|
||||
let d = time.duration_since(UNIX_EPOCH).unwrap();
|
||||
(d.as_secs() as i64, d.subsec_nanos())
|
||||
} else {
|
||||
let d = UNIX_EPOCH.duration_since(time).unwrap();
|
||||
(-(d.as_secs() as i64), d.subsec_nanos())
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets how `format_system_time` behaves if the time cannot be converted.
|
||||
pub enum FormatSystemTimeFallback {
|
||||
Integer, // Just print seconds since epoch (`ls`)
|
||||
IntegerError, // The above, and print an error (`du``)
|
||||
Float, // Just print seconds+nanoseconds since epoch (`stat`)
|
||||
}
|
||||
|
||||
/// Format a `SystemTime` according to given fmt, and append to vector out.
|
||||
pub fn format_system_time<W: Write>(
|
||||
out: &mut W,
|
||||
time: SystemTime,
|
||||
fmt: &str,
|
||||
show_error: bool,
|
||||
mode: FormatSystemTimeFallback,
|
||||
) -> UResult<()> {
|
||||
let zoned: Result<Zoned, _> = time.try_into();
|
||||
match zoned {
|
||||
@@ -42,16 +60,22 @@ pub fn format_system_time<W: Write>(
|
||||
// but it still far enough in the future/past to be unlikely to matter:
|
||||
// jiff: Year between -9999 to 9999 (UTC) [-377705023201..=253402207200]
|
||||
// GNU: Year fits in signed 32 bits (timezone dependent)
|
||||
let ts: i64 = if time > UNIX_EPOCH {
|
||||
time.duration_since(UNIX_EPOCH).unwrap().as_secs() as i64
|
||||
} else {
|
||||
-(UNIX_EPOCH.duration_since(time).unwrap().as_secs() as i64)
|
||||
let (mut secs, mut nsecs) = system_time_to_sec(time);
|
||||
match mode {
|
||||
FormatSystemTimeFallback::Integer => out.write_all(secs.to_string().as_bytes())?,
|
||||
FormatSystemTimeFallback::IntegerError => {
|
||||
let str = secs.to_string();
|
||||
show_error!("time '{str}' is out of range");
|
||||
out.write_all(str.as_bytes())?;
|
||||
}
|
||||
FormatSystemTimeFallback::Float => {
|
||||
if secs < 0 && nsecs != 0 {
|
||||
secs -= 1;
|
||||
nsecs = 1_000_000_000 - nsecs;
|
||||
}
|
||||
out.write_fmt(format_args!("{secs}.{nsecs:09}"))?;
|
||||
}
|
||||
};
|
||||
let str = ts.to_string();
|
||||
if show_error {
|
||||
show_error!("time '{str}' is out of range");
|
||||
}
|
||||
out.write_all(str.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -59,7 +83,7 @@ pub fn format_system_time<W: Write>(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::time::format_system_time;
|
||||
use crate::time::{FormatSystemTimeFallback, format_system_time};
|
||||
use std::time::{Duration, UNIX_EPOCH};
|
||||
|
||||
// Test epoch SystemTime get printed correctly at UTC0, with 2 simple formats.
|
||||
@@ -69,12 +93,23 @@ mod tests {
|
||||
|
||||
let time = UNIX_EPOCH;
|
||||
let mut out = Vec::new();
|
||||
format_system_time(&mut out, time, "%Y-%m-%d %H:%M", false).expect("Formatting error.");
|
||||
format_system_time(
|
||||
&mut out,
|
||||
time,
|
||||
"%Y-%m-%d %H:%M",
|
||||
FormatSystemTimeFallback::Integer,
|
||||
)
|
||||
.expect("Formatting error.");
|
||||
assert_eq!(String::from_utf8(out).unwrap(), "1970-01-01 00:00");
|
||||
|
||||
let mut out = Vec::new();
|
||||
format_system_time(&mut out, time, "%Y-%m-%d %H:%M:%S.%N %z", false)
|
||||
.expect("Formatting error.");
|
||||
format_system_time(
|
||||
&mut out,
|
||||
time,
|
||||
"%Y-%m-%d %H:%M:%S.%N %z",
|
||||
FormatSystemTimeFallback::Integer,
|
||||
)
|
||||
.expect("Formatting error.");
|
||||
assert_eq!(
|
||||
String::from_utf8(out).unwrap(),
|
||||
"1970-01-01 00:00:00.000000000 +0000"
|
||||
@@ -86,12 +121,58 @@ mod tests {
|
||||
fn test_large_system_time() {
|
||||
let time = UNIX_EPOCH + Duration::from_secs(67_768_036_191_763_200);
|
||||
let mut out = Vec::new();
|
||||
format_system_time(&mut out, time, "%Y-%m-%d %H:%M", false).expect("Formatting error.");
|
||||
format_system_time(
|
||||
&mut out,
|
||||
time,
|
||||
"%Y-%m-%d %H:%M",
|
||||
FormatSystemTimeFallback::Integer,
|
||||
)
|
||||
.expect("Formatting error.");
|
||||
assert_eq!(String::from_utf8(out).unwrap(), "67768036191763200");
|
||||
|
||||
let time = UNIX_EPOCH - Duration::from_secs(67_768_040_922_076_800);
|
||||
let mut out = Vec::new();
|
||||
format_system_time(&mut out, time, "%Y-%m-%d %H:%M", false).expect("Formatting error.");
|
||||
format_system_time(
|
||||
&mut out,
|
||||
time,
|
||||
"%Y-%m-%d %H:%M",
|
||||
FormatSystemTimeFallback::Integer,
|
||||
)
|
||||
.expect("Formatting error.");
|
||||
assert_eq!(String::from_utf8(out).unwrap(), "-67768040922076800");
|
||||
}
|
||||
|
||||
// Test that very large (positive or negative) lead to just the timestamp being printed.
|
||||
#[test]
|
||||
fn test_large_system_time_float() {
|
||||
let time =
|
||||
UNIX_EPOCH + Duration::from_secs(67_768_036_191_763_000) + Duration::from_nanos(123);
|
||||
let mut out = Vec::new();
|
||||
format_system_time(
|
||||
&mut out,
|
||||
time,
|
||||
"%Y-%m-%d %H:%M",
|
||||
FormatSystemTimeFallback::Float,
|
||||
)
|
||||
.expect("Formatting error.");
|
||||
assert_eq!(
|
||||
String::from_utf8(out).unwrap(),
|
||||
"67768036191763000.000000123"
|
||||
);
|
||||
|
||||
let time =
|
||||
UNIX_EPOCH - Duration::from_secs(67_768_040_922_076_000) + Duration::from_nanos(123);
|
||||
let mut out = Vec::new();
|
||||
format_system_time(
|
||||
&mut out,
|
||||
time,
|
||||
"%Y-%m-%d %H:%M",
|
||||
FormatSystemTimeFallback::Float,
|
||||
)
|
||||
.expect("Formatting error.");
|
||||
assert_eq!(
|
||||
String::from_utf8(out).unwrap(),
|
||||
"-67768040922076000.000000123"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user