diff --git a/src/fs/file.rs b/src/fs/file.rs index 0d42d54..5745208 100644 --- a/src/fs/file.rs +++ b/src/fs/file.rs @@ -4,7 +4,7 @@ use std::io::Error as IOError; use std::io::Result as IOResult; use std::os::unix::fs::{MetadataExt, PermissionsExt, FileTypeExt}; use std::path::{Path, PathBuf}; -use std::time::{UNIX_EPOCH, Duration}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; use log::{debug, error}; @@ -325,36 +325,36 @@ impl<'dir> File<'dir> { } } - /// This file’s last modified timestamp. - /// If the file's time is invalid, assume it was modified today - pub fn modified_time(&self) -> Duration { - match self.metadata.modified() { - Ok(system_time) => system_time.duration_since(UNIX_EPOCH).unwrap(), - Err(_) => Duration::new(0, 0), - } + /// This file’s last modified timestamp, if available on this platform. + pub fn modified_time(&self) -> Option { + self.metadata.modified().ok() } - /// This file’s last changed timestamp. - pub fn changed_time(&self) -> Duration { - Duration::new(self.metadata.ctime() as u64, self.metadata.ctime_nsec() as u32) + /// This file’s last changed timestamp, if available on this platform. + pub fn changed_time(&self) -> Option { + let (mut sec, mut nsec) = (self.metadata.ctime(), self.metadata.ctime_nsec()); + + Some( + if sec < 0 { + if nsec > 0 { + sec += 1; + nsec = nsec - 1_000_000_000; + } + UNIX_EPOCH - Duration::new(sec.abs() as u64, nsec.abs() as u32) + } else { + UNIX_EPOCH + Duration::new(sec as u64, nsec as u32) + } + ) } - /// This file’s last accessed timestamp. - /// If the file's time is invalid, assume it was accessed today - pub fn accessed_time(&self) -> Duration { - match self.metadata.accessed() { - Ok(system_time) => system_time.duration_since(UNIX_EPOCH).unwrap(), - Err(_) => Duration::new(0, 0), - } + /// This file’s last accessed timestamp, if available on this platform. + pub fn accessed_time(&self) -> Option { + self.metadata.accessed().ok() } - /// This file’s created timestamp. - /// If the file's time is invalid, assume it was created today - pub fn created_time(&self) -> Duration { - match self.metadata.created() { - Ok(system_time) => system_time.duration_since(UNIX_EPOCH).unwrap(), - Err(_) => Duration::new(0, 0), - } + /// This file’s created timestamp, if available on this platform. + pub fn created_time(&self) -> Option { + self.metadata.created().ok() } /// This file’s ‘type’. diff --git a/src/output/render/times.rs b/src/output/render/times.rs index c5c6b3a..135be35 100644 --- a/src/output/render/times.rs +++ b/src/output/render/times.rs @@ -11,18 +11,21 @@ pub trait Render { format: &TimeFormat) -> TextCell; } -impl Render for std::time::Duration { +impl Render for Option { fn render(self, style: Style, tz: &Option, format: &TimeFormat) -> TextCell { - if let Some(ref tz) = *tz { - let datestamp = format.format_zoned(self, tz); - TextCell::paint(style, datestamp) - } - else { - let datestamp = format.format_local(self); - TextCell::paint(style, datestamp) - } + let datestamp = if let Some(time) = self { + if let Some(ref tz) = tz { + format.format_zoned(time, tz) + } else { + format.format_local(time) + } + } else { + String::from("-") + }; + + TextCell::paint(style, datestamp) } } diff --git a/src/output/time.rs b/src/output/time.rs index 49b6930..b168c6b 100644 --- a/src/output/time.rs +++ b/src/output/time.rs @@ -1,6 +1,6 @@ //! Timestamp formatting. -use std::time::Duration; +use std::time::{SystemTime, UNIX_EPOCH}; use datetime::{LocalDateTime, TimeZone, DatePiece, TimePiece}; use datetime::fmt::DateFormat; @@ -51,7 +51,7 @@ pub enum TimeFormat { // timestamps are separate types. impl TimeFormat { - pub fn format_local(&self, time: Duration) -> String { + pub fn format_local(&self, time: SystemTime) -> String { match *self { TimeFormat::DefaultFormat(ref fmt) => fmt.format_local(time), TimeFormat::ISOFormat(ref iso) => iso.format_local(time), @@ -60,7 +60,7 @@ impl TimeFormat { } } - pub fn format_zoned(&self, time: Duration, zone: &TimeZone) -> String { + pub fn format_zoned(&self, time: SystemTime, zone: &TimeZone) -> String { match *self { TimeFormat::DefaultFormat(ref fmt) => fmt.format_zoned(time, zone), TimeFormat::ISOFormat(ref iso) => iso.format_zoned(time, zone), @@ -146,11 +146,8 @@ impl DefaultFormat { } #[allow(trivial_numeric_casts)] - fn format_local(&self, time: Duration) -> String { - if time.as_nanos() == 0 { - return "-".to_string(); - } - let date = LocalDateTime::at(time.as_secs() as i64); + fn format_local(&self, time: SystemTime) -> String { + let date = LocalDateTime::at(systemtime_epoch(time)); if self.is_recent(date) { format!("{:2} {} {:02}:{:02}", @@ -163,12 +160,8 @@ impl DefaultFormat { } #[allow(trivial_numeric_casts)] - fn format_zoned(&self, time: Duration, zone: &TimeZone) -> String { - if time.as_nanos() == 0 { - return "-".to_string(); - } - - let date = zone.to_zoned(LocalDateTime::at(time.as_secs() as i64)); + fn format_zoned(&self, time: SystemTime, zone: &TimeZone) -> String { + let date = zone.to_zoned(LocalDateTime::at(systemtime_epoch(time))); if self.is_recent(date) { format!("{:2} {} {:02}:{:02}", @@ -181,19 +174,45 @@ impl DefaultFormat { } } +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 + } + }) +} #[allow(trivial_numeric_casts)] -fn long_local(time: Duration) -> String { - let date = LocalDateTime::at(time.as_secs() as i64); +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()) } #[allow(trivial_numeric_casts)] -fn long_zoned(time: Duration, zone: &TimeZone) -> String { - let date = zone.to_zoned(LocalDateTime::at(time.as_secs() as i64)); +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()) @@ -201,23 +220,23 @@ fn long_zoned(time: Duration, zone: &TimeZone) -> String { #[allow(trivial_numeric_casts)] -fn full_local(time: Duration) -> String { - let date = LocalDateTime::at(time.as_secs() as i64); +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(), time.subsec_nanos()) + date.hour(), date.minute(), date.second(), systemtime_nanos(time)) } #[allow(trivial_numeric_casts)] -fn full_zoned(time: Duration, zone: &TimeZone) -> String { +fn full_zoned(time: SystemTime, zone: &TimeZone) -> String { use datetime::Offset; - let local = LocalDateTime::at(time.as_secs() as i64); + 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(), time.subsec_nanos(), + date.hour(), date.minute(), date.second(), systemtime_nanos(time), offset.hours(), offset.minutes().abs()) } @@ -244,8 +263,8 @@ impl ISOFormat { } #[allow(trivial_numeric_casts)] - fn format_local(&self, time: Duration) -> String { - let date = LocalDateTime::at(time.as_secs() as i64); + fn format_local(&self, time: SystemTime) -> String { + let date = LocalDateTime::at(systemtime_epoch(time)); if self.is_recent(date) { format!("{:02}-{:02} {:02}:{:02}", @@ -259,8 +278,8 @@ impl ISOFormat { } #[allow(trivial_numeric_casts)] - fn format_zoned(&self, time: Duration, zone: &TimeZone) -> String { - let date = zone.to_zoned(LocalDateTime::at(time.as_secs() as i64)); + fn format_zoned(&self, time: SystemTime, zone: &TimeZone) -> String { + let date = zone.to_zoned(LocalDateTime::at(systemtime_epoch(time))); if self.is_recent(date) { format!("{:02}-{:02} {:02}:{:02}",