Explorar o código

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.
Thomas Hurst %!s(int64=5) %!d(string=hai) anos
pai
achega
bc830b9158
Modificáronse 3 ficheiros con 48 adicións e 45 borrados
  1. 12 21
      src/fs/file.rs
  2. 1 1
      src/output/render/times.rs
  3. 35 23
      src/output/time.rs

+ 12 - 21
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::{SystemTime, UNIX_EPOCH};
 
 use log::{debug, error};
 
@@ -326,35 +326,26 @@ 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),
-        }
+    /// 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 file’s 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 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),
-        }
+    /// 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 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),
-        }
+    /// 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 file’s ‘type’.

+ 1 - 1
src/output/render/times.rs

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

+ 35 - 23
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,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}",