Merge pull request #8398 from drinkcat/stat-bigtime

`stat`: Convert to use `uucore` functions
This commit is contained in:
Sylvestre Ledru
2025-07-30 10:18:23 +02:00
committed by GitHub
7 changed files with 160 additions and 70 deletions
Generated
-1
View File
@@ -3773,7 +3773,6 @@ dependencies = [
name = "uu_stat"
version = "0.1.0"
dependencies = [
"chrono",
"clap",
"fluent",
"thiserror 2.0.12",
+7 -1
View File
@@ -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
View File
@@ -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)]
+7 -2
View File
@@ -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
View File
@@ -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)]
+17 -16
View File
@@ -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))]
+97 -16
View File
@@ -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"
);
}
}