1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-29 03:57:44 +00:00

ls, date: refactor common code for formatting a datetime (#7194)

* refactoring ls and date

* Refactored and integrated ls and date and added tests to the new feature

* Style refactoring
This commit is contained in:
Tommaso Fellegara 2025-01-22 17:39:00 +01:00 committed by GitHub
parent e9aa8a58fd
commit 93e3d08d5f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 80 additions and 57 deletions

7
Cargo.lock generated
View file

@ -2617,9 +2617,7 @@ name = "uu_date"
version = "0.0.29" version = "0.0.29"
dependencies = [ dependencies = [
"chrono", "chrono",
"chrono-tz",
"clap", "clap",
"iana-time-zone",
"libc", "libc",
"parse_datetime", "parse_datetime",
"uucore", "uucore",
@ -2879,11 +2877,9 @@ version = "0.0.29"
dependencies = [ dependencies = [
"ansi-width", "ansi-width",
"chrono", "chrono",
"chrono-tz",
"clap", "clap",
"glob", "glob",
"hostname", "hostname",
"iana-time-zone",
"lscolors", "lscolors",
"number_prefix", "number_prefix",
"once_cell", "once_cell",
@ -3472,6 +3468,8 @@ version = "0.0.29"
dependencies = [ dependencies = [
"blake2b_simd", "blake2b_simd",
"blake3", "blake3",
"chrono",
"chrono-tz",
"clap", "clap",
"crc32fast", "crc32fast",
"data-encoding", "data-encoding",
@ -3481,6 +3479,7 @@ dependencies = [
"dunce", "dunce",
"glob", "glob",
"hex", "hex",
"iana-time-zone",
"itertools 0.14.0", "itertools 0.14.0",
"lazy_static", "lazy_static",
"libc", "libc",

View file

@ -20,10 +20,8 @@ path = "src/date.rs"
[dependencies] [dependencies]
chrono = { workspace = true } chrono = { workspace = true }
clap = { workspace = true } clap = { workspace = true }
uucore = { workspace = true } uucore = { workspace = true, features = ["custom-tz-fmt"] }
parse_datetime = { workspace = true } parse_datetime = { workspace = true }
chrono-tz = { workspace = true }
iana-time-zone = { workspace = true }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
libc = { workspace = true } libc = { workspace = true }

View file

@ -6,17 +6,16 @@
// spell-checker:ignore (chrono) Datelike Timelike ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes // spell-checker:ignore (chrono) Datelike Timelike ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes
use chrono::format::{Item, StrftimeItems}; use chrono::format::{Item, StrftimeItems};
use chrono::{DateTime, FixedOffset, Local, Offset, TimeDelta, TimeZone, Utc}; use chrono::{DateTime, FixedOffset, Local, Offset, TimeDelta, Utc};
#[cfg(windows)] #[cfg(windows)]
use chrono::{Datelike, Timelike}; use chrono::{Datelike, Timelike};
use chrono_tz::{OffsetName, Tz};
use clap::{crate_version, Arg, ArgAction, Command}; use clap::{crate_version, Arg, ArgAction, Command};
use iana_time_zone::get_timezone;
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))] #[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))]
use libc::{clock_settime, timespec, CLOCK_REALTIME}; use libc::{clock_settime, timespec, CLOCK_REALTIME};
use std::fs::File; use std::fs::File;
use std::io::{BufRead, BufReader}; use std::io::{BufRead, BufReader};
use std::path::PathBuf; use std::path::PathBuf;
use uucore::custom_tz_fmt::custom_time_format;
use uucore::display::Quotable; use uucore::display::Quotable;
use uucore::error::FromIo; use uucore::error::FromIo;
use uucore::error::{UResult, USimpleError}; use uucore::error::{UResult, USimpleError};
@ -274,21 +273,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
for date in dates { for date in dates {
match date { match date {
Ok(date) => { Ok(date) => {
// TODO - Revisit when chrono 0.5 is released. https://github.com/chronotope/chrono/issues/970 let format_string = custom_time_format(format_string);
let tz = match std::env::var("TZ") {
// TODO Support other time zones...
Ok(s) if s == "UTC0" || s.is_empty() => Tz::Etc__UTC,
_ => match get_timezone() {
Ok(tz_str) => tz_str.parse().unwrap(),
Err(_) => Tz::Etc__UTC,
},
};
let offset = tz.offset_from_utc_date(&Utc::now().date_naive());
let tz_abbreviation = offset.abbreviation();
// GNU `date` uses `%N` for nano seconds, however crate::chrono uses `%f`
let format_string = &format_string
.replace("%N", "%f")
.replace("%Z", tz_abbreviation.unwrap_or("UTC"));
// Refuse to pass this string to chrono as it is crashing in this crate // Refuse to pass this string to chrono as it is crashing in this crate
if format_string.contains("%#z") { if format_string.contains("%#z") {
return Err(USimpleError::new( return Err(USimpleError::new(
@ -298,7 +283,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
} }
// Hack to work around panic in chrono, // Hack to work around panic in chrono,
// TODO - remove when a fix for https://github.com/chronotope/chrono/issues/623 is released // TODO - remove when a fix for https://github.com/chronotope/chrono/issues/623 is released
let format_items = StrftimeItems::new(format_string); let format_items = StrftimeItems::new(format_string.as_str());
if format_items.clone().any(|i| i == Item::Error) { if format_items.clone().any(|i| i == Item::Error) {
return Err(USimpleError::new( return Err(USimpleError::new(
1, 1,

View file

@ -19,11 +19,9 @@ path = "src/ls.rs"
[dependencies] [dependencies]
ansi-width = { workspace = true } ansi-width = { workspace = true }
chrono = { workspace = true } chrono = { workspace = true }
chrono-tz = { workspace = true }
clap = { workspace = true, features = ["env"] } clap = { workspace = true, features = ["env"] }
glob = { workspace = true } glob = { workspace = true }
hostname = { workspace = true } hostname = { workspace = true }
iana-time-zone = { workspace = true }
lscolors = { workspace = true } lscolors = { workspace = true }
number_prefix = { workspace = true } number_prefix = { workspace = true }
once_cell = { workspace = true } once_cell = { workspace = true }
@ -31,6 +29,7 @@ selinux = { workspace = true, optional = true }
terminal_size = { workspace = true } terminal_size = { workspace = true }
uucore = { workspace = true, features = [ uucore = { workspace = true, features = [
"colors", "colors",
"custom-tz-fmt",
"entries", "entries",
"format", "format",
"fs", "fs",

View file

@ -27,14 +27,12 @@ use std::{
use std::{collections::HashSet, io::IsTerminal}; use std::{collections::HashSet, io::IsTerminal};
use ansi_width::ansi_width; use ansi_width::ansi_width;
use chrono::{DateTime, Local, TimeDelta, TimeZone, Utc}; use chrono::{DateTime, Local, TimeDelta};
use chrono_tz::{OffsetName, Tz};
use clap::{ use clap::{
builder::{NonEmptyStringValueParser, PossibleValue, ValueParser}, builder::{NonEmptyStringValueParser, PossibleValue, ValueParser},
crate_version, Arg, ArgAction, Command, crate_version, Arg, ArgAction, Command,
}; };
use glob::{MatchOptions, Pattern}; use glob::{MatchOptions, Pattern};
use iana_time_zone::get_timezone;
use lscolors::LsColors; use lscolors::LsColors;
use term_grid::{Direction, Filling, Grid, GridOptions}; use term_grid::{Direction, Filling, Grid, GridOptions};
@ -60,6 +58,7 @@ use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR};
use uucore::line_ending::LineEnding; use uucore::line_ending::LineEnding;
use uucore::quoting_style::{self, escape_name, QuotingStyle}; use uucore::quoting_style::{self, escape_name, QuotingStyle};
use uucore::{ use uucore::{
custom_tz_fmt,
display::Quotable, display::Quotable,
error::{set_exit_code, UError, UResult}, error::{set_exit_code, UError, UResult},
format_usage, format_usage,
@ -345,31 +344,6 @@ fn is_recent(time: DateTime<Local>) -> bool {
time + TimeDelta::try_seconds(31_556_952 / 2).unwrap() > Local::now() time + TimeDelta::try_seconds(31_556_952 / 2).unwrap() > Local::now()
} }
/// Get the alphabetic abbreviation of the current timezone.
///
/// For example, "UTC" or "CET" or "PDT".
fn timezone_abbrev() -> String {
let tz = match std::env::var("TZ") {
// TODO Support other time zones...
Ok(s) if s == "UTC0" || s.is_empty() => Tz::Etc__UTC,
_ => match get_timezone() {
Ok(tz_str) => tz_str.parse().unwrap(),
Err(_) => Tz::Etc__UTC,
},
};
let offset = tz.offset_from_utc_date(&Utc::now().date_naive());
offset.abbreviation().unwrap_or("UTC").to_string()
}
/// Format the given time according to a custom format string.
fn custom_time_format(fmt: &str, time: DateTime<Local>) -> String {
// TODO Refactor the common code from `ls` and `date` for rendering dates.
// TODO - Revisit when chrono 0.5 is released. https://github.com/chronotope/chrono/issues/970
// GNU `date` uses `%N` for nano seconds, however the `chrono` crate uses `%f`.
let fmt = fmt.replace("%N", "%f").replace("%Z", &timezone_abbrev());
time.format(&fmt).to_string()
}
impl TimeStyle { impl TimeStyle {
/// Format the given time according to this time format style. /// Format the given time according to this time format style.
fn format(&self, time: DateTime<Local>) -> String { fn format(&self, time: DateTime<Local>) -> String {
@ -386,7 +360,9 @@ impl TimeStyle {
//So it's not yet implemented //So it's not yet implemented
(Self::Locale, true) => time.format("%b %e %H:%M").to_string(), (Self::Locale, true) => time.format("%b %e %H:%M").to_string(),
(Self::Locale, false) => time.format("%b %e %Y").to_string(), (Self::Locale, false) => time.format("%b %e %Y").to_string(),
(Self::Format(e), _) => custom_time_format(e, time), (Self::Format(fmt), _) => time
.format(custom_tz_fmt::custom_time_format(fmt).as_str())
.to_string(),
} }
} }
} }

View file

@ -18,6 +18,8 @@ edition = "2021"
path = "src/lib/lib.rs" path = "src/lib/lib.rs"
[dependencies] [dependencies]
chrono = { workspace = true }
chrono-tz = { workspace = true }
clap = { workspace = true } clap = { workspace = true }
uucore_procs = { workspace = true } uucore_procs = { workspace = true }
number_prefix = { workspace = true } number_prefix = { workspace = true }
@ -25,6 +27,7 @@ dns-lookup = { workspace = true, optional = true }
dunce = { version = "1.0.4", optional = true } dunce = { version = "1.0.4", optional = true }
wild = "2.2.1" wild = "2.2.1"
glob = { workspace = true } glob = { workspace = true }
iana-time-zone = { workspace = true }
lazy_static = "1.4.0" lazy_static = "1.4.0"
# * optional # * optional
itertools = { workspace = true, optional = true } itertools = { workspace = true, optional = true }
@ -114,4 +117,5 @@ utf8 = []
utmpx = ["time", "time/macros", "libc", "dns-lookup"] utmpx = ["time", "time/macros", "libc", "dns-lookup"]
version-cmp = [] version-cmp = []
wide = [] wide = []
custom-tz-fmt = []
tty = [] tty = []

View file

@ -12,6 +12,8 @@ pub mod buf_copy;
pub mod checksum; pub mod checksum;
#[cfg(feature = "colors")] #[cfg(feature = "colors")]
pub mod colors; pub mod colors;
#[cfg(feature = "custom-tz-fmt")]
pub mod custom_tz_fmt;
#[cfg(feature = "encoding")] #[cfg(feature = "encoding")]
pub mod encoding; pub mod encoding;
#[cfg(feature = "format")] #[cfg(feature = "format")]

View file

@ -0,0 +1,58 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use chrono::{TimeZone, Utc};
use chrono_tz::{OffsetName, Tz};
use iana_time_zone::get_timezone;
/// Get the alphabetic abbreviation of the current timezone.
///
/// For example, "UTC" or "CET" or "PDT"
fn timezone_abbreviation() -> String {
let tz = match std::env::var("TZ") {
// TODO Support other time zones...
Ok(s) if s == "UTC0" || s.is_empty() => Tz::Etc__UTC,
_ => match get_timezone() {
Ok(tz_str) => tz_str.parse().unwrap(),
Err(_) => Tz::Etc__UTC,
},
};
let offset = tz.offset_from_utc_date(&Utc::now().date_naive());
offset.abbreviation().unwrap_or("UTC").to_string()
}
/// Adapt the given string to be accepted by the chrono library crate.
///
/// # Arguments
///
/// fmt: the format of the string
///
/// # Return
///
/// A string that can be used as parameter of the chrono functions that use formats
pub fn custom_time_format(fmt: &str) -> String {
// TODO - Revisit when chrono 0.5 is released. https://github.com/chronotope/chrono/issues/970
// GNU `date` uses `%N` for nano seconds, however the `chrono` crate uses `%f`.
fmt.replace("%N", "%f")
.replace("%Z", timezone_abbreviation().as_ref())
}
#[cfg(test)]
mod tests {
use super::{custom_time_format, timezone_abbreviation};
#[test]
fn test_custom_time_format() {
assert_eq!(custom_time_format("%Y-%m-%d %H-%M-%S"), "%Y-%m-%d %H-%M-%S");
assert_eq!(custom_time_format("%d-%m-%Y %H-%M-%S"), "%d-%m-%Y %H-%M-%S");
assert_eq!(custom_time_format("%Y-%m-%d %H-%M-%S"), "%Y-%m-%d %H-%M-%S");
assert_eq!(
custom_time_format("%Y-%m-%d %H-%M-%S.%N"),
"%Y-%m-%d %H-%M-%S.%f"
);
assert_eq!(custom_time_format("%Z"), timezone_abbreviation());
}
}

View file

@ -46,6 +46,8 @@ pub use crate::features::buf_copy;
pub use crate::features::checksum; pub use crate::features::checksum;
#[cfg(feature = "colors")] #[cfg(feature = "colors")]
pub use crate::features::colors; pub use crate::features::colors;
#[cfg(feature = "custom-tz-fmt")]
pub use crate::features::custom_tz_fmt;
#[cfg(feature = "encoding")] #[cfg(feature = "encoding")]
pub use crate::features::encoding; pub use crate::features::encoding;
#[cfg(feature = "format")] #[cfg(feature = "format")]