Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions src/uu/du/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@ workspace = true
path = "src/du.rs"

[dependencies]
chrono = { workspace = true }
# For the --exclude & --exclude-from options
glob = { workspace = true }
clap = { workspace = true }
uucore = { workspace = true, features = ["format", "parser"] }
uucore = { workspace = true, features = ["format", "parser", "time"] }
thiserror = { workspace = true }

[target.'cfg(target_os = "windows")'.dependencies]
Expand Down
13 changes: 6 additions & 7 deletions src/uu/du/src/du.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

use chrono::{DateTime, Local};
use clap::{Arg, ArgAction, ArgMatches, Command, builder::PossibleValue};
use glob::Pattern;
use std::collections::{HashMap, HashSet};
use std::env;
#[cfg(not(windows))]
use std::fs::Metadata;
use std::fs::{self, DirEntry, File};
use std::io::{BufRead, BufReader};
use std::io::{BufRead, BufReader, stdout};
#[cfg(not(windows))]
use std::os::unix::fs::MetadataExt;
#[cfg(windows)]
Expand Down Expand Up @@ -576,13 +575,13 @@ impl StatPrinter {
}

fn print_stat(&self, stat: &Stat, size: u64) -> UResult<()> {
print!("{}\t", self.convert_size(size));

if let Some(time) = self.time {
let secs = get_time_secs(time, stat)?;
let tm = DateTime::<Local>::from(UNIX_EPOCH + Duration::from_secs(secs));
let time_str = tm.format(&self.time_format).to_string();
print!("{}\t{time_str}\t", self.convert_size(size));
} else {
print!("{}\t", self.convert_size(size));
let time = UNIX_EPOCH + Duration::from_secs(secs);
uucore::time::format_system_time(&mut stdout(), time, &self.time_format, true)?;
print!("\t");
}

print_verbatim(&stat.path).unwrap();
Expand Down
6 changes: 1 addition & 5 deletions src/uu/ls/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,6 @@ ansi-width = { workspace = true }
clap = { workspace = true, features = ["env"] }
glob = { workspace = true }
hostname = { workspace = true }
jiff = { workspace = true, features = [
"tzdb-bundle-platform",
"tzdb-zoneinfo",
"tzdb-concatenated",
] }
lscolors = { workspace = true }
selinux = { workspace = true, optional = true }
terminal_size = { workspace = true }
Expand All @@ -41,6 +36,7 @@ uucore = { workspace = true, features = [
"fsxattr",
"parser",
"quoting-style",
"time",
"version-cmp",
] }
uutils_term_grid = { workspace = true }
Expand Down
135 changes: 51 additions & 84 deletions src/uu/ls/src/ls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ use clap::{
builder::{NonEmptyStringValueParser, PossibleValue, ValueParser},
};
use glob::{MatchOptions, Pattern};
use jiff::fmt::StdIoWrite;
use jiff::fmt::strtime::BrokenDownTime;
use jiff::{Timestamp, Zoned};
use lscolors::LsColors;
use term_grid::{DEFAULT_SEPARATOR_SIZE, Direction, Filling, Grid, GridOptions, SPACES_IN_TAB};
use thiserror::Error;
Expand Down Expand Up @@ -258,83 +255,54 @@ enum Time {
Birth,
}

#[derive(Debug)]
enum TimeStyle {
FullIso,
LongIso,
Iso,
Locale,
Format(String),
}

/// Whether the given date is considered recent (i.e., in the last 6 months).
fn is_recent(time: Timestamp, state: &mut ListState) -> bool {
time > state.recent_time_threshold
}

impl TimeStyle {
/// Format the given time according to this time format style.
fn format(
&self,
date: Zoned,
out: &mut Vec<u8>,
state: &mut ListState,
) -> Result<(), jiff::Error> {
let recent = is_recent(date.timestamp(), state);
let tm = BrokenDownTime::from(&date);
let mut out = StdIoWrite(out);
let config = jiff::fmt::strtime::Config::new().lenient(true);

let fmt = match (self, recent) {
(Self::FullIso, _) => "%Y-%m-%d %H:%M:%S.%f %z",
(Self::LongIso, _) => "%Y-%m-%d %H:%M",
(Self::Iso, true) => "%m-%d %H:%M",
(Self::Iso, false) => "%Y-%m-%d ",
// TODO: Using correct locale string is not implemented.
(Self::Locale, true) => "%b %e %H:%M",
(Self::Locale, false) => "%b %e %Y",
(Self::Format(fmt), _) => fmt,
};
fn parse_time_style(options: &clap::ArgMatches) -> Result<(String, Option<String>), LsError> {
const TIME_STYLES: [(&str, (&str, Option<&str>)); 4] = [
("full-iso", ("%Y-%m-%d %H:%M:%S.%f %z", None)),
("long-iso", ("%Y-%m-%d %H:%M", None)),
("iso", ("%m-%d %H:%M", Some("%Y-%m-%d "))),
// TODO: Using correct locale string is not implemented.
("locale", ("%b %e %H:%M", Some("%b %e %Y"))),
];
// A map from a time-style parameter to a length-2 tuple of formats:
// the first one is used for recent dates, the second one for older ones (optional).
let time_styles = HashMap::from(TIME_STYLES);
let possible_time_styles = TIME_STYLES
.iter()
.map(|(x, _)| *x)
.chain(iter::once(
"+FORMAT (e.g., +%H:%M) for a 'date'-style format",
))
.map(|s| s.to_string());

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

fn parse_time_style(options: &clap::ArgMatches) -> Result<TimeStyle, LsError> {
let possible_time_styles = vec![
"full-iso".to_string(),
"long-iso".to_string(),
"iso".to_string(),
"locale".to_string(),
"+FORMAT (e.g., +%H:%M) for a 'date'-style format".to_string(),
];
if let Some(field) = options.get_one::<String>(options::TIME_STYLE) {
//If both FULL_TIME and TIME_STYLE are present
//The one added last is dominant
if options.get_flag(options::FULL_TIME)
&& options.indices_of(options::FULL_TIME).unwrap().next_back()
> options.indices_of(options::TIME_STYLE).unwrap().next_back()
{
Ok(TimeStyle::FullIso)
ok(time_styles["full-iso"])
} else {
match field.as_str() {
"full-iso" => Ok(TimeStyle::FullIso),
"long-iso" => Ok(TimeStyle::LongIso),
"iso" => Ok(TimeStyle::Iso),
"locale" => Ok(TimeStyle::Locale),
_ => match field.chars().next().unwrap() {
'+' => Ok(TimeStyle::Format(String::from(&field[1..]))),
match time_styles.get(field.as_str()) {
Some(formats) => ok(*formats),
None => match field.chars().next().unwrap() {
'+' => Ok((field[1..].to_string(), None)),
_ => Err(LsError::TimeStyleParseError(
String::from(field),
possible_time_styles,
possible_time_styles.collect(),
)),
},
}
}
} else if options.get_flag(options::FULL_TIME) {
Ok(TimeStyle::FullIso)
ok(time_styles["full-iso"])
} else {
Ok(TimeStyle::Locale)
ok(time_styles["locale"])
}
}

Expand Down Expand Up @@ -377,7 +345,8 @@ pub struct Config {
// Dir and vdir needs access to this field
pub quoting_style: QuotingStyle,
indicator_style: IndicatorStyle,
time_style: TimeStyle,
time_format_recent: String, // Time format for recent dates
time_format_older: Option<String>, // Time format for older dates (optional, if not present, time_format_recent is used)
context: bool,
selinux_supported: bool,
group_directories_first: bool,
Expand Down Expand Up @@ -925,10 +894,10 @@ impl Config {
let indicator_style = extract_indicator_style(options);
// Only parse the value to "--time-style" if it will become relevant.
let dired = options.get_flag(options::DIRED);
let time_style = if format == Format::Long || dired {
let (time_format_recent, time_format_older) = if format == Format::Long || dired {
parse_time_style(options)?
} else {
TimeStyle::Iso
Default::default()
};

let mut ignore_patterns: Vec<Pattern> = Vec::new();
Expand Down Expand Up @@ -1114,7 +1083,8 @@ impl Config {
width,
quoting_style,
indicator_style,
time_style,
time_format_recent,
time_format_older,
context,
selinux_supported: {
#[cfg(feature = "selinux")]
Expand Down Expand Up @@ -2002,7 +1972,7 @@ struct ListState<'a> {
uid_cache: HashMap<u32, String>,
#[cfg(unix)]
gid_cache: HashMap<u32, String>,
recent_time_threshold: Timestamp,
recent_time_threshold: SystemTime,
}

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

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

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

fn get_time(md: &Metadata, config: &Config) -> Option<Zoned> {
let time = get_system_time(md, config)?;
time.try_into().ok()
}

fn display_date(
metadata: &Metadata,
config: &Config,
state: &mut ListState,
out: &mut Vec<u8>,
) -> UResult<()> {
match get_time(metadata, config) {
// TODO: Some fancier error conversion might be nice.
Some(time) => config
.time_style
.format(time, out, state)
.map_err(|x| USimpleError::new(1, x.to_string())),
None => {
out.extend(b"???");
Ok(())
}
}
let Some(time) = get_system_time(metadata, config) else {
out.extend(b"???");
return Ok(());
};

// Use "recent" format if the given date is considered recent (i.e., in the last 6 months),
// or if no "older" format is available.
let fmt = match &config.time_format_older {
Some(time_format_older) if time <= state.recent_time_threshold => time_format_older,
_ => &config.time_format_recent,
};

uucore::time::format_system_time(out, time, fmt, false)
}

#[allow(dead_code)]
Expand Down
8 changes: 7 additions & 1 deletion src/uucore/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# spell-checker:ignore (features) bigdecimal zerocopy extendedbigdecimal
# spell-checker:ignore (features) bigdecimal zerocopy extendedbigdecimal tzdb zoneinfo

[package]
name = "uucore"
Expand Down Expand Up @@ -29,6 +29,11 @@ dunce = { version = "1.0.4", optional = true }
wild = "2.2.1"
glob = { workspace = true, optional = true }
itertools = { workspace = true, optional = true }
jiff = { workspace = true, optional = true, features = [
"tzdb-bundle-platform",
"tzdb-zoneinfo",
"tzdb-concatenated",
] }
time = { workspace = true, optional = true, features = [
"formatting",
"local-offset",
Expand Down Expand Up @@ -150,4 +155,5 @@ utmpx = ["time", "time/macros", "libc", "dns-lookup"]
version-cmp = []
wide = []
tty = []
time = ["jiff"]
uptime = ["chrono", "libc", "windows-sys", "utmpx", "utmp-classic"]
2 changes: 2 additions & 0 deletions src/uucore/src/lib/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ pub mod ranges;
pub mod ringbuffer;
#[cfg(feature = "sum")]
pub mod sum;
#[cfg(feature = "time")]
pub mod time;
#[cfg(feature = "update-control")]
pub mod update_control;
#[cfg(feature = "uptime")]
Expand Down
Loading
Loading