Extract time formatter

This commit collects all the time-related fields from the Environment and bundles them all together in their own encapsulated struct.
This commit is contained in:
Benjamin Sago 2017-07-03 08:45:14 +01:00
parent fc60838ff3
commit 652e27e6dd
4 changed files with 115 additions and 86 deletions

View File

@ -8,6 +8,7 @@ pub mod file_name;
pub mod grid_details; pub mod grid_details;
pub mod grid; pub mod grid;
pub mod lines; pub mod lines;
pub mod time;
mod cell; mod cell;
mod colours; mod colours;

View File

@ -1,45 +1,25 @@
use datetime::TimeZone;
use fs::fields as f;
use output::cell::TextCell; use output::cell::TextCell;
use output::colours::Colours; use output::colours::Colours;
use fs::fields as f; use output::time::TimeFormat;
use datetime::{LocalDateTime, TimeZone, DatePiece};
use datetime::fmt::DateFormat;
use locale;
#[allow(trivial_numeric_casts)] #[allow(trivial_numeric_casts)]
impl f::Time { impl f::Time {
pub fn render(&self, colours: &Colours, tz: &Option<TimeZone>, pub fn render(&self, colours: &Colours,
date_and_time: &DateFormat<'static>, date_and_year: &DateFormat<'static>, tz: &Option<TimeZone>,
time: &locale::Time, current_year: i64) -> TextCell { style: &TimeFormat) -> TextCell {
// TODO(ogham): This method needs some serious de-duping!
// zoned and local times have different types at the moment,
// so it's tricky.
if let Some(ref tz) = *tz { if let Some(ref tz) = *tz {
let date = tz.to_zoned(LocalDateTime::at(self.0 as i64)); let datestamp = style.format_zoned(self.0 as i64, tz);
let datestamp = if date.year() == current_year {
date_and_time.format(&date, time)
}
else {
date_and_year.format(&date, time)
};
TextCell::paint(colours.date, datestamp) TextCell::paint(colours.date, datestamp)
} }
else { else {
let date = LocalDateTime::at(self.0 as i64); let datestamp = style.format_local(self.0 as i64);
let datestamp = if date.year() == current_year {
date_and_time.format(&date, time)
}
else {
date_and_year.format(&date, time)
};
TextCell::paint(colours.date, datestamp) TextCell::paint(colours.date, datestamp)
} }
} }
} }

View File

@ -1,8 +1,6 @@
use std::cmp::max; use std::cmp::max;
use std::sync::{Mutex, MutexGuard}; use std::sync::{Mutex, MutexGuard};
use datetime::fmt::DateFormat;
use datetime::{LocalDateTime, DatePiece};
use datetime::TimeZone; use datetime::TimeZone;
use zoneinfo_compiled::{CompiledData, Result as TZResult}; use zoneinfo_compiled::{CompiledData, Result as TZResult};
@ -13,6 +11,7 @@ use users::UsersCache;
use output::cell::TextCell; use output::cell::TextCell;
use output::colours::Colours; use output::colours::Colours;
use output::column::{Alignment, Column}; use output::column::{Alignment, Column};
use output::time::TimeFormat;
use fs::{File, fields as f}; 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. /// Any environment field should be able to be mocked up for test runs.
pub struct Environment { 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. /// Localisation rules for formatting numbers.
numeric: locale::Numeric, numeric: locale::Numeric,
/// Localisation rules for formatting timestamps. /// Rules for formatting timestamps.
time: locale::Time, time_format: TimeFormat,
/// Date format for printing out timestamps that are in the current year.
date_and_time: DateFormat<'static>,
/// Date format for printing out timestamps that *arent*.
date_and_year: DateFormat<'static>,
/// The computer's current time zone. This gets used to determine how to /// The computer's current time zone. This gets used to determine how to
/// offset files' timestamps. /// offset files' timestamps.
@ -55,43 +44,22 @@ impl Environment {
impl Default for Environment { impl Default for Environment {
fn default() -> Self { 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(); let time_format = TimeFormat::deduce();
if let Err(ref e) = tz {
println!("Unable to determine time zone: {}", e);
}
let numeric = locale::Numeric::load_user_locale() let numeric = locale::Numeric::load_user_locale()
.unwrap_or_else(|_| locale::Numeric::english()); .unwrap_or_else(|_| locale::Numeric::english());
let time = locale::Time::load_user_locale() let users = Mutex::new(UsersCache::new());
.unwrap_or_else(|_| locale::Time::english());
// Some locales use a three-character wide month name (Jan to Dec); Environment { tz, time_format, numeric, users }
// 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 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()),
}
} }
} }
@ -171,17 +139,18 @@ impl<'a, 'f> Table<'a> {
use output::column::TimeType::*; use output::column::TimeType::*;
match *column { match *column {
Column::Permissions => self.permissions_plus(file, xattrs).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::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::HardLinks => file.links().render(&self.colours, &self.env.numeric),
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::Inode => file.inode().render(&self.colours),
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::Blocks => file.blocks().render(&self.colours),
Column::HardLinks => file.links().render(&self.colours, &self.env.numeric), Column::User => file.user().render(&self.colours, &*self.env.lock_users()),
Column::Inode => file.inode().render(&self.colours), Column::Group => file.group().render(&self.colours, &*self.env.lock_users()),
Column::Blocks => file.blocks().render(&self.colours), Column::GitStatus => file.git_status().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::Timestamp(Modified) => file.modified_time().render(&self.colours, &self.env.tz, &self.env.time_format),
Column::GitStatus => file.git_status().render(&self.colours), 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),
} }
} }

79
src/output/time.rs Normal file
View File

@ -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 *arent*.
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 }
}
}