From d1c9e9ddecff35a6eab845c8668bfc1b528e86db Mon Sep 17 00:00:00 2001 From: ariasuni Date: Sat, 8 May 2021 23:04:13 +0200 Subject: [PATCH] feat: use chrono crate to handle datetime-related features - Improve compatibility with other OSes, timezone are handled for us - Reduce significantly the code to render date and time --- Cargo.lock | 204 +++++++++++++++++++++++++++--- Cargo.toml | 11 +- build.rs | 6 +- src/fs/file.rs | 70 ++++------ src/output/render/times.rs | 24 ++-- src/output/table.rs | 55 ++------ src/output/time.rs | 253 ++++++++++++------------------------- 7 files changed, 315 insertions(+), 308 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 39717a67..762145b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "ansiterm" version = "0.12.2" @@ -29,6 +44,12 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + [[package]] name = "byteorder" version = "1.4.3" @@ -44,6 +65,33 @@ dependencies = [ "jobserver", ] +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f56b4c72906975ca04becb8a30e102dfecddd0c06181e3e95ddc444be28881f8" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "time", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + [[package]] name = "datetime" version = "0.5.2" @@ -51,8 +99,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c3f7a77f3e57fedf80e09136f2d8777ebf621207306f6d96d610af048354bc" dependencies = [ "libc", - "locale", - "pad", "redox_syscall", "winapi", ] @@ -83,7 +129,7 @@ name = "eza" version = "0.11.0" dependencies = [ "ansiterm", - "datetime", + "chrono", "gethostname", "git2", "glob", @@ -142,9 +188,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "hermit-abi" @@ -152,6 +198,29 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +[[package]] +name = "iana-time-zone" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "idna" version = "0.2.3" @@ -183,6 +252,15 @@ dependencies = [ "libc", ] +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -253,6 +331,15 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c" +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.16.0" @@ -269,6 +356,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + [[package]] name = "openssl-src" version = "111.26.0+1.1.1u" @@ -292,15 +385,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "pad" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3" -dependencies = [ - "unicode-width", -] - [[package]] name = "partition-identity" version = "0.3.0" @@ -440,9 +524,9 @@ checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "syn" -version = "2.0.31" +version = "2.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" dependencies = [ "proc-macro2", "quote", @@ -470,24 +554,35 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.47" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" +checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.47" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" +checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi", + "winapi", +] + [[package]] name = "timeago" version = "0.4.1" @@ -573,6 +668,66 @@ version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbdbff6266a24120518560b5dc983096efb98462e51d0d68169895b237be3e5d" +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + [[package]] name = "winapi" version = "0.3.9" @@ -595,6 +750,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index f4800ef4..3c1ce14b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ name = "eza" [dependencies] ansiterm = "0.12.2" gethostname = "0.4.3" +chrono = "0.4" glob = "0.3" lazy_static = "1.3" libc = "0.2" @@ -55,11 +56,6 @@ unicode-width = "0.1" urlencoding = "2.1.3" zoneinfo_compiled = "0.5.1" -[dependencies.datetime] -version = "0.5.2" -default-features = false -features = ["format"] - [dependencies.git2] version = "0.18" optional = true @@ -71,9 +67,8 @@ proc-mounts = "0.3" [target.'cfg(unix)'.dependencies] uzers = "0.11.2" -[build-dependencies.datetime] -version = "0.5.2" -default-features = false +[build-dependencies] +chrono = "0.4" [features] default = [ "git" ] diff --git a/build.rs b/build.rs index 532d81b2..143a467b 100644 --- a/build.rs +++ b/build.rs @@ -15,7 +15,7 @@ use std::fs::File; use std::io::{self, Write}; use std::path::PathBuf; -use datetime::{LocalDateTime, ISO}; +use chrono::prelude::*; /// The build script entry point. @@ -118,6 +118,6 @@ fn nonstandard_features_string() -> String { /// Formats the current date as an ISO 8601 string. fn build_date() -> String { - let now = LocalDateTime::now(); - format!("{}", now.date().iso()) + let now = Local::now(); + now.date_naive().format("%Y-%m-%d").to_string() } diff --git a/src/fs/file.rs b/src/fs/file.rs index 0905d96c..2fe04c73 100644 --- a/src/fs/file.rs +++ b/src/fs/file.rs @@ -6,9 +6,8 @@ use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt}; #[cfg(windows)] use std::os::windows::fs::MetadataExt; use std::path::{Path, PathBuf}; -use std::time::SystemTime; -#[cfg(unix)] -use std::time::{Duration, UNIX_EPOCH}; + +use chrono::prelude::*; use log::*; @@ -536,42 +535,29 @@ impl<'dir> File<'dir> { } /// This file’s last modified timestamp, if available on this platform. - pub fn modified_time(&self) -> Option { + pub fn modified_time(&self) -> Option { if self.is_link() && self.deref_links { - match self.link_target_recurse() { - FileTarget::Ok(f) => f.metadata.modified().ok(), + return match self.link_target_recurse() { + FileTarget::Ok(f) => f.modified_time(), _ => None, - } - } else { - self.metadata.modified().ok() + }; } + self.metadata.modified().map(|st| DateTime::::from(st).naive_utc()).ok() } /// This file’s last changed timestamp, if available on this platform. #[cfg(unix)] - pub fn changed_time(&self) -> Option { + pub fn changed_time(&self) -> Option { if self.is_link() && self.deref_links { - match self.link_target_recurse() { - FileTarget::Ok(f) => return f.changed_time(), - _ => return None, - } - } - - let (mut sec, mut nanosec) = (self.metadata.ctime(), self.metadata.ctime_nsec()); - - if sec < 0 { - if nanosec > 0 { - sec += 1; - nanosec -= 1_000_000_000; - } - - let duration = Duration::new(sec.unsigned_abs(), nanosec.unsigned_abs() as u32); - Some(UNIX_EPOCH - duration) - } - else { - let duration = Duration::new(sec as u64, nanosec as u32); - Some(UNIX_EPOCH + duration) + return match self.link_target_recurse() { + FileTarget::Ok(f) => f.changed_time(), + _ => None, + }; } + NaiveDateTime::from_timestamp_opt( + self.metadata.ctime(), + self.metadata.ctime_nsec() as u32, + ) } #[cfg(windows)] @@ -580,27 +566,25 @@ impl<'dir> File<'dir> { } /// This file’s last accessed timestamp, if available on this platform. - pub fn accessed_time(&self) -> Option { + pub fn accessed_time(&self) -> Option { if self.is_link() && self.deref_links { - match self.link_target_recurse() { - FileTarget::Ok(f) => f.metadata.accessed().ok(), + return match self.link_target_recurse() { + FileTarget::Ok(f) => f.accessed_time(), _ => None, - } - } else { - self.metadata.accessed().ok() + }; } + self.metadata.accessed().map(|st| DateTime::::from(st).naive_utc()).ok() } /// This file’s created timestamp, if available on this platform. - pub fn created_time(&self) -> Option { + pub fn created_time(&self) -> Option { if self.is_link() && self.deref_links { - match self.link_target_recurse() { - FileTarget::Ok(f) => f.metadata.created().ok(), - _ => None, - } - } else { - self.metadata.created().ok() + return match self.link_target_recurse() { + FileTarget::Ok(f) => f.created_time(), + _ => None, + }; } + self.metadata.created().map(|st| DateTime::::from(st).naive_utc()).ok() } /// This file’s ‘type’. diff --git a/src/output/render/times.rs b/src/output/render/times.rs index 96538f7b..c4b0090c 100644 --- a/src/output/render/times.rs +++ b/src/output/render/times.rs @@ -1,27 +1,19 @@ -use std::time::SystemTime; - -use datetime::TimeZone; -use ansiterm::Style; - use crate::output::cell::TextCell; use crate::output::time::TimeFormat; +use ansiterm::Style; +use chrono::prelude::*; + pub trait Render { - fn render(self, style: Style, tz: &Option, format: TimeFormat) -> TextCell; + fn render(self, style: Style, time_offset: FixedOffset, time_format: TimeFormat) -> TextCell; } -impl Render for Option { - fn render(self, style: Style, tz: &Option, 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 { - if let Some(ref tz) = tz { - format.format_zoned(time, tz) - } - else { - format.format_local(time) - } - } - else { + time_format.format(&DateTime::::from_naive_utc_and_offset(time, time_offset)) + } else { String::from("-") }; diff --git a/src/output/table.rs b/src/output/table.rs index 9cd5e7a5..d7d39ea5 100644 --- a/src/output/table.rs +++ b/src/output/table.rs @@ -1,14 +1,9 @@ use std::cmp::max; -#[cfg(unix)] -use std::env; use std::ops::Deref; #[cfg(unix)] use std::sync::{Mutex, MutexGuard}; -use datetime::TimeZone; -#[cfg(unix)] -use zoneinfo_compiled::CompiledData; -use zoneinfo_compiled::Result as TZResult; +use chrono::prelude::*; use lazy_static::lazy_static; use log::*; @@ -336,13 +331,12 @@ 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, - /// The computer’s current time zone. This gets used to determine how to - /// offset files’ timestamps. - tz: Option, - /// Mapping cache of user IDs to usernames. #[cfg(unix)] users: Mutex, @@ -355,15 +349,7 @@ impl Environment { } fn load_all() -> Self { - let tz = match determine_time_zone() { - Ok(t) => { - Some(t) - } - Err(ref e) => { - eprintln!("Unable to determine time zone: {e}"); - None - } - }; + let time_offset = *Local::now().offset(); let numeric = locale::Numeric::load_user_locale() .unwrap_or_else(|_| locale::Numeric::english()); @@ -371,28 +357,7 @@ impl Environment { #[cfg(unix)] let users = Mutex::new(UsersCache::new()); - Self { numeric, tz, #[cfg(unix)] users } - } -} - -#[cfg(unix)] -fn determine_time_zone() -> TZResult { - if let Ok(file) = env::var("TZ") { - TimeZone::from_file({ - if file.starts_with('/') { - file - } else { - format!("/usr/share/zoneinfo/{}", { - if file.starts_with(':') { - file.replacen(':', "", 1) - } else { - file - } - }) - } - }) - } else { - TimeZone::from_file("/etc/localtime") + Self { time_offset, numeric, users } } } @@ -561,16 +526,16 @@ impl<'a> Table<'a> { } Column::Timestamp(TimeType::Modified) => { - file.modified_time().render(self.theme.ui.date, &self.env.tz, self.time_format) + file.modified_time().render(self.theme.ui.date, self.env.time_offset, self.time_format) } Column::Timestamp(TimeType::Changed) => { - file.changed_time().render(self.theme.ui.date, &self.env.tz, self.time_format) + file.changed_time().render(self.theme.ui.date, self.env.time_offset, self.time_format) } Column::Timestamp(TimeType::Created) => { - file.created_time().render(self.theme.ui.date, &self.env.tz, self.time_format) + file.created_time().render(self.theme.ui.date, self.env.time_offset, self.time_format) } Column::Timestamp(TimeType::Accessed) => { - file.accessed_time().render(self.theme.ui.date, &self.env.tz, self.time_format) + file.accessed_time().render(self.theme.ui.date, self.env.time_offset, self.time_format) } } } diff --git a/src/output/time.rs b/src/output/time.rs index 163c870c..a3f177ad 100644 --- a/src/output/time.rs +++ b/src/output/time.rs @@ -1,12 +1,8 @@ //! Timestamp formatting. -use std::convert::TryInto; -use std::cmp::max; -use std::time::{SystemTime, UNIX_EPOCH, Duration}; - -use datetime::{LocalDateTime, TimeZone, DatePiece, TimePiece, Instant}; -use datetime::fmt::DateFormat; - +use core::cmp::max; +use std::time::Duration; +use chrono::prelude::*; use lazy_static::lazy_static; use unicode_width::UnicodeWidthStr; @@ -53,80 +49,59 @@ pub enum TimeFormat { Relative, } -// There are two different formatting functions because local and zoned -// timestamps are separate types. - impl TimeFormat { - pub fn format_local(self, time: SystemTime) -> String { + pub fn format(self, time: &DateTime) -> String { match self { - Self::DefaultFormat => default_local(time), - Self::ISOFormat => iso_local(time), - Self::LongISO => long_local(time), - Self::FullISO => full_local(time), - Self::Relative => relative(time), - } - } - - pub fn format_zoned(self, time: SystemTime, zone: &TimeZone) -> String { - match self { - Self::DefaultFormat => default_zoned(time, zone), - Self::ISOFormat => iso_zoned(time, zone), - Self::LongISO => long_zoned(time, zone), - Self::FullISO => full_zoned(time, zone), + Self::DefaultFormat => default(time), + Self::ISOFormat => iso(time), + Self::LongISO => long(time), + Self::FullISO => full(time), Self::Relative => relative(time), } } } - -#[allow(trivial_numeric_casts)] -fn default_local(time: SystemTime) -> String { - let date = LocalDateTime::at(systemtime_epoch(time)); - let date_format = get_dateformat(&date); - date_format.format(&date, &LOCALE) +fn default(time: &DateTime) -> String { + let month = &*LOCALE.short_month_name(time.month0() as usize); + let month_width = short_month_padding(*MAX_MONTH_WIDTH, month); + let format = if time.year() == *CURRENT_YEAR { + format!("%_d {month: String { - let date = zone.to_zoned(LocalDateTime::at(systemtime_epoch(time))); - let date_format = get_dateformat(&date); - date_format.format(&date, &LOCALE) +/// Convert between Unicode width and width in chars to use in format!. +/// ex: in Japanese, 月 is one character, but it has the width of two. +/// For alignement purposes, we take the real display width into account. +/// So, `MAXIMUM_MONTH_WIDTH` (“12月”) = 4, but if we use `{:4}` in format!, +/// it will add a space (“ 12月”) because format! counts characters. +/// Conversely, a char can have a width of zero (like combining diacritics) +fn short_month_padding(max_month_width: usize, month: &str) -> usize { + let shift = month.chars().count() as isize - UnicodeWidthStr::width(month) as isize; + (max_month_width as isize + shift) as usize } -fn get_dateformat(date: &LocalDateTime) -> &'static DateFormat<'static> { - match (is_recent(date), *MAXIMUM_MONTH_WIDTH) { - (true, 4) => &FOUR_WIDE_DATE_TIME, - (true, 5) => &FIVE_WIDE_DATE_TIME, - (true, _) => &OTHER_WIDE_DATE_TIME, - (false, 4) => &FOUR_WIDE_DATE_YEAR, - (false, 5) => &FIVE_WIDE_DATE_YEAR, - (false, _) => &OTHER_WIDE_DATE_YEAR, +fn iso(time: &DateTime) -> String { + if time.year() == *CURRENT_YEAR { + time.format("%m-%d %H:%M").to_string() + } else { + time.format("%Y-%m-%d").to_string() } } -#[allow(trivial_numeric_casts)] -fn long_local(time: SystemTime) -> String { - let date = LocalDateTime::at(systemtime_epoch(time)); - format!("{:04}-{:02}-{:02} {:02}:{:02}", - date.year(), date.month() as usize, date.day(), - date.hour(), date.minute()) +fn long(time: &DateTime) -> String { + time.format("%Y-%m-%d %H:%M").to_string() } -#[allow(trivial_numeric_casts)] -fn long_zoned(time: SystemTime, zone: &TimeZone) -> String { - let date = zone.to_zoned(LocalDateTime::at(systemtime_epoch(time))); - format!("{:04}-{:02}-{:02} {:02}:{:02}", - date.year(), date.month() as usize, date.day(), - date.hour(), date.minute()) -} - -#[allow(trivial_numeric_casts)] -fn relative(time: SystemTime) -> String { +// #[allow(trivial_numeric_casts)] +fn relative(time: &DateTime) -> String { timeago::Formatter::new() .ago("") .convert( Duration::from_secs( - max(0, Instant::now().seconds() - systemtime_epoch(time)) + max(0, Local::now().timestamp() - time.timestamp()) // this .unwrap is safe since the call above can never result in a // value < 0 .try_into().unwrap() @@ -134,131 +109,63 @@ fn relative(time: SystemTime) -> String { ) } -#[allow(trivial_numeric_casts)] -fn full_local(time: SystemTime) -> String { - let date = LocalDateTime::at(systemtime_epoch(time)); - format!("{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:09}", - date.year(), date.month() as usize, date.day(), - date.hour(), date.minute(), date.second(), systemtime_nanos(time)) -} - -#[allow(trivial_numeric_casts)] -fn full_zoned(time: SystemTime, zone: &TimeZone) -> String { - use datetime::Offset; - - let local = LocalDateTime::at(systemtime_epoch(time)); - let date = zone.to_zoned(local); - let offset = Offset::of_seconds(zone.offset(local) as i32).expect("Offset out of range"); - format!("{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:09} {:+03}{:02}", - date.year(), date.month() as usize, date.day(), - date.hour(), date.minute(), date.second(), systemtime_nanos(time), - offset.hours(), offset.minutes().abs()) -} - -#[allow(trivial_numeric_casts)] -fn iso_local(time: SystemTime) -> String { - let date = LocalDateTime::at(systemtime_epoch(time)); - - if is_recent(&date) { - format!("{:02}-{:02} {:02}:{:02}", - date.month() as usize, date.day(), - date.hour(), date.minute()) - } - else { - format!("{:04}-{:02}-{:02}", - date.year(), date.month() as usize, date.day()) - } -} - -#[allow(trivial_numeric_casts)] -fn iso_zoned(time: SystemTime, zone: &TimeZone) -> String { - let date = zone.to_zoned(LocalDateTime::at(systemtime_epoch(time))); - - if is_recent(&date) { - format!("{:02}-{:02} {:02}:{:02}", - date.month() as usize, date.day(), - date.hour(), date.minute()) - } - else { - format!("{:04}-{:02}-{:02}", - date.year(), date.month() as usize, date.day()) - } -} - - -fn systemtime_epoch(time: SystemTime) -> i64 { - time.duration_since(UNIX_EPOCH) - .map(|t| t.as_secs() as i64) - .unwrap_or_else(|e| { - let diff = e.duration(); - let mut secs = diff.as_secs(); - if diff.subsec_nanos() > 0 { - secs += 1; - } - -(secs as i64) - }) -} - -fn systemtime_nanos(time: SystemTime) -> u32 { - time.duration_since(UNIX_EPOCH) - .map(|t| t.subsec_nanos()) - .unwrap_or_else(|e| { - let nanos = e.duration().subsec_nanos(); - if nanos > 0 { - 1_000_000_000 - nanos - } else { - nanos - } - }) -} - -fn is_recent(date: &LocalDateTime) -> bool { - date.year() == *CURRENT_YEAR +fn full(time: &DateTime) -> String { + time.format("%Y-%m-%d %H:%M:%S.%f %z").to_string() } lazy_static! { - static ref CURRENT_YEAR: i64 = LocalDateTime::now().year(); + static ref CURRENT_YEAR: i32 = Local::now().year(); static ref LOCALE: locale::Time = { locale::Time::load_user_locale() .unwrap_or_else(|_| locale::Time::english()) }; - static ref MAXIMUM_MONTH_WIDTH: usize = { + static ref MAX_MONTH_WIDTH: usize = { // Some locales use a three-character wide month name (Jan to Dec); // others vary between three to four (1月 to 12月, juil.). We check each month width // to detect the longest and set the output format accordingly. - let mut maximum_month_width = 0; - for i in 0..11 { - let current_month_width = UnicodeWidthStr::width(&*LOCALE.short_month_name(i)); - maximum_month_width = std::cmp::max(maximum_month_width, current_month_width); - } - maximum_month_width + (0..11).map(|i| UnicodeWidthStr::width(&*LOCALE.short_month_name(i))).max().unwrap() }; - - static ref FOUR_WIDE_DATE_TIME: DateFormat<'static> = DateFormat::parse( - "{2>:D} {4<:M} {02>:h}:{02>:m}" - ).unwrap(); - - static ref FIVE_WIDE_DATE_TIME: DateFormat<'static> = DateFormat::parse( - "{2>:D} {5<:M} {02>:h}:{02>:m}" - ).unwrap(); - - static ref OTHER_WIDE_DATE_TIME: DateFormat<'static> = DateFormat::parse( - "{2>:D} {:M} {02>:h}:{02>:m}" - ).unwrap(); - - static ref FOUR_WIDE_DATE_YEAR: DateFormat<'static> = DateFormat::parse( - "{2>:D} {4<:M} {5>:Y}" - ).unwrap(); - - static ref FIVE_WIDE_DATE_YEAR: DateFormat<'static> = DateFormat::parse( - "{2>:D} {5<:M} {5>:Y}" - ).unwrap(); - - static ref OTHER_WIDE_DATE_YEAR: DateFormat<'static> = DateFormat::parse( - "{2>:D} {:M} {5>:Y}" - ).unwrap(); +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn short_month_width_japanese() { + let max_month_width = 4; + let month = "1\u{2F49}"; // 1月 + let padding = short_month_padding(max_month_width, month); + let final_str = format!("{: