diff --git a/Cargo.lock b/Cargo.lock index 1334ec3d4..dcf841d61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -211,6 +211,27 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "chrono-tz" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6dd8046d00723a59a2f8c5f295c515b9bb9a331ee4f8f3d4dd49e428acd3b6" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94fea34d77a245229e7746bd2beb786cd2a896f306ff491fb8cecb3074b10a7" +dependencies = [ + "parse-zoneinfo", + "phf_codegen", +] + [[package]] name = "ciborium" version = "0.2.2" @@ -438,10 +459,12 @@ dependencies = [ "ansi-width", "backtrace", "chrono", + "chrono-tz", "criterion", "dirs", "git2", "glob", + "iana-time-zone", "libc", "locale", "log", @@ -1008,6 +1031,15 @@ dependencies = [ "syn", ] +[[package]] +name = "parse-zoneinfo" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" +dependencies = [ + "regex", +] + [[package]] name = "partition-identity" version = "0.3.0" @@ -1039,6 +1071,16 @@ dependencies = [ "phf_shared", ] +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared", +] + [[package]] name = "phf_generator" version = "0.11.3" diff --git a/Cargo.toml b/Cargo.toml index 68bf0414a..ff15999b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,6 +86,8 @@ name = "eza" [dependencies] rayon = "1.10.0" chrono = { version = "0.4.34", default-features = false, features = ["clock"] } +chrono-tz = "0.10.0" +iana-time-zone = "0.1.58" nu-ansi-term = { version = "0.50.1", features = [ "serde", "derive_serde_style", diff --git a/src/output/render/times.rs b/src/output/render/times.rs index 88b7c1efb..814a5a3ce 100644 --- a/src/output/render/times.rs +++ b/src/output/render/times.rs @@ -11,12 +11,26 @@ use chrono::prelude::*; use nu_ansi_term::Style; pub trait Render { - fn render(self, style: Style, time_offset: FixedOffset, time_format: TimeFormat) -> TextCell; + fn render(self, style: Style, time_format: TimeFormat) -> TextCell; } impl Render for Option { - fn render(self, style: Style, time_offset: FixedOffset, time_format: TimeFormat) -> TextCell { - let datestamp = if let Some(time) = self { + fn render(self, style: Style, time_format: TimeFormat) -> TextCell { + let datestamp = if let Ok(timezone_str) = iana_time_zone::get_timezone() { + let timezone: chrono_tz::Tz = timezone_str.parse().unwrap(); + if let Some(time) = self { + let time_offset = timezone.offset_from_utc_datetime(&time).fix(); + time_format.format(&DateTime::::from_naive_utc_and_offset( + time, + time_offset, + )) + } else { + String::from("-") + } + } else if let Some(time) = self { + // This is the next best thing, use the timezone now, instead of at the time of the + // timestamp. + let time_offset: FixedOffset = *Local::now().offset(); time_format.format(&DateTime::::from_naive_utc_and_offset( time, time_offset, @@ -24,7 +38,6 @@ impl Render for Option { } else { String::from("-") }; - TextCell::paint(style, datestamp) } } diff --git a/src/output/table.rs b/src/output/table.rs index 83082c5db..d6709bb5c 100644 --- a/src/output/table.rs +++ b/src/output/table.rs @@ -363,9 +363,6 @@ impl Default for TimeTypes { /// /// Any environment field should be able to be mocked up for test runs. pub struct Environment { - /// The computer’s current time offset, determined from time zone. - time_offset: FixedOffset, - /// Localisation rules for formatting numbers. numeric: locale::Numeric, @@ -381,8 +378,6 @@ impl Environment { } fn load_all() -> Self { - let time_offset = *Local::now().offset(); - let numeric = locale::Numeric::load_user_locale().unwrap_or_else(|_| locale::Numeric::english()); @@ -390,7 +385,6 @@ impl Environment { let users = Mutex::new(UsersCache::new()); Self { - time_offset, numeric, #[cfg(unix)] users, @@ -568,7 +562,6 @@ impl<'a> Table<'a> { } else { self.theme.ui.date.unwrap_or_default() }, - self.env.time_offset, self.time_format.clone(), ), } diff --git a/src/output/time.rs b/src/output/time.rs index d1bc6ea5f..a0ad113ac 100644 --- a/src/output/time.rs +++ b/src/output/time.rs @@ -196,4 +196,34 @@ mod test { .all(|string| UnicodeWidthStr::width(string.as_str()) == max_month_width) ); } + + #[test] + fn display_timestamp_correctly_in_timezone_with_dst() { + let timezone_str = "Europe/Berlin"; + let timezone: chrono_tz::Tz = timezone_str.parse().unwrap(); + let time = DateTime::::from_timestamp(1685867700, 0) + .unwrap() + .naive_utc(); + + let fixed_offset = timezone.offset_from_utc_datetime(&time).fix(); + let real_converted = DateTime::::from_naive_utc_and_offset(time, fixed_offset); + let formatted = full(&real_converted); + let expected = "2023-06-04 10:35:00.000000000 +0200"; + assert_eq!(expected, formatted); + } + + #[test] + fn display_timestamp_correctly_in_timezone_without_dst() { + let timezone_str = "Europe/Berlin"; + let timezone: chrono_tz::Tz = timezone_str.parse().unwrap(); + let time = DateTime::::from_timestamp(1699090500, 0) + .unwrap() + .naive_utc(); + + let fixed_offset = timezone.offset_from_utc_datetime(&time).fix(); + let real_converted = DateTime::::from_naive_utc_and_offset(time, fixed_offset); + let formatted = full(&real_converted); + let expected = "2023-11-04 10:35:00.000000000 +0100"; + assert_eq!(expected, formatted); + } }