1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37: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"
dependencies = [
"chrono",
"chrono-tz",
"clap",
"iana-time-zone",
"libc",
"parse_datetime",
"uucore",
@ -2879,11 +2877,9 @@ version = "0.0.29"
dependencies = [
"ansi-width",
"chrono",
"chrono-tz",
"clap",
"glob",
"hostname",
"iana-time-zone",
"lscolors",
"number_prefix",
"once_cell",
@ -3472,6 +3468,8 @@ version = "0.0.29"
dependencies = [
"blake2b_simd",
"blake3",
"chrono",
"chrono-tz",
"clap",
"crc32fast",
"data-encoding",
@ -3481,6 +3479,7 @@ dependencies = [
"dunce",
"glob",
"hex",
"iana-time-zone",
"itertools 0.14.0",
"lazy_static",
"libc",

View file

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

View file

@ -6,17 +6,16 @@
// spell-checker:ignore (chrono) Datelike Timelike ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes
use chrono::format::{Item, StrftimeItems};
use chrono::{DateTime, FixedOffset, Local, Offset, TimeDelta, TimeZone, Utc};
use chrono::{DateTime, FixedOffset, Local, Offset, TimeDelta, Utc};
#[cfg(windows)]
use chrono::{Datelike, Timelike};
use chrono_tz::{OffsetName, Tz};
use clap::{crate_version, Arg, ArgAction, Command};
use iana_time_zone::get_timezone;
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))]
use libc::{clock_settime, timespec, CLOCK_REALTIME};
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
use uucore::custom_tz_fmt::custom_time_format;
use uucore::display::Quotable;
use uucore::error::FromIo;
use uucore::error::{UResult, USimpleError};
@ -274,21 +273,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
for date in dates {
match date {
Ok(date) => {
// TODO - Revisit when chrono 0.5 is released. https://github.com/chronotope/chrono/issues/970
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"));
let format_string = custom_time_format(format_string);
// Refuse to pass this string to chrono as it is crashing in this crate
if format_string.contains("%#z") {
return Err(USimpleError::new(
@ -298,7 +283,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
}
// Hack to work around panic in chrono,
// 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) {
return Err(USimpleError::new(
1,

View file

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

View file

@ -27,14 +27,12 @@ use std::{
use std::{collections::HashSet, io::IsTerminal};
use ansi_width::ansi_width;
use chrono::{DateTime, Local, TimeDelta, TimeZone, Utc};
use chrono_tz::{OffsetName, Tz};
use chrono::{DateTime, Local, TimeDelta};
use clap::{
builder::{NonEmptyStringValueParser, PossibleValue, ValueParser},
crate_version, Arg, ArgAction, Command,
};
use glob::{MatchOptions, Pattern};
use iana_time_zone::get_timezone;
use lscolors::LsColors;
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::quoting_style::{self, escape_name, QuotingStyle};
use uucore::{
custom_tz_fmt,
display::Quotable,
error::{set_exit_code, UError, UResult},
format_usage,
@ -345,31 +344,6 @@ fn is_recent(time: DateTime<Local>) -> bool {
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 {
/// Format the given time according to this time format style.
fn format(&self, time: DateTime<Local>) -> String {
@ -386,7 +360,9 @@ impl TimeStyle {
//So it's not yet implemented
(Self::Locale, true) => time.format("%b %e %H:%M").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"
[dependencies]
chrono = { workspace = true }
chrono-tz = { workspace = true }
clap = { workspace = true }
uucore_procs = { workspace = true }
number_prefix = { workspace = true }
@ -25,6 +27,7 @@ dns-lookup = { workspace = true, optional = true }
dunce = { version = "1.0.4", optional = true }
wild = "2.2.1"
glob = { workspace = true }
iana-time-zone = { workspace = true }
lazy_static = "1.4.0"
# * optional
itertools = { workspace = true, optional = true }
@ -114,4 +117,5 @@ utf8 = []
utmpx = ["time", "time/macros", "libc", "dns-lookup"]
version-cmp = []
wide = []
custom-tz-fmt = []
tty = []

View file

@ -12,6 +12,8 @@ pub mod buf_copy;
pub mod checksum;
#[cfg(feature = "colors")]
pub mod colors;
#[cfg(feature = "custom-tz-fmt")]
pub mod custom_tz_fmt;
#[cfg(feature = "encoding")]
pub mod encoding;
#[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;
#[cfg(feature = "colors")]
pub use crate::features::colors;
#[cfg(feature = "custom-tz-fmt")]
pub use crate::features::custom_tz_fmt;
#[cfg(feature = "encoding")]
pub use crate::features::encoding;
#[cfg(feature = "format")]