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

Use chrono
This commit is contained in:
Christina Sørensen 2023-09-11 08:04:02 +02:00 committed by GitHub
commit e074aa8f67
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 316 additions and 335 deletions

204
Cargo.lock generated
View file

@ -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"

View file

@ -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" ]

View file

@ -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()
}

View file

@ -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 files 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 files 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 files 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 files 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 files type.

View file

@ -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("-")
};

View file

@ -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 computers current time offset, determined from time zone.
time_offset: FixedOffset,
/// Localisation rules for formatting numbers.
numeric: locale::Numeric,
/// The computers 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 }
Self { time_offset, numeric, #[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")
}
}
#[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)
}
}
}

View file

@ -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<FixedOffset>) -> 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<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();
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!("{:<width$}", month, width = padding);
assert_eq!(max_month_width, UnicodeWidthStr::width(final_str.as_str()));
}
#[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)
);
}
}