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:
parent
e9aa8a58fd
commit
93e3d08d5f
9 changed files with 80 additions and 57 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 = []
|
||||||
|
|
|
@ -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")]
|
||||||
|
|
58
src/uucore/src/lib/features/custom_tz_fmt.rs
Normal file
58
src/uucore/src/lib/features/custom_tz_fmt.rs
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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")]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue