Skip to content

Commit ded761d

Browse files
authored
Merge pull request #8390 from drinkcat/ls-bigtime
du/ls: Merge date formatting code, and handle timestamps far in the future/past by just printing them
2 parents 8bbce0d + d1c0780 commit ded761d

File tree

9 files changed

+168
-101
lines changed

9 files changed

+168
-101
lines changed

Cargo.lock

Lines changed: 1 addition & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/uu/du/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,10 @@ workspace = true
1818
path = "src/du.rs"
1919

2020
[dependencies]
21-
chrono = { workspace = true }
2221
# For the --exclude & --exclude-from options
2322
glob = { workspace = true }
2423
clap = { workspace = true }
25-
uucore = { workspace = true, features = ["format", "parser"] }
24+
uucore = { workspace = true, features = ["format", "parser", "time"] }
2625
thiserror = { workspace = true }
2726

2827
[target.'cfg(target_os = "windows")'.dependencies]

src/uu/du/src/du.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
55

6-
use chrono::{DateTime, Local};
76
use clap::{Arg, ArgAction, ArgMatches, Command, builder::PossibleValue};
87
use glob::Pattern;
98
use std::collections::{HashMap, HashSet};
109
use std::env;
1110
#[cfg(not(windows))]
1211
use std::fs::Metadata;
1312
use std::fs::{self, DirEntry, File};
14-
use std::io::{BufRead, BufReader};
13+
use std::io::{BufRead, BufReader, stdout};
1514
#[cfg(not(windows))]
1615
use std::os::unix::fs::MetadataExt;
1716
#[cfg(windows)]
@@ -576,13 +575,13 @@ impl StatPrinter {
576575
}
577576

578577
fn print_stat(&self, stat: &Stat, size: u64) -> UResult<()> {
578+
print!("{}\t", self.convert_size(size));
579+
579580
if let Some(time) = self.time {
580581
let secs = get_time_secs(time, stat)?;
581-
let tm = DateTime::<Local>::from(UNIX_EPOCH + Duration::from_secs(secs));
582-
let time_str = tm.format(&self.time_format).to_string();
583-
print!("{}\t{time_str}\t", self.convert_size(size));
584-
} else {
585-
print!("{}\t", self.convert_size(size));
582+
let time = UNIX_EPOCH + Duration::from_secs(secs);
583+
uucore::time::format_system_time(&mut stdout(), time, &self.time_format, true)?;
584+
print!("\t");
586585
}
587586

588587
print_verbatim(&stat.path).unwrap();

src/uu/ls/Cargo.toml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,6 @@ ansi-width = { workspace = true }
2424
clap = { workspace = true, features = ["env"] }
2525
glob = { workspace = true }
2626
hostname = { workspace = true }
27-
jiff = { workspace = true, features = [
28-
"tzdb-bundle-platform",
29-
"tzdb-zoneinfo",
30-
"tzdb-concatenated",
31-
] }
3227
lscolors = { workspace = true }
3328
selinux = { workspace = true, optional = true }
3429
terminal_size = { workspace = true }
@@ -41,6 +36,7 @@ uucore = { workspace = true, features = [
4136
"fsxattr",
4237
"parser",
4338
"quoting-style",
39+
"time",
4440
"version-cmp",
4541
] }
4642
uutils_term_grid = { workspace = true }

src/uu/ls/src/ls.rs

Lines changed: 51 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,6 @@ use clap::{
3131
builder::{NonEmptyStringValueParser, PossibleValue, ValueParser},
3232
};
3333
use glob::{MatchOptions, Pattern};
34-
use jiff::fmt::StdIoWrite;
35-
use jiff::fmt::strtime::BrokenDownTime;
36-
use jiff::{Timestamp, Zoned};
3734
use lscolors::LsColors;
3835
use term_grid::{DEFAULT_SEPARATOR_SIZE, Direction, Filling, Grid, GridOptions, SPACES_IN_TAB};
3936
use thiserror::Error;
@@ -258,83 +255,54 @@ enum Time {
258255
Birth,
259256
}
260257

261-
#[derive(Debug)]
262-
enum TimeStyle {
263-
FullIso,
264-
LongIso,
265-
Iso,
266-
Locale,
267-
Format(String),
268-
}
269-
270-
/// Whether the given date is considered recent (i.e., in the last 6 months).
271-
fn is_recent(time: Timestamp, state: &mut ListState) -> bool {
272-
time > state.recent_time_threshold
273-
}
274-
275-
impl TimeStyle {
276-
/// Format the given time according to this time format style.
277-
fn format(
278-
&self,
279-
date: Zoned,
280-
out: &mut Vec<u8>,
281-
state: &mut ListState,
282-
) -> Result<(), jiff::Error> {
283-
let recent = is_recent(date.timestamp(), state);
284-
let tm = BrokenDownTime::from(&date);
285-
let mut out = StdIoWrite(out);
286-
let config = jiff::fmt::strtime::Config::new().lenient(true);
287-
288-
let fmt = match (self, recent) {
289-
(Self::FullIso, _) => "%Y-%m-%d %H:%M:%S.%f %z",
290-
(Self::LongIso, _) => "%Y-%m-%d %H:%M",
291-
(Self::Iso, true) => "%m-%d %H:%M",
292-
(Self::Iso, false) => "%Y-%m-%d ",
293-
// TODO: Using correct locale string is not implemented.
294-
(Self::Locale, true) => "%b %e %H:%M",
295-
(Self::Locale, false) => "%b %e %Y",
296-
(Self::Format(fmt), _) => fmt,
297-
};
258+
fn parse_time_style(options: &clap::ArgMatches) -> Result<(String, Option<String>), LsError> {
259+
const TIME_STYLES: [(&str, (&str, Option<&str>)); 4] = [
260+
("full-iso", ("%Y-%m-%d %H:%M:%S.%f %z", None)),
261+
("long-iso", ("%Y-%m-%d %H:%M", None)),
262+
("iso", ("%m-%d %H:%M", Some("%Y-%m-%d "))),
263+
// TODO: Using correct locale string is not implemented.
264+
("locale", ("%b %e %H:%M", Some("%b %e %Y"))),
265+
];
266+
// A map from a time-style parameter to a length-2 tuple of formats:
267+
// the first one is used for recent dates, the second one for older ones (optional).
268+
let time_styles = HashMap::from(TIME_STYLES);
269+
let possible_time_styles = TIME_STYLES
270+
.iter()
271+
.map(|(x, _)| *x)
272+
.chain(iter::once(
273+
"+FORMAT (e.g., +%H:%M) for a 'date'-style format",
274+
))
275+
.map(|s| s.to_string());
298276

299-
tm.format_with_config(&config, fmt, &mut out)
277+
// Convert time_styles references to owned String/option.
278+
fn ok((recent, older): (&str, Option<&str>)) -> Result<(String, Option<String>), LsError> {
279+
Ok((recent.to_string(), older.map(String::from)))
300280
}
301-
}
302281

303-
fn parse_time_style(options: &clap::ArgMatches) -> Result<TimeStyle, LsError> {
304-
let possible_time_styles = vec![
305-
"full-iso".to_string(),
306-
"long-iso".to_string(),
307-
"iso".to_string(),
308-
"locale".to_string(),
309-
"+FORMAT (e.g., +%H:%M) for a 'date'-style format".to_string(),
310-
];
311282
if let Some(field) = options.get_one::<String>(options::TIME_STYLE) {
312283
//If both FULL_TIME and TIME_STYLE are present
313284
//The one added last is dominant
314285
if options.get_flag(options::FULL_TIME)
315286
&& options.indices_of(options::FULL_TIME).unwrap().next_back()
316287
> options.indices_of(options::TIME_STYLE).unwrap().next_back()
317288
{
318-
Ok(TimeStyle::FullIso)
289+
ok(time_styles["full-iso"])
319290
} else {
320-
match field.as_str() {
321-
"full-iso" => Ok(TimeStyle::FullIso),
322-
"long-iso" => Ok(TimeStyle::LongIso),
323-
"iso" => Ok(TimeStyle::Iso),
324-
"locale" => Ok(TimeStyle::Locale),
325-
_ => match field.chars().next().unwrap() {
326-
'+' => Ok(TimeStyle::Format(String::from(&field[1..]))),
291+
match time_styles.get(field.as_str()) {
292+
Some(formats) => ok(*formats),
293+
None => match field.chars().next().unwrap() {
294+
'+' => Ok((field[1..].to_string(), None)),
327295
_ => Err(LsError::TimeStyleParseError(
328296
String::from(field),
329-
possible_time_styles,
297+
possible_time_styles.collect(),
330298
)),
331299
},
332300
}
333301
}
334302
} else if options.get_flag(options::FULL_TIME) {
335-
Ok(TimeStyle::FullIso)
303+
ok(time_styles["full-iso"])
336304
} else {
337-
Ok(TimeStyle::Locale)
305+
ok(time_styles["locale"])
338306
}
339307
}
340308

@@ -377,7 +345,8 @@ pub struct Config {
377345
// Dir and vdir needs access to this field
378346
pub quoting_style: QuotingStyle,
379347
indicator_style: IndicatorStyle,
380-
time_style: TimeStyle,
348+
time_format_recent: String, // Time format for recent dates
349+
time_format_older: Option<String>, // Time format for older dates (optional, if not present, time_format_recent is used)
381350
context: bool,
382351
selinux_supported: bool,
383352
group_directories_first: bool,
@@ -925,10 +894,10 @@ impl Config {
925894
let indicator_style = extract_indicator_style(options);
926895
// Only parse the value to "--time-style" if it will become relevant.
927896
let dired = options.get_flag(options::DIRED);
928-
let time_style = if format == Format::Long || dired {
897+
let (time_format_recent, time_format_older) = if format == Format::Long || dired {
929898
parse_time_style(options)?
930899
} else {
931-
TimeStyle::Iso
900+
Default::default()
932901
};
933902

934903
let mut ignore_patterns: Vec<Pattern> = Vec::new();
@@ -1114,7 +1083,8 @@ impl Config {
11141083
width,
11151084
quoting_style,
11161085
indicator_style,
1117-
time_style,
1086+
time_format_recent,
1087+
time_format_older,
11181088
context,
11191089
selinux_supported: {
11201090
#[cfg(feature = "selinux")]
@@ -2002,7 +1972,7 @@ struct ListState<'a> {
20021972
uid_cache: HashMap<u32, String>,
20031973
#[cfg(unix)]
20041974
gid_cache: HashMap<u32, String>,
2005-
recent_time_threshold: Timestamp,
1975+
recent_time_threshold: SystemTime,
20061976
}
20071977

20081978
#[allow(clippy::cognitive_complexity)]
@@ -2020,7 +1990,7 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> {
20201990
#[cfg(unix)]
20211991
gid_cache: HashMap::new(),
20221992
// According to GNU a Gregorian year has 365.2425 * 24 * 60 * 60 == 31556952 seconds on the average.
2023-
recent_time_threshold: Timestamp::now() - Duration::new(31_556_952 / 2, 0),
1993+
recent_time_threshold: SystemTime::now() - Duration::new(31_556_952 / 2, 0),
20241994
};
20251995

20261996
for loc in locs {
@@ -2993,7 +2963,7 @@ fn display_group(_metadata: &Metadata, _config: &Config, _state: &mut ListState)
29932963
"somegroup"
29942964
}
29952965

2996-
// The implementations for get_time are separated because some options, such
2966+
// The implementations for get_system_time are separated because some options, such
29972967
// as ctime will not be available
29982968
#[cfg(unix)]
29992969
fn get_system_time(md: &Metadata, config: &Config) -> Option<SystemTime> {
@@ -3015,28 +2985,25 @@ fn get_system_time(md: &Metadata, config: &Config) -> Option<SystemTime> {
30152985
}
30162986
}
30172987

3018-
fn get_time(md: &Metadata, config: &Config) -> Option<Zoned> {
3019-
let time = get_system_time(md, config)?;
3020-
time.try_into().ok()
3021-
}
3022-
30232988
fn display_date(
30242989
metadata: &Metadata,
30252990
config: &Config,
30262991
state: &mut ListState,
30272992
out: &mut Vec<u8>,
30282993
) -> UResult<()> {
3029-
match get_time(metadata, config) {
3030-
// TODO: Some fancier error conversion might be nice.
3031-
Some(time) => config
3032-
.time_style
3033-
.format(time, out, state)
3034-
.map_err(|x| USimpleError::new(1, x.to_string())),
3035-
None => {
3036-
out.extend(b"???");
3037-
Ok(())
3038-
}
3039-
}
2994+
let Some(time) = get_system_time(metadata, config) else {
2995+
out.extend(b"???");
2996+
return Ok(());
2997+
};
2998+
2999+
// Use "recent" format if the given date is considered recent (i.e., in the last 6 months),
3000+
// or if no "older" format is available.
3001+
let fmt = match &config.time_format_older {
3002+
Some(time_format_older) if time <= state.recent_time_threshold => time_format_older,
3003+
_ => &config.time_format_recent,
3004+
};
3005+
3006+
uucore::time::format_system_time(out, time, fmt, false)
30403007
}
30413008

30423009
#[allow(dead_code)]

src/uucore/Cargo.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# spell-checker:ignore (features) bigdecimal zerocopy extendedbigdecimal
1+
# spell-checker:ignore (features) bigdecimal zerocopy extendedbigdecimal tzdb zoneinfo
22

33
[package]
44
name = "uucore"
@@ -29,6 +29,11 @@ dunce = { version = "1.0.4", optional = true }
2929
wild = "2.2.1"
3030
glob = { workspace = true, optional = true }
3131
itertools = { workspace = true, optional = true }
32+
jiff = { workspace = true, optional = true, features = [
33+
"tzdb-bundle-platform",
34+
"tzdb-zoneinfo",
35+
"tzdb-concatenated",
36+
] }
3237
time = { workspace = true, optional = true, features = [
3338
"formatting",
3439
"local-offset",
@@ -150,4 +155,5 @@ utmpx = ["time", "time/macros", "libc", "dns-lookup"]
150155
version-cmp = []
151156
wide = []
152157
tty = []
158+
time = ["jiff"]
153159
uptime = ["chrono", "libc", "windows-sys", "utmpx", "utmp-classic"]

src/uucore/src/lib/features.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ pub mod ranges;
4040
pub mod ringbuffer;
4141
#[cfg(feature = "sum")]
4242
pub mod sum;
43+
#[cfg(feature = "time")]
44+
pub mod time;
4345
#[cfg(feature = "update-control")]
4446
pub mod update_control;
4547
#[cfg(feature = "uptime")]

0 commit comments

Comments
 (0)