Handle timestamps before UNIX_EPOCH (#658)

Instead of returning a Duration since the epoch from file metadata,
which cannot represent times before it, return the SystemTime directly.

Move conversion closer to where it's needed, and perform it infallibly.
This commit is contained in:
Thomas Hurst 2020-05-18 19:52:59 +00:00
parent 78ba0b8973
commit bc830b9158
3 changed files with 48 additions and 45 deletions

View File

@ -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::{SystemTime, UNIX_EPOCH};
use log::{debug, error};
@ -326,35 +326,26 @@ impl<'dir> File<'dir> {
}
/// This files 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),
}
/// If the file's time is invalid, assume it was modified at the epoch
pub fn modified_time(&self) -> SystemTime {
self.metadata.modified().unwrap_or(UNIX_EPOCH)
}
/// This files last changed timestamp.
pub fn changed_time(&self) -> Duration {
Duration::new(self.metadata.ctime() as u64, self.metadata.ctime_nsec() as u32)
pub fn changed_time(&self) -> SystemTime {
self.metadata.modified().unwrap_or(UNIX_EPOCH)
}
/// This files 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),
}
/// If the file's time is invalid, assume it was accessed at the epoch
pub fn accessed_time(&self) -> SystemTime {
self.metadata.accessed().unwrap_or(UNIX_EPOCH)
}
/// This files 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),
}
/// If the file's time is invalid, assume it was created at the epoch
pub fn created_time(&self) -> SystemTime {
self.metadata.created().unwrap_or(UNIX_EPOCH)
}
/// This files type.

View File

@ -11,7 +11,7 @@ pub trait Render {
format: &TimeFormat) -> TextCell;
}
impl Render for std::time::Duration {
impl Render for std::time::SystemTime {
fn render(self, style: Style,
tz: &Option<TimeZone>,
format: &TimeFormat) -> TextCell {

View File

@ -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,11 @@ impl DefaultFormat {
}
#[allow(trivial_numeric_casts)]
fn format_local(&self, time: Duration) -> String {
if time.as_nanos() == 0 {
fn format_local(&self, time: SystemTime) -> String {
if time == UNIX_EPOCH {
return "-".to_string();
}
let date = LocalDateTime::at(time.as_secs() as i64);
let date = LocalDateTime::at(systemtime_epoch(time));
if self.is_recent(date) {
format!("{:2} {} {:02}:{:02}",
@ -163,12 +163,12 @@ impl DefaultFormat {
}
#[allow(trivial_numeric_casts)]
fn format_zoned(&self, time: Duration, zone: &TimeZone) -> String {
if time.as_nanos() == 0 {
fn format_zoned(&self, time: SystemTime, zone: &TimeZone) -> String {
if time == UNIX_EPOCH {
return "-".to_string();
}
let date = zone.to_zoned(LocalDateTime::at(time.as_secs() as i64));
let date = zone.to_zoned(LocalDateTime::at(systemtime_epoch(time)));
if self.is_recent(date) {
format!("{:2} {} {:02}:{:02}",
@ -181,19 +181,31 @@ impl DefaultFormat {
}
}
fn systemtime_epoch(time: SystemTime) -> i64 {
time
.duration_since(UNIX_EPOCH)
.map(|t| t.as_secs() as i64)
.unwrap_or_else(|e| -(e.duration().as_secs() as i64))
}
fn systemtime_nanos(time: SystemTime) -> u32 {
time
.duration_since(UNIX_EPOCH)
.map(|t| t.subsec_nanos())
.unwrap_or_else(|e| e.duration().subsec_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 +213,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 +256,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 +271,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}",