From 3a587e4cb871d29b66ff8221e458dd0916fa154c Mon Sep 17 00:00:00 2001 From: tommi Date: Wed, 22 Jan 2025 10:47:07 +0100 Subject: [PATCH 1/3] refactoring ls and date --- src/uu/date/src/date.rs | 6 +++-- src/uu/ls/src/ls.rs | 5 +++- src/uucore/src/lib/features/timezone_date.rs | 25 ++++++++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 src/uucore/src/lib/features/timezone_date.rs diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index f4d420c3fd..ae64de5c15 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -275,6 +275,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { match date { Ok(date) => { // TODO - Revisit when chrono 0.5 is released. https://github.com/chronotope/chrono/issues/970 + // From this line to line 292 can be refactored in the timezone_date.rs module + // custom_time_format(format_string, Utc::now().date_naive()); let tz = match std::env::var("TZ") { // TODO Support other time zones... Ok(s) if s == "UTC0" || s.is_empty() => Tz::Etc__UTC, @@ -284,11 +286,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }, }; let offset = tz.offset_from_utc_date(&Utc::now().date_naive()); - let tz_abbreviation = offset.abbreviation(); + let tz_abbreviation = offset.abbreviation().unwrap_or("UTC"); // 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")); + .replace("%Z", tz_abbreviation); // Refuse to pass this string to chrono as it is crashing in this crate if format_string.contains("%#z") { return Err(USimpleError::new( diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 9aaa0d0a4e..a8ab5ecee0 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -386,7 +386,10 @@ 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(e), _) => { + // this line can be replaced with the one in timzone_date.rs + custom_time_format(e, time) + } } } } diff --git a/src/uucore/src/lib/features/timezone_date.rs b/src/uucore/src/lib/features/timezone_date.rs new file mode 100644 index 0000000000..40f6c246dd --- /dev/null +++ b/src/uucore/src/lib/features/timezone_date.rs @@ -0,0 +1,25 @@ +mod timezone_date { + /// Get the alphabetic abbreviation of the current timezone. + /// + /// For example, "UTC" or "CET" or "PDT". + fn timezone_abbrev() -> &str { + 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. + pub fn custom_time_format(fmt: &str, time: DateTime) -> 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`. + let fmt = fmt.replace("%N", "%f").replace("%Z", timezone_abbrev()); + time.format(&fmt).to_string() + } +} From 78f6fb0c3d1dcd0a8ba8da3338a273cb4dc47950 Mon Sep 17 00:00:00 2001 From: tommi Date: Wed, 22 Jan 2025 16:00:05 +0100 Subject: [PATCH 2/3] Refactored and integrated ls and date and added tests to the new feature --- Cargo.lock | 6 +- src/uu/date/Cargo.toml | 4 +- src/uu/date/src/date.rs | 25 ++------- src/uu/ls/Cargo.toml | 2 +- src/uu/ls/src/ls.rs | 35 ++---------- src/uucore/Cargo.toml | 4 ++ src/uucore/src/lib/features.rs | 2 + src/uucore/src/lib/features/custom_tz_fmt.rs | 58 ++++++++++++++++++++ src/uucore/src/lib/features/timezone_date.rs | 25 --------- src/uucore/src/lib/lib.rs | 2 + 10 files changed, 80 insertions(+), 83 deletions(-) create mode 100644 src/uucore/src/lib/features/custom_tz_fmt.rs delete mode 100644 src/uucore/src/lib/features/timezone_date.rs diff --git a/Cargo.lock b/Cargo.lock index 5c2e937813..0c7d61d41d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2617,9 +2617,7 @@ name = "uu_date" version = "0.0.29" dependencies = [ "chrono", - "chrono-tz", "clap", - "iana-time-zone", "libc", "parse_datetime", "uucore", @@ -2883,7 +2881,6 @@ dependencies = [ "clap", "glob", "hostname", - "iana-time-zone", "lscolors", "number_prefix", "once_cell", @@ -3472,6 +3469,8 @@ version = "0.0.29" dependencies = [ "blake2b_simd", "blake3", + "chrono", + "chrono-tz", "clap", "crc32fast", "data-encoding", @@ -3481,6 +3480,7 @@ dependencies = [ "dunce", "glob", "hex", + "iana-time-zone", "itertools 0.14.0", "lazy_static", "libc", diff --git a/src/uu/date/Cargo.toml b/src/uu/date/Cargo.toml index 87e8d383a7..71f5225074 100644 --- a/src/uu/date/Cargo.toml +++ b/src/uu/date/Cargo.toml @@ -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 } diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index ae64de5c15..4e47c1bf7d 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -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,23 +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 - // From this line to line 292 can be refactored in the timezone_date.rs module - // custom_time_format(format_string, Utc::now().date_naive()); - 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().unwrap_or("UTC"); - // GNU `date` uses `%N` for nano seconds, however crate::chrono uses `%f` - let format_string = &format_string - .replace("%N", "%f") - .replace("%Z", tz_abbreviation); + 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( @@ -300,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, diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index 0b60009e65..3c14023f13 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -23,7 +23,6 @@ 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 +30,7 @@ selinux = { workspace = true, optional = true } terminal_size = { workspace = true } uucore = { workspace = true, features = [ "colors", + "custom-tz-fmt", "entries", "format", "fs", diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index a8ab5ecee0..1bfb612a9c 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -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) -> 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) -> 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) -> String { @@ -386,9 +360,10 @@ 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), _) => { + (Self::Format(fmt), _) => { // this line can be replaced with the one in timzone_date.rs - custom_time_format(e, time) + time.format(custom_tz_fmt::custom_time_format(fmt).as_str()) + .to_string() } } } diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index ce097d410a..2c7653795c 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -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 = [] diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index ef5be724d9..00079eed88 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -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")] diff --git a/src/uucore/src/lib/features/custom_tz_fmt.rs b/src/uucore/src/lib/features/custom_tz_fmt.rs new file mode 100644 index 0000000000..132155f540 --- /dev/null +++ b/src/uucore/src/lib/features/custom_tz_fmt.rs @@ -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()); + } +} diff --git a/src/uucore/src/lib/features/timezone_date.rs b/src/uucore/src/lib/features/timezone_date.rs deleted file mode 100644 index 40f6c246dd..0000000000 --- a/src/uucore/src/lib/features/timezone_date.rs +++ /dev/null @@ -1,25 +0,0 @@ -mod timezone_date { - /// Get the alphabetic abbreviation of the current timezone. - /// - /// For example, "UTC" or "CET" or "PDT". - fn timezone_abbrev() -> &str { - 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. - pub fn custom_time_format(fmt: &str, time: DateTime) -> 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`. - let fmt = fmt.replace("%N", "%f").replace("%Z", timezone_abbrev()); - time.format(&fmt).to_string() - } -} diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 9516b5e1bf..da29baf0c7 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -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")] From 2d7652a3045a3dfb81fe30f958245b300824e45d Mon Sep 17 00:00:00 2001 From: tommi Date: Wed, 22 Jan 2025 16:59:59 +0100 Subject: [PATCH 3/3] Style refactoring --- Cargo.lock | 1 - src/uu/date/src/date.rs | 2 +- src/uu/ls/Cargo.toml | 1 - src/uu/ls/src/ls.rs | 8 +++----- src/uucore/Cargo.toml | 2 +- 5 files changed, 5 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0c7d61d41d..b02f7eaa90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2877,7 +2877,6 @@ version = "0.0.29" dependencies = [ "ansi-width", "chrono", - "chrono-tz", "clap", "glob", "hostname", diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index 4e47c1bf7d..a3f2ad0426 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -273,7 +273,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { for date in dates { match date { Ok(date) => { - let format_string = custom_time_format(&format_string); + 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( diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index 3c14023f13..a21f178542 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -19,7 +19,6 @@ 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 } diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 1bfb612a9c..9a1fc795f7 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -360,11 +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(fmt), _) => { - // this line can be replaced with the one in timzone_date.rs - time.format(custom_tz_fmt::custom_time_format(fmt).as_str()) - .to_string() - } + (Self::Format(fmt), _) => time + .format(custom_tz_fmt::custom_time_format(fmt).as_str()) + .to_string(), } } } diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 2c7653795c..bc10328fbb 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -19,7 +19,7 @@ path = "src/lib/lib.rs" [dependencies] chrono = { workspace = true } -chrono-tz = {workspace = true} +chrono-tz = { workspace = true } clap = { workspace = true } uucore_procs = { workspace = true } number_prefix = { workspace = true }