فهرست منبع

Merge pull request #670 from Freaky/fix-pre-epoch-times

Handle timestamps before UNIX_EPOCH (#658)
Benjamin Sago 5 سال پیش
والد
کامیت
4b459631aa
3فایلهای تغییر یافته به همراه84 افزوده شده و 62 حذف شده
  1. 25 25
      src/fs/file.rs
  2. 12 9
      src/output/render/times.rs
  3. 47 28
      src/output/time.rs

+ 25 - 25
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<SystemTime> {
+        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<SystemTime> {
+        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<SystemTime> {
+        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<SystemTime> {
+        self.metadata.created().ok()
     }
 
     /// This file’s ‘type’.

+ 12 - 9
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<std::time::SystemTime> {
     fn render(self, style: Style,
                         tz: &Option<TimeZone>,
                         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)
     }
 }

+ 47 - 28
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}",