Skip to content

Commit e48c4a7

Browse files
authored
Merge pull request #8398 from drinkcat/stat-bigtime
`stat`: Convert to use `uucore` functions
2 parents 4a68e3d + a055e85 commit e48c4a7

File tree

7 files changed

+160
-70
lines changed

7 files changed

+160
-70
lines changed

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/uu/du/src/du.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use uucore::translate;
2828
use uucore::parser::parse_glob;
2929
use uucore::parser::parse_size::{ParseSizeError, parse_size_u64};
3030
use uucore::parser::shortcut_value_parser::ShortcutValueParser;
31+
use uucore::time::{FormatSystemTimeFallback, format_system_time};
3132
use uucore::{format_usage, show, show_error, show_warning};
3233
#[cfg(windows)]
3334
use windows_sys::Win32::Foundation::HANDLE;
@@ -508,7 +509,12 @@ impl StatPrinter {
508509

509510
if let Some(md_time) = &self.time {
510511
if let Some(time) = metadata_get_time(&stat.metadata, *md_time) {
511-
uucore::time::format_system_time(&mut stdout(), time, &self.time_format, true)?;
512+
format_system_time(
513+
&mut stdout(),
514+
time,
515+
&self.time_format,
516+
FormatSystemTimeFallback::IntegerError,
517+
)?;
512518
print!("\t");
513519
} else {
514520
print!("???\t");

src/uu/ls/src/ls.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ use uucore::line_ending::LineEnding;
5959
use uucore::translate;
6060

6161
use uucore::quoting_style::{QuotingStyle, locale_aware_escape_dir_name, locale_aware_escape_name};
62+
use uucore::time::{FormatSystemTimeFallback, format_system_time};
6263
use uucore::{
6364
display::Quotable,
6465
error::{UError, UResult, set_exit_code},
@@ -2946,7 +2947,7 @@ fn display_date(
29462947
_ => &config.time_format_recent,
29472948
};
29482949

2949-
uucore::time::format_system_time(out, time, fmt, false)
2950+
format_system_time(out, time, fmt, FormatSystemTimeFallback::Integer)
29502951
}
29512952

29522953
#[allow(dead_code)]

src/uu/stat/Cargo.toml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,13 @@ path = "src/stat.rs"
1919

2020
[dependencies]
2121
clap = { workspace = true }
22-
uucore = { workspace = true, features = ["entries", "libc", "fs", "fsext"] }
23-
chrono = { workspace = true }
22+
uucore = { workspace = true, features = [
23+
"entries",
24+
"libc",
25+
"fs",
26+
"fsext",
27+
"time",
28+
] }
2429
thiserror = { workspace = true }
2530
fluent = { workspace = true }
2631

src/uu/stat/src/stat.rs

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ use clap::builder::ValueParser;
1111
use uucore::display::Quotable;
1212
use uucore::fs::display_permissions;
1313
use uucore::fsext::{
14-
BirthTime, FsMeta, StatFs, pretty_filetype, pretty_fstype, read_fs_list, statfs,
14+
FsMeta, MetadataTimeField, StatFs, metadata_get_time, pretty_filetype, pretty_fstype,
15+
read_fs_list, statfs,
1516
};
1617
use uucore::libc::mode_t;
1718
use uucore::{entries, format_usage, show_error, show_warning};
1819

19-
use chrono::{DateTime, Local};
2020
use clap::{Arg, ArgAction, ArgMatches, Command};
2121
use std::borrow::Cow;
2222
use std::ffi::{OsStr, OsString};
@@ -27,6 +27,7 @@ use std::path::Path;
2727
use std::{env, fs};
2828

2929
use thiserror::Error;
30+
use uucore::time::{FormatSystemTimeFallback, format_system_time, system_time_to_sec};
3031

3132
#[derive(Debug, Error)]
3233
enum StatError {
@@ -1022,41 +1023,28 @@ impl Stater {
10221023
}
10231024

10241025
// time of file birth, human-readable; - if unknown
1025-
'w' => {
1026-
OutputType::Str(meta.birth().map_or(String::from("-"), |(sec, nsec)| {
1027-
pretty_time(sec as i64, nsec as i64)
1028-
}))
1029-
}
1026+
'w' => OutputType::Str(pretty_time(meta, MetadataTimeField::Birth)),
10301027

10311028
// time of file birth, seconds since Epoch; 0 if unknown
1032-
'W' => OutputType::Unsigned(meta.birth().unwrap_or_default().0),
1029+
'W' => OutputType::Integer(
1030+
metadata_get_time(meta, MetadataTimeField::Birth)
1031+
.map_or(0, |x| system_time_to_sec(x).0),
1032+
),
10331033

10341034
// time of last access, human-readable
1035-
'x' => OutputType::Str(pretty_time(meta.atime(), meta.atime_nsec())),
1035+
'x' => OutputType::Str(pretty_time(meta, MetadataTimeField::Access)),
10361036
// time of last access, seconds since Epoch
10371037
'X' => OutputType::Integer(meta.atime()),
10381038
// time of last data modification, human-readable
1039-
'y' => OutputType::Str(pretty_time(meta.mtime(), meta.mtime_nsec())),
1039+
'y' => OutputType::Str(pretty_time(meta, MetadataTimeField::Modification)),
10401040
// time of last data modification, seconds since Epoch
10411041
'Y' => {
1042-
let sec = meta.mtime();
1043-
let nsec = meta.mtime_nsec();
1044-
let tm = DateTime::from_timestamp(sec, nsec as u32).unwrap_or_default();
1045-
let tm: DateTime<Local> = tm.into();
1046-
match tm.timestamp_nanos_opt() {
1047-
None => {
1048-
let micros = tm.timestamp_micros();
1049-
let secs = micros as f64 / 1_000_000.0;
1050-
OutputType::Float(secs)
1051-
}
1052-
Some(ns) => {
1053-
let secs = ns as f64 / 1_000_000_000.0;
1054-
OutputType::Float(secs)
1055-
}
1056-
}
1042+
let (sec, nsec) = metadata_get_time(meta, MetadataTimeField::Modification)
1043+
.map_or((0, 0), system_time_to_sec);
1044+
OutputType::Float(sec as f64 + nsec as f64 / 1_000_000_000.0)
10571045
}
10581046
// time of last status change, human-readable
1059-
'z' => OutputType::Str(pretty_time(meta.ctime(), meta.ctime_nsec())),
1047+
'z' => OutputType::Str(pretty_time(meta, MetadataTimeField::Change)),
10601048
// time of last status change, seconds since Epoch
10611049
'Z' => OutputType::Integer(meta.ctime()),
10621050
'R' => {
@@ -1291,14 +1279,23 @@ pub fn uu_app() -> Command {
12911279
)
12921280
}
12931281

1294-
const PRETTY_DATETIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S.%f %z";
1282+
const PRETTY_DATETIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S.%N %z";
12951283

1296-
fn pretty_time(sec: i64, nsec: i64) -> String {
1297-
// Return the date in UTC
1298-
let tm = DateTime::from_timestamp(sec, nsec as u32).unwrap_or_default();
1299-
let tm: DateTime<Local> = tm.into();
1300-
1301-
tm.format(PRETTY_DATETIME_FORMAT).to_string()
1284+
fn pretty_time(meta: &Metadata, md_time_field: MetadataTimeField) -> String {
1285+
if let Some(time) = metadata_get_time(meta, md_time_field) {
1286+
let mut tmp = Vec::new();
1287+
if format_system_time(
1288+
&mut tmp,
1289+
time,
1290+
PRETTY_DATETIME_FORMAT,
1291+
FormatSystemTimeFallback::Float,
1292+
)
1293+
.is_ok()
1294+
{
1295+
return String::from_utf8(tmp).unwrap();
1296+
}
1297+
}
1298+
"-".to_string()
13021299
}
13031300

13041301
#[cfg(test)]

src/uucore/src/lib/features/fsext.rs

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ use std::io::Error as IOError;
6969
use std::mem;
7070
#[cfg(windows)]
7171
use std::path::Path;
72-
use std::time::{SystemTime, UNIX_EPOCH};
72+
use std::time::SystemTime;
73+
#[cfg(not(windows))]
74+
use std::time::UNIX_EPOCH;
7375
use std::{borrow::Cow, ffi::OsString};
7476

7577
use std::fs::Metadata;
@@ -114,19 +116,6 @@ pub use libc::statfs as statfs_fn;
114116
))]
115117
pub use libc::statvfs as statfs_fn;
116118

117-
pub trait BirthTime {
118-
fn birth(&self) -> Option<(u64, u32)>;
119-
}
120-
121-
impl BirthTime for Metadata {
122-
fn birth(&self) -> Option<(u64, u32)> {
123-
self.created()
124-
.ok()
125-
.and_then(|t| t.duration_since(UNIX_EPOCH).ok())
126-
.map(|e| (e.as_secs(), e.subsec_nanos()))
127-
}
128-
}
129-
130119
#[derive(Debug, Copy, Clone)]
131120
pub enum MetadataTimeField {
132121
Modification,
@@ -153,8 +142,20 @@ impl From<&str> for MetadataTimeField {
153142

154143
#[cfg(unix)]
155144
fn metadata_get_change_time(md: &Metadata) -> Option<SystemTime> {
156-
// TODO: This is incorrect for negative timestamps.
157-
Some(UNIX_EPOCH + Duration::new(md.ctime() as u64, md.ctime_nsec() as u32))
145+
let mut st = UNIX_EPOCH;
146+
let (secs, nsecs) = (md.ctime(), md.ctime_nsec());
147+
if secs >= 0 {
148+
st += Duration::from_secs(secs as u64);
149+
} else {
150+
st -= Duration::from_secs(-secs as u64);
151+
}
152+
if nsecs >= 0 {
153+
st += Duration::from_nanos(nsecs as u64);
154+
} else {
155+
// Probably never the case, but cover just in case.
156+
st -= Duration::from_nanos(-nsecs as u64);
157+
}
158+
Some(st)
158159
}
159160

160161
#[cfg(not(unix))]

src/uucore/src/lib/features/time.rs

Lines changed: 97 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,30 @@ fn format_zoned<W: Write>(out: &mut W, zoned: Zoned, fmt: &str) -> UResult<()> {
2525
.map_err(|x| USimpleError::new(1, x.to_string()))
2626
}
2727

28+
/// Convert a SystemTime` to a number of seconds since UNIX_EPOCH
29+
pub fn system_time_to_sec(time: SystemTime) -> (i64, u32) {
30+
if time > UNIX_EPOCH {
31+
let d = time.duration_since(UNIX_EPOCH).unwrap();
32+
(d.as_secs() as i64, d.subsec_nanos())
33+
} else {
34+
let d = UNIX_EPOCH.duration_since(time).unwrap();
35+
(-(d.as_secs() as i64), d.subsec_nanos())
36+
}
37+
}
38+
39+
/// Sets how `format_system_time` behaves if the time cannot be converted.
40+
pub enum FormatSystemTimeFallback {
41+
Integer, // Just print seconds since epoch (`ls`)
42+
IntegerError, // The above, and print an error (`du``)
43+
Float, // Just print seconds+nanoseconds since epoch (`stat`)
44+
}
45+
2846
/// Format a `SystemTime` according to given fmt, and append to vector out.
2947
pub fn format_system_time<W: Write>(
3048
out: &mut W,
3149
time: SystemTime,
3250
fmt: &str,
33-
show_error: bool,
51+
mode: FormatSystemTimeFallback,
3452
) -> UResult<()> {
3553
let zoned: Result<Zoned, _> = time.try_into();
3654
match zoned {
@@ -42,24 +60,30 @@ pub fn format_system_time<W: Write>(
4260
// but it still far enough in the future/past to be unlikely to matter:
4361
// jiff: Year between -9999 to 9999 (UTC) [-377705023201..=253402207200]
4462
// GNU: Year fits in signed 32 bits (timezone dependent)
45-
let ts: i64 = if time > UNIX_EPOCH {
46-
time.duration_since(UNIX_EPOCH).unwrap().as_secs() as i64
47-
} else {
48-
-(UNIX_EPOCH.duration_since(time).unwrap().as_secs() as i64)
63+
let (mut secs, mut nsecs) = system_time_to_sec(time);
64+
match mode {
65+
FormatSystemTimeFallback::Integer => out.write_all(secs.to_string().as_bytes())?,
66+
FormatSystemTimeFallback::IntegerError => {
67+
let str = secs.to_string();
68+
show_error!("time '{str}' is out of range");
69+
out.write_all(str.as_bytes())?;
70+
}
71+
FormatSystemTimeFallback::Float => {
72+
if secs < 0 && nsecs != 0 {
73+
secs -= 1;
74+
nsecs = 1_000_000_000 - nsecs;
75+
}
76+
out.write_fmt(format_args!("{secs}.{nsecs:09}"))?;
77+
}
4978
};
50-
let str = ts.to_string();
51-
if show_error {
52-
show_error!("time '{str}' is out of range");
53-
}
54-
out.write_all(str.as_bytes())?;
5579
Ok(())
5680
}
5781
}
5882
}
5983

6084
#[cfg(test)]
6185
mod tests {
62-
use crate::time::format_system_time;
86+
use crate::time::{FormatSystemTimeFallback, format_system_time};
6387
use std::time::{Duration, UNIX_EPOCH};
6488

6589
// Test epoch SystemTime get printed correctly at UTC0, with 2 simple formats.
@@ -69,12 +93,23 @@ mod tests {
6993

7094
let time = UNIX_EPOCH;
7195
let mut out = Vec::new();
72-
format_system_time(&mut out, time, "%Y-%m-%d %H:%M", false).expect("Formatting error.");
96+
format_system_time(
97+
&mut out,
98+
time,
99+
"%Y-%m-%d %H:%M",
100+
FormatSystemTimeFallback::Integer,
101+
)
102+
.expect("Formatting error.");
73103
assert_eq!(String::from_utf8(out).unwrap(), "1970-01-01 00:00");
74104

75105
let mut out = Vec::new();
76-
format_system_time(&mut out, time, "%Y-%m-%d %H:%M:%S.%N %z", false)
77-
.expect("Formatting error.");
106+
format_system_time(
107+
&mut out,
108+
time,
109+
"%Y-%m-%d %H:%M:%S.%N %z",
110+
FormatSystemTimeFallback::Integer,
111+
)
112+
.expect("Formatting error.");
78113
assert_eq!(
79114
String::from_utf8(out).unwrap(),
80115
"1970-01-01 00:00:00.000000000 +0000"
@@ -86,12 +121,58 @@ mod tests {
86121
fn test_large_system_time() {
87122
let time = UNIX_EPOCH + Duration::from_secs(67_768_036_191_763_200);
88123
let mut out = Vec::new();
89-
format_system_time(&mut out, time, "%Y-%m-%d %H:%M", false).expect("Formatting error.");
124+
format_system_time(
125+
&mut out,
126+
time,
127+
"%Y-%m-%d %H:%M",
128+
FormatSystemTimeFallback::Integer,
129+
)
130+
.expect("Formatting error.");
90131
assert_eq!(String::from_utf8(out).unwrap(), "67768036191763200");
91132

92133
let time = UNIX_EPOCH - Duration::from_secs(67_768_040_922_076_800);
93134
let mut out = Vec::new();
94-
format_system_time(&mut out, time, "%Y-%m-%d %H:%M", false).expect("Formatting error.");
135+
format_system_time(
136+
&mut out,
137+
time,
138+
"%Y-%m-%d %H:%M",
139+
FormatSystemTimeFallback::Integer,
140+
)
141+
.expect("Formatting error.");
95142
assert_eq!(String::from_utf8(out).unwrap(), "-67768040922076800");
96143
}
144+
145+
// Test that very large (positive or negative) lead to just the timestamp being printed.
146+
#[test]
147+
fn test_large_system_time_float() {
148+
let time =
149+
UNIX_EPOCH + Duration::from_secs(67_768_036_191_763_000) + Duration::from_nanos(123);
150+
let mut out = Vec::new();
151+
format_system_time(
152+
&mut out,
153+
time,
154+
"%Y-%m-%d %H:%M",
155+
FormatSystemTimeFallback::Float,
156+
)
157+
.expect("Formatting error.");
158+
assert_eq!(
159+
String::from_utf8(out).unwrap(),
160+
"67768036191763000.000000123"
161+
);
162+
163+
let time =
164+
UNIX_EPOCH - Duration::from_secs(67_768_040_922_076_000) + Duration::from_nanos(123);
165+
let mut out = Vec::new();
166+
format_system_time(
167+
&mut out,
168+
time,
169+
"%Y-%m-%d %H:%M",
170+
FormatSystemTimeFallback::Float,
171+
)
172+
.expect("Formatting error.");
173+
assert_eq!(
174+
String::from_utf8(out).unwrap(),
175+
"-67768040922076000.000000123"
176+
);
177+
}
97178
}

0 commit comments

Comments
 (0)