From 60b5303ce6551c57d910ee38ae35afa14a43eb60 Mon Sep 17 00:00:00 2001 From: Alex Atkinson Date: Sat, 28 Feb 2026 19:30:41 -0500 Subject: [PATCH 1/4] feat: Add TZ Abbreviations --- src/output/time.rs | 71 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 66 insertions(+), 5 deletions(-) diff --git a/src/output/time.rs b/src/output/time.rs index f003745b0..b52ce2801 100644 --- a/src/output/time.rs +++ b/src/output/time.rs @@ -126,21 +126,82 @@ fn relative(time: &DateTime) -> String { } fn full(time: &DateTime) -> String { - time.format("%Y-%m-%d %H:%M:%S.%f %z").to_string() + format_with_tz(time, "%Y-%m-%d %H:%M:%S.%f %z") } fn custom(time: &DateTime, non_recent_fmt: &str, recent_fmt: Option<&str>) -> String { - if let Some(recent_fmt) = recent_fmt { + let format = if let Some(recent_fmt) = recent_fmt { if time.year() == *CURRENT_YEAR { - time.format(recent_fmt).to_string() + recent_fmt } else { - time.format(non_recent_fmt).to_string() + non_recent_fmt } } else { - time.format(non_recent_fmt).to_string() + non_recent_fmt + }; + + format_with_tz(time, format) +} + +fn format_with_tz(time: &DateTime, format: &str) -> String { + if !format.contains("%Z") { + return time.format(format).to_string(); + } + + let tz_name = TZ_HANDLER.get_abbreviation(time.timestamp()); + let mut result = String::new(); + let mut last_end = 0; + for (start, part) in format.match_indices("%Z") { + result.push_str(&time.format(&format[last_end..start]).to_string()); + result.push_str(&tz_name); + last_end = start + part.len(); + } + result.push_str(&time.format(&format[last_end..]).to_string()); + result +} + +struct TimezoneHandler; + +impl TimezoneHandler { + fn get_abbreviation(&self, timestamp: i64) -> String { + #[cfg(unix)] + { + unsafe { + extern "C" { + fn tzset(); + fn localtime_r( + timep: *const libc::time_t, + result: *mut libc::tm, + ) -> *mut libc::tm; + static tzname: [*const libc::c_char; 2]; + } + + tzset(); + let mut tm: libc::tm = std::mem::zeroed(); + #[allow(trivial_numeric_casts)] + let t = timestamp as libc::time_t; + if localtime_r(&t, &mut tm).is_null() { + return String::new(); + } + + let idx = if tm.tm_isdst > 0 { 1 } else { 0 }; + let ptr = tzname[idx]; + if ptr.is_null() { + return String::new(); + } + + std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned() + } + } + #[cfg(not(unix))] + { + String::new() + } } } +static TZ_HANDLER: LazyLock = LazyLock::new(|| TimezoneHandler); + static CURRENT_YEAR: LazyLock = LazyLock::new(|| Local::now().year()); static LOCALE: LazyLock = From a058861cd5cd731d289589230cf866518e047374 Mon Sep 17 00:00:00 2001 From: Alex Atkinson Date: Sat, 28 Feb 2026 19:37:14 -0500 Subject: [PATCH 2/4] feat: Add UTC argument --- src/options/parser.rs | 1 + src/options/view.rs | 2 ++ src/output/render/times.rs | 24 ++++++++++++++++++------ src/output/table.rs | 10 +++++++++- src/output/time.rs | 27 ++++++++++++++++++--------- 5 files changed, 48 insertions(+), 16 deletions(-) diff --git a/src/options/parser.rs b/src/options/parser.rs index c5b592fc7..43d88b825 100644 --- a/src/options/parser.rs +++ b/src/options/parser.rs @@ -132,6 +132,7 @@ pub fn get_command() -> clap::Command { .arg(arg!(-u --accessed "show the accessed timestamp field (replace default field, combinable)")) .arg(arg!(--changed "show the changed timestamp field (replace default field, combinable)")) .arg(arg!(-U --created "show the created timestamp field (replace default field, combinable)")) + .arg(arg!(--utc "show the time in the UTC timezone")) .arg(arg!(--"time-style"