From d82d038b5475a14d2210925bac9d949774a4531c Mon Sep 17 00:00:00 2001 From: Krishna Nagam <40730166+KrishnaNagam@users.noreply.github.com> Date: Sun, 12 Jan 2025 20:02:31 -0500 Subject: [PATCH] date: display %Z alphabetic time zone abbreviation Improve the display of dates formatted with the `%Z` specifier so that the timezone abbreviation is displayed, not just its numeric offset. Fixes #3756 Co-authored-by: Jeffrey Finkelstein --- src/uu/date/src/date.rs | 21 ++++++++++++++++++--- tests/by-util/test_date.rs | 16 ++++++++++------ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index 766e79bd4..d91f8f82c 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -6,10 +6,12 @@ // spell-checker:ignore (chrono) Datelike Timelike ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes use chrono::format::{Item, StrftimeItems}; -use chrono::{DateTime, FixedOffset, Local, Offset, TimeDelta, Utc}; +use chrono::{DateTime, FixedOffset, Local, Offset, TimeDelta, TimeZone, 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; @@ -272,8 +274,21 @@ 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" => 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"); + let format_string = &format_string + .replace("%N", "%f") + .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( @@ -403,7 +418,7 @@ fn make_format_string(settings: &Settings) -> &str { Rfc3339Format::Ns => "%F %T.%f%:z", }, Format::Custom(ref fmt) => fmt, - Format::Default => "%c", + Format::Default => "%a %b %e %X %Z %Y", } } diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs index 553414af8..d0a9c09a2 100644 --- a/tests/by-util/test_date.rs +++ b/tests/by-util/test_date.rs @@ -144,11 +144,12 @@ fn test_date_utc() { #[test] fn test_date_utc_issue_6495() { new_ucmd!() + .env("TZ", "UTC0") .arg("-u") .arg("-d") .arg("@0") .succeeds() - .stdout_is("Thu Jan 1 00:00:00 1970\n"); + .stdout_is("Thu Jan 1 00:00:00 UTC 1970\n"); } #[test] @@ -423,16 +424,18 @@ fn test_invalid_date_string() { #[test] fn test_date_one_digit_date() { new_ucmd!() + .env("TZ", "UTC0") .arg("-d") .arg("2000-1-1") .succeeds() - .stdout_contains("Sat Jan 1 00:00:00 2000"); + .stdout_only("Sat Jan 1 00:00:00 UTC 2000\n"); new_ucmd!() + .env("TZ", "UTC0") .arg("-d") .arg("2000-1-4") .succeeds() - .stdout_contains("Tue Jan 4 00:00:00 2000"); + .stdout_only("Tue Jan 4 00:00:00 UTC 2000\n"); } #[test] @@ -464,6 +467,7 @@ fn test_date_parse_from_format() { #[test] fn test_date_from_stdin() { new_ucmd!() + .env("TZ", "UTC0") .arg("-f") .arg("-") .pipe_in( @@ -473,8 +477,8 @@ fn test_date_from_stdin() { ) .succeeds() .stdout_is( - "Mon Mar 27 08:30:00 2023\n\ - Sat Apr 1 12:00:00 2023\n\ - Sat Apr 15 18:30:00 2023\n", + "Mon Mar 27 08:30:00 UTC 2023\n\ + Sat Apr 1 12:00:00 UTC 2023\n\ + Sat Apr 15 18:30:00 UTC 2023\n", ); }