Skip to content

Commit 79f4b89

Browse files
authored
Merge pull request #7154 from jfinkels/ls-time-style-timezone-abbrev
ls: display %Z alphabetic time zone abbreviation
2 parents 64dad0c + ab6d95c commit 79f4b89

File tree

4 files changed

+91
-43
lines changed

4 files changed

+91
-43
lines changed

Cargo.lock

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

src/uu/ls/Cargo.toml

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,17 @@ path = "src/ls.rs"
1818

1919
[dependencies]
2020
ansi-width = { workspace = true }
21-
clap = { workspace = true, features = ["env"] }
2221
chrono = { workspace = true }
23-
number_prefix = { workspace = true }
24-
uutils_term_grid = { workspace = true }
25-
terminal_size = { workspace = true }
22+
chrono-tz = { workspace = true }
23+
clap = { workspace = true, features = ["env"] }
2624
glob = { workspace = true }
25+
hostname = { workspace = true }
26+
iana-time-zone = { workspace = true }
2727
lscolors = { workspace = true }
28+
number_prefix = { workspace = true }
29+
once_cell = { workspace = true }
30+
selinux = { workspace = true, optional = true }
31+
terminal_size = { workspace = true }
2832
uucore = { workspace = true, features = [
2933
"colors",
3034
"entries",
@@ -34,9 +38,7 @@ uucore = { workspace = true, features = [
3438
"quoting-style",
3539
"version-cmp",
3640
] }
37-
once_cell = { workspace = true }
38-
selinux = { workspace = true, optional = true }
39-
hostname = { workspace = true }
41+
uutils_term_grid = { workspace = true }
4042

4143
[[bin]]
4244
name = "ls"

src/uu/ls/src/ls.rs

Lines changed: 69 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,9 @@
55

66
// spell-checker:ignore (ToDO) somegroup nlink tabsize dired subdired dtype colorterm stringly
77

8-
use clap::{
9-
builder::{NonEmptyStringValueParser, PossibleValue, ValueParser},
10-
crate_version, Arg, ArgAction, Command,
11-
};
12-
use glob::{MatchOptions, Pattern};
13-
use lscolors::LsColors;
14-
15-
use ansi_width::ansi_width;
16-
use std::{cell::OnceCell, num::IntErrorKind};
17-
use std::{collections::HashSet, io::IsTerminal};
18-
198
#[cfg(windows)]
209
use std::os::windows::fs::MetadataExt;
10+
use std::{cell::OnceCell, num::IntErrorKind};
2111
use std::{
2212
cmp::Reverse,
2313
error::Error,
@@ -34,7 +24,20 @@ use std::{
3424
os::unix::fs::{FileTypeExt, MetadataExt},
3525
time::Duration,
3626
};
27+
use std::{collections::HashSet, io::IsTerminal};
28+
29+
use ansi_width::ansi_width;
30+
use chrono::{DateTime, Local, TimeDelta, TimeZone, Utc};
31+
use chrono_tz::{OffsetName, Tz};
32+
use clap::{
33+
builder::{NonEmptyStringValueParser, PossibleValue, ValueParser},
34+
crate_version, Arg, ArgAction, Command,
35+
};
36+
use glob::{MatchOptions, Pattern};
37+
use iana_time_zone::get_timezone;
38+
use lscolors::LsColors;
3739
use term_grid::{Direction, Filling, Grid, GridOptions};
40+
3841
use uucore::error::USimpleError;
3942
use uucore::format::human::{human_readable, SizeFormat};
4043
#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))]
@@ -67,10 +70,12 @@ use uucore::{
6770
version_cmp::version_cmp,
6871
};
6972
use uucore::{help_about, help_section, help_usage, parse_glob, show, show_error, show_warning};
73+
7074
mod dired;
7175
use dired::{is_dired_arg_present, DiredOutput};
7276
mod colors;
7377
use colors::{color_name, StyleManager};
78+
7479
#[cfg(not(feature = "selinux"))]
7580
static CONTEXT_HELP_TEXT: &str = "print any security context of each file (not enabled)";
7681
#[cfg(feature = "selinux")]
@@ -334,6 +339,58 @@ enum TimeStyle {
334339
Format(String),
335340
}
336341

342+
/// Whether the given date is considered recent (i.e., in the last 6 months).
343+
fn is_recent(time: DateTime<Local>) -> bool {
344+
// According to GNU a Gregorian year has 365.2425 * 24 * 60 * 60 == 31556952 seconds on the average.
345+
time + TimeDelta::try_seconds(31_556_952 / 2).unwrap() > Local::now()
346+
}
347+
348+
/// Get the alphabetic abbreviation of the current timezone.
349+
///
350+
/// For example, "UTC" or "CET" or "PDT".
351+
fn timezone_abbrev() -> String {
352+
let tz = match std::env::var("TZ") {
353+
// TODO Support other time zones...
354+
Ok(s) if s == "UTC0" || s.is_empty() => Tz::Etc__UTC,
355+
_ => match get_timezone() {
356+
Ok(tz_str) => tz_str.parse().unwrap(),
357+
Err(_) => Tz::Etc__UTC,
358+
},
359+
};
360+
let offset = tz.offset_from_utc_date(&Utc::now().date_naive());
361+
offset.abbreviation().unwrap_or("UTC").to_string()
362+
}
363+
364+
/// Format the given time according to a custom format string.
365+
fn custom_time_format(fmt: &str, time: DateTime<Local>) -> String {
366+
// TODO Refactor the common code from `ls` and `date` for rendering dates.
367+
// TODO - Revisit when chrono 0.5 is released. https://github.com/chronotope/chrono/issues/970
368+
// GNU `date` uses `%N` for nano seconds, however the `chrono` crate uses `%f`.
369+
let fmt = fmt.replace("%N", "%f").replace("%Z", &timezone_abbrev());
370+
time.format(&fmt).to_string()
371+
}
372+
373+
impl TimeStyle {
374+
/// Format the given time according to this time format style.
375+
fn format(&self, time: DateTime<Local>) -> String {
376+
let recent = is_recent(time);
377+
match (self, recent) {
378+
(Self::FullIso, _) => time.format("%Y-%m-%d %H:%M:%S.%f %z").to_string(),
379+
(Self::LongIso, _) => time.format("%Y-%m-%d %H:%M").to_string(),
380+
(Self::Iso, true) => time.format("%m-%d %H:%M").to_string(),
381+
(Self::Iso, false) => time.format("%Y-%m-%d ").to_string(),
382+
// spell-checker:ignore (word) datetime
383+
//In this version of chrono translating can be done
384+
//The function is chrono::datetime::DateTime::format_localized
385+
//However it's currently still hard to get the current pure-rust-locale
386+
//So it's not yet implemented
387+
(Self::Locale, true) => time.format("%b %e %H:%M").to_string(),
388+
(Self::Locale, false) => time.format("%b %e %Y").to_string(),
389+
(Self::Format(e), _) => custom_time_format(e, time),
390+
}
391+
}
392+
}
393+
337394
fn parse_time_style(options: &clap::ArgMatches) -> Result<TimeStyle, LsError> {
338395
let possible_time_styles = vec![
339396
"full-iso".to_string(),
@@ -3115,31 +3172,7 @@ fn get_time(md: &Metadata, config: &Config) -> Option<chrono::DateTime<chrono::L
31153172

31163173
fn display_date(metadata: &Metadata, config: &Config) -> String {
31173174
match get_time(metadata, config) {
3118-
Some(time) => {
3119-
//Date is recent if from past 6 months
3120-
//According to GNU a Gregorian year has 365.2425 * 24 * 60 * 60 == 31556952 seconds on the average.
3121-
let recent = time + chrono::TimeDelta::try_seconds(31_556_952 / 2).unwrap()
3122-
> chrono::Local::now();
3123-
3124-
match &config.time_style {
3125-
TimeStyle::FullIso => time.format("%Y-%m-%d %H:%M:%S.%f %z"),
3126-
TimeStyle::LongIso => time.format("%Y-%m-%d %H:%M"),
3127-
TimeStyle::Iso => time.format(if recent { "%m-%d %H:%M" } else { "%Y-%m-%d " }),
3128-
TimeStyle::Locale => {
3129-
let fmt = if recent { "%b %e %H:%M" } else { "%b %e %Y" };
3130-
3131-
// spell-checker:ignore (word) datetime
3132-
//In this version of chrono translating can be done
3133-
//The function is chrono::datetime::DateTime::format_localized
3134-
//However it's currently still hard to get the current pure-rust-locale
3135-
//So it's not yet implemented
3136-
3137-
time.format(fmt)
3138-
}
3139-
TimeStyle::Format(e) => time.format(e),
3140-
}
3141-
.to_string()
3142-
}
3175+
Some(time) => config.time_style.format(time),
31433176
None => "???".into(),
31443177
}
31453178
}

tests/by-util/test_ls.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5628,3 +5628,14 @@ fn test_non_unicode_names() {
56285628
.succeeds()
56295629
.stdout_is_bytes(b"\xC0.dir\n\xC0.file\n");
56305630
}
5631+
5632+
#[test]
5633+
fn test_time_style_timezone_name() {
5634+
let re_custom_format = Regex::new(r"[a-z-]* \d* [\w.]* [\w.]* \d* UTC f\n").unwrap();
5635+
let (at, mut ucmd) = at_and_ucmd!();
5636+
at.touch("f");
5637+
ucmd.env("TZ", "UTC0")
5638+
.args(&["-l", "--time-style=+%Z"])
5639+
.succeeds()
5640+
.stdout_matches(&re_custom_format);
5641+
}

0 commit comments

Comments
 (0)