From 652e27e6dd7b24264c035273daedd2d59b25ad09 Mon Sep 17 00:00:00 2001 From: Benjamin Sago Date: Mon, 3 Jul 2017 08:45:14 +0100 Subject: [PATCH] Extract time formatter This commit collects all the time-related fields from the Environment and bundles them all together in their own encapsulated struct. --- src/output/mod.rs | 1 + src/output/render/times.rs | 40 +++++-------------- src/output/table.rs | 81 ++++++++++++-------------------------- src/output/time.rs | 79 +++++++++++++++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 86 deletions(-) create mode 100644 src/output/time.rs diff --git a/src/output/mod.rs b/src/output/mod.rs index 9565536..1d0da6d 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -8,6 +8,7 @@ pub mod file_name; pub mod grid_details; pub mod grid; pub mod lines; +pub mod time; mod cell; mod colours; diff --git a/src/output/render/times.rs b/src/output/render/times.rs index b6b32d0..03bcf08 100644 --- a/src/output/render/times.rs +++ b/src/output/render/times.rs @@ -1,45 +1,25 @@ +use datetime::TimeZone; + +use fs::fields as f; use output::cell::TextCell; use output::colours::Colours; -use fs::fields as f; - -use datetime::{LocalDateTime, TimeZone, DatePiece}; -use datetime::fmt::DateFormat; -use locale; +use output::time::TimeFormat; #[allow(trivial_numeric_casts)] impl f::Time { - pub fn render(&self, colours: &Colours, tz: &Option, - date_and_time: &DateFormat<'static>, date_and_year: &DateFormat<'static>, - time: &locale::Time, current_year: i64) -> TextCell { - - // TODO(ogham): This method needs some serious de-duping! - // zoned and local times have different types at the moment, - // so it's tricky. + pub fn render(&self, colours: &Colours, + tz: &Option, + style: &TimeFormat) -> TextCell { if let Some(ref tz) = *tz { - let date = tz.to_zoned(LocalDateTime::at(self.0 as i64)); - - let datestamp = if date.year() == current_year { - date_and_time.format(&date, time) - } - else { - date_and_year.format(&date, time) - }; - + let datestamp = style.format_zoned(self.0 as i64, tz); TextCell::paint(colours.date, datestamp) } else { - let date = LocalDateTime::at(self.0 as i64); - - let datestamp = if date.year() == current_year { - date_and_time.format(&date, time) - } - else { - date_and_year.format(&date, time) - }; - + let datestamp = style.format_local(self.0 as i64); TextCell::paint(colours.date, datestamp) } } } + diff --git a/src/output/table.rs b/src/output/table.rs index cb7b3c6..1b67893 100644 --- a/src/output/table.rs +++ b/src/output/table.rs @@ -1,8 +1,6 @@ use std::cmp::max; use std::sync::{Mutex, MutexGuard}; -use datetime::fmt::DateFormat; -use datetime::{LocalDateTime, DatePiece}; use datetime::TimeZone; use zoneinfo_compiled::{CompiledData, Result as TZResult}; @@ -13,6 +11,7 @@ use users::UsersCache; use output::cell::TextCell; use output::colours::Colours; use output::column::{Alignment, Column}; +use output::time::TimeFormat; use fs::{File, fields as f}; @@ -23,21 +22,11 @@ use fs::{File, fields as f}; /// Any environment field should be able to be mocked up for test runs. pub struct Environment { - /// The year of the current time. This gets used to determine which date - /// format to use. - current_year: i64, - /// Localisation rules for formatting numbers. numeric: locale::Numeric, - /// Localisation rules for formatting timestamps. - time: locale::Time, - - /// Date format for printing out timestamps that are in the current year. - date_and_time: DateFormat<'static>, - - /// Date format for printing out timestamps that *aren’t*. - date_and_year: DateFormat<'static>, + /// Rules for formatting timestamps. + time_format: TimeFormat, /// The computer's current time zone. This gets used to determine how to /// offset files' timestamps. @@ -55,43 +44,22 @@ impl Environment { impl Default for Environment { fn default() -> Self { - use unicode_width::UnicodeWidthStr; + let tz = match determine_time_zone() { + Ok(t) => Some(t), + Err(ref e) => { + println!("Unable to determine time zone: {}", e); + None + } + }; - let tz = determine_time_zone(); - if let Err(ref e) = tz { - println!("Unable to determine time zone: {}", e); - } + let time_format = TimeFormat::deduce(); let numeric = locale::Numeric::load_user_locale() .unwrap_or_else(|_| locale::Numeric::english()); - let time = locale::Time::load_user_locale() - .unwrap_or_else(|_| locale::Time::english()); - - // Some locales use a three-character wide month name (Jan to Dec); - // others vary between three and four (1月 to 12月). We assume that - // December is the month with the maximum width, and use the width of - // that to determine how to pad the other months. - let december_width = UnicodeWidthStr::width(&*time.short_month_name(11)); - let date_and_time = match december_width { - 4 => DateFormat::parse("{2>:D} {4>:M} {2>:h}:{02>:m}").unwrap(), - _ => DateFormat::parse("{2>:D} {:M} {2>:h}:{02>:m}").unwrap(), - }; + let users = Mutex::new(UsersCache::new()); - let date_and_year = match december_width { - 4 => DateFormat::parse("{2>:D} {4>:M} {5>:Y}").unwrap(), - _ => DateFormat::parse("{2>:D} {:M} {5>:Y}").unwrap() - }; - - Environment { - current_year: LocalDateTime::now().year(), - numeric: numeric, - date_and_time: date_and_time, - date_and_year: date_and_year, - time: time, - tz: tz.ok(), - users: Mutex::new(UsersCache::new()), - } + Environment { tz, time_format, numeric, users } } } @@ -171,17 +139,18 @@ impl<'a, 'f> Table<'a> { use output::column::TimeType::*; match *column { - Column::Permissions => self.permissions_plus(file, xattrs).render(&self.colours), - Column::FileSize(fmt) => file.size().render(&self.colours, fmt, &self.env.numeric), - Column::Timestamp(Modified) => file.modified_time().render(&self.colours, &self.env.tz, &self.env.date_and_time, &self.env.date_and_year, &self.env.time, self.env.current_year), - Column::Timestamp(Created) => file.created_time().render( &self.colours, &self.env.tz, &self.env.date_and_time, &self.env.date_and_year, &self.env.time, self.env.current_year), - Column::Timestamp(Accessed) => file.accessed_time().render(&self.colours, &self.env.tz, &self.env.date_and_time, &self.env.date_and_year, &self.env.time, self.env.current_year), - Column::HardLinks => file.links().render(&self.colours, &self.env.numeric), - Column::Inode => file.inode().render(&self.colours), - Column::Blocks => file.blocks().render(&self.colours), - Column::User => file.user().render(&self.colours, &*self.env.lock_users()), - Column::Group => file.group().render(&self.colours, &*self.env.lock_users()), - Column::GitStatus => file.git_status().render(&self.colours), + Column::Permissions => self.permissions_plus(file, xattrs).render(&self.colours), + Column::FileSize(fmt) => file.size().render(&self.colours, fmt, &self.env.numeric), + Column::HardLinks => file.links().render(&self.colours, &self.env.numeric), + Column::Inode => file.inode().render(&self.colours), + Column::Blocks => file.blocks().render(&self.colours), + Column::User => file.user().render(&self.colours, &*self.env.lock_users()), + Column::Group => file.group().render(&self.colours, &*self.env.lock_users()), + Column::GitStatus => file.git_status().render(&self.colours), + + Column::Timestamp(Modified) => file.modified_time().render(&self.colours, &self.env.tz, &self.env.time_format), + Column::Timestamp(Created) => file.created_time().render( &self.colours, &self.env.tz, &self.env.time_format), + Column::Timestamp(Accessed) => file.accessed_time().render(&self.colours, &self.env.tz, &self.env.time_format), } } diff --git a/src/output/time.rs b/src/output/time.rs new file mode 100644 index 0000000..d0e7162 --- /dev/null +++ b/src/output/time.rs @@ -0,0 +1,79 @@ +use datetime::{LocalDateTime, TimeZone, DatePiece}; +use datetime::fmt::DateFormat; +use locale; + +use fs::fields::time_t; + + +#[derive(Debug, Clone)] +pub struct TimeFormat { + + /// The year of the current time. This gets used to determine which date + /// format to use. + pub current_year: i64, + + /// Localisation rules for formatting timestamps. + pub locale: locale::Time, + + /// Date format for printing out timestamps that are in the current year. + pub date_and_time: DateFormat<'static>, + + /// Date format for printing out timestamps that *aren’t*. + pub date_and_year: DateFormat<'static>, +} + +impl TimeFormat { + fn is_recent(&self, date: LocalDateTime) -> bool { + date.year() == self.current_year + } + + #[allow(trivial_numeric_casts)] + pub fn format_local(&self, time: time_t) -> String { + let date = LocalDateTime::at(time as i64); + + if self.is_recent(date) { + self.date_and_time.format(&date, &self.locale) + } + else { + self.date_and_year.format(&date, &self.locale) + } + } + + #[allow(trivial_numeric_casts)] + pub fn format_zoned(&self, time: time_t, zone: &TimeZone) -> String { + let date = zone.to_zoned(LocalDateTime::at(time as i64)); + + if self.is_recent(date) { + self.date_and_time.format(&date, &self.locale) + } + else { + self.date_and_year.format(&date, &self.locale) + } + } + + pub fn deduce() -> TimeFormat { + use unicode_width::UnicodeWidthStr; + + let locale = locale::Time::load_user_locale() + .unwrap_or_else(|_| locale::Time::english()); + + let current_year = LocalDateTime::now().year(); + + // Some locales use a three-character wide month name (Jan to Dec); + // others vary between three and four (1月 to 12月). We assume that + // December is the month with the maximum width, and use the width of + // that to determine how to pad the other months. + let december_width = UnicodeWidthStr::width(&*locale.short_month_name(11)); + let date_and_time = match december_width { + 4 => DateFormat::parse("{2>:D} {4>:M} {2>:h}:{02>:m}").unwrap(), + _ => DateFormat::parse("{2>:D} {:M} {2>:h}:{02>:m}").unwrap(), + }; + + let date_and_year = match december_width { + 4 => DateFormat::parse("{2>:D} {4>:M} {5>:Y}").unwrap(), + _ => DateFormat::parse("{2>:D} {:M} {5>:Y}").unwrap() + }; + + TimeFormat { current_year, locale, date_and_time, date_and_year } + } +} -- GitLab