diff --git a/Cargo.lock b/Cargo.lock index 5c2e937813..b02f7eaa90 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", @@ -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", 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 f4d420c3fd..a3f2ad0426 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,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, diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index 0b60009e65..a21f178542 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -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", diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 9aaa0d0a4e..9a1fc795f7 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,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(), } } } diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index ce097d410a..bc10328fbb 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/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")]