1
0
Эх сурвалжийг харах

Merge pull request #227 from eza-community/use-chrono

Use chrono
Christina Sørensen 2 жил өмнө
parent
commit
e074aa8f67
7 өөрчлөгдсөн 313 нэмэгдсэн , 332 устгасан
  1. 184 20
      Cargo.lock
  2. 3 8
      Cargo.toml
  3. 3 3
      build.rs
  4. 28 44
      src/fs/file.rs
  5. 8 16
      src/output/render/times.rs
  6. 10 71
      src/output/table.rs
  7. 77 170
      src/output/time.rs

+ 184 - 20
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"

+ 3 - 8
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" ]

+ 3 - 3
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()
 }

+ 28 - 44
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,71 +535,56 @@ impl<'dir> File<'dir> {
     }
 
     /// This file’s last modified timestamp, if available on this platform.
-    pub fn modified_time(&self) -> Option<SystemTime> {
+    pub fn modified_time(&self) -> Option<NaiveDateTime> {
         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::<Utc>::from(st).naive_utc()).ok()
     }
 
     /// This file’s last changed timestamp, if available on this platform.
     #[cfg(unix)]
-    pub fn changed_time(&self) -> Option<SystemTime> {
+    pub fn changed_time(&self) -> Option<NaiveDateTime> {
         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)]
-    pub fn changed_time(&self) -> Option<SystemTime> {
+    pub fn changed_time(&self) -> Option<NaiveDateTime> {
         self.modified_time()
     }
 
     /// This file’s last accessed timestamp, if available on this platform.
-    pub fn accessed_time(&self) -> Option<SystemTime> {
+    pub fn accessed_time(&self) -> Option<NaiveDateTime> {
         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::<Utc>::from(st).naive_utc()).ok()
     }
 
     /// This file’s created timestamp, if available on this platform.
-    pub fn created_time(&self) -> Option<SystemTime> {
+    pub fn created_time(&self) -> Option<NaiveDateTime> {
         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::<Utc>::from(st).naive_utc()).ok()
     }
 
     /// This file’s ‘type’.

+ 8 - 16
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<TimeZone>, format: TimeFormat) -> TextCell;
+    fn render(self, style: Style, time_offset: FixedOffset, time_format: TimeFormat) -> TextCell;
 }
 
-impl Render for Option<SystemTime> {
-    fn render(self, style: Style, tz: &Option<TimeZone>, format: TimeFormat) -> TextCell {
+impl Render for Option<NaiveDateTime> {
+    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::<FixedOffset>::from_naive_utc_and_offset(time, time_offset))
+        } else {
             String::from("-")
         };
 

+ 10 - 71
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<TimeZone>,
-
     /// Mapping cache of user IDs to usernames.
     #[cfg(unix)]
     users: Mutex<UsersCache>,
@@ -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,57 +357,10 @@ impl Environment {
         #[cfg(unix)]
         let users = Mutex::new(UsersCache::new());
 
-        Self { numeric, tz, #[cfg(unix)] users }
-    }
-}
-
-#[cfg(unix)]
-fn determine_time_zone() -> TZResult<TimeZone> {
-    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, #[cfg(unix)] users }
     }
 }
 
-#[allow(clippy::unnecessary_wraps)] // Needs to match Unix function
-#[cfg(windows)]
-fn determine_time_zone() -> TZResult<TimeZone> {
-    use datetime::zone::{FixedTimespan, FixedTimespanSet, StaticTimeZone, TimeZoneSource};
-    use std::borrow::Cow;
-
-    Ok(TimeZone(TimeZoneSource::Static(&StaticTimeZone {
-        name: "Unsupported",
-        fixed_timespans: FixedTimespanSet {
-            first: FixedTimespan {
-                offset: 0,
-                is_dst: false,
-                name: Cow::Borrowed("ZONE_A"),
-            },
-            rest: &[(
-                1_206_838_800, // Sun Mar 30 2008 01:00:00 GMT+0000
-                FixedTimespan {
-                    offset: 3600,
-                    is_dst: false,
-                    name: Cow::Borrowed("ZONE_B"),
-                },
-            )],
-        },
-    })))
-}
-
 lazy_static! {
     static ref ENVIRONMENT: Environment = Environment::load_all();
 }
@@ -561,16 +500,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)
             }
         }
     }

+ 77 - 170
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 {
-        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 {
+    pub fn format(self, time: &DateTime<FixedOffset>) -> 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<FixedOffset>) -> 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:<month_width$} %H:%M")
+    } else {
+        format!("%_d {month:<month_width$}  %Y")
+    };
+    time.format(format.as_str()).to_string()
 }
 
-#[allow(trivial_numeric_casts)]
-fn default_zoned(time: SystemTime, zone: &TimeZone) -> 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<FixedOffset>) -> 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<FixedOffset>) -> 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<FixedOffset>) -> 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<FixedOffset>) -> 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();
+#[cfg(test)]
+mod test {
+    use super::*;
 
-    static ref FIVE_WIDE_DATE_YEAR: DateFormat<'static> = DateFormat::parse(
-        "{2>:D} {5<:M} {5>:Y}"
-    ).unwrap();
+    #[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!("{:<width$}", month, width = padding);
+        assert_eq!(max_month_width, UnicodeWidthStr::width(final_str.as_str()));
+    }
 
-    static ref OTHER_WIDE_DATE_YEAR: DateFormat<'static> = DateFormat::parse(
-        "{2>:D} {:M} {5>:Y}"
-    ).unwrap();
+    #[test]
+    fn short_month_width_hindi() {
+        let max_month_width = 4;
+        assert_eq!(true, [
+            "\u{091C}\u{0928}\u{0970}", // जन॰
+            "\u{092B}\u{093C}\u{0930}\u{0970}", // फ़र॰
+            "\u{092E}\u{093E}\u{0930}\u{094D}\u{091A}", // मार्च
+            "\u{0905}\u{092A}\u{094D}\u{0930}\u{0948}\u{0932}", // अप्रैल
+            "\u{092E}\u{0908}", // मई
+            "\u{091C}\u{0942}\u{0928}", // जून
+            "\u{091C}\u{0941}\u{0932}\u{0970}", // जुल॰
+            "\u{0905}\u{0917}\u{0970}", // अग॰
+            "\u{0938}\u{093F}\u{0924}\u{0970}", // सित॰
+            "\u{0905}\u{0915}\u{094D}\u{0924}\u{0942}\u{0970}", // अक्तू॰
+            "\u{0928}\u{0935}\u{0970}", // नव॰
+            "\u{0926}\u{093F}\u{0938}\u{0970}", // दिस॰
+        ].iter()
+            .map(|month| format!(
+                "{:<width$}",
+                month,
+                width = short_month_padding(max_month_width, month)
+            )).all(|string| UnicodeWidthStr::width(string.as_str()) == max_month_width)
+        );
+    }
 }