Skip to content

Commit bc830b9

Browse files
committed
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.
1 parent 78ba0b8 commit bc830b9

File tree

3 files changed

+48
-45
lines changed

3 files changed

+48
-45
lines changed

src/fs/file.rs

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::io::Error as IOError;
44
use std::io::Result as IOResult;
55
use std::os::unix::fs::{MetadataExt, PermissionsExt, FileTypeExt};
66
use std::path::{Path, PathBuf};
7-
use std::time::{UNIX_EPOCH, Duration};
7+
use std::time::{SystemTime, UNIX_EPOCH};
88

99
use log::{debug, error};
1010

@@ -326,35 +326,26 @@ impl<'dir> File<'dir> {
326326
}
327327

328328
/// This file’s last modified timestamp.
329-
/// If the file's time is invalid, assume it was modified today
330-
pub fn modified_time(&self) -> Duration {
331-
match self.metadata.modified() {
332-
Ok(system_time) => system_time.duration_since(UNIX_EPOCH).unwrap(),
333-
Err(_) => Duration::new(0, 0),
334-
}
329+
/// If the file's time is invalid, assume it was modified at the epoch
330+
pub fn modified_time(&self) -> SystemTime {
331+
self.metadata.modified().unwrap_or(UNIX_EPOCH)
335332
}
336333

337334
/// This file’s last changed timestamp.
338-
pub fn changed_time(&self) -> Duration {
339-
Duration::new(self.metadata.ctime() as u64, self.metadata.ctime_nsec() as u32)
335+
pub fn changed_time(&self) -> SystemTime {
336+
self.metadata.modified().unwrap_or(UNIX_EPOCH)
340337
}
341338

342339
/// This file’s last accessed timestamp.
343-
/// If the file's time is invalid, assume it was accessed today
344-
pub fn accessed_time(&self) -> Duration {
345-
match self.metadata.accessed() {
346-
Ok(system_time) => system_time.duration_since(UNIX_EPOCH).unwrap(),
347-
Err(_) => Duration::new(0, 0),
348-
}
340+
/// If the file's time is invalid, assume it was accessed at the epoch
341+
pub fn accessed_time(&self) -> SystemTime {
342+
self.metadata.accessed().unwrap_or(UNIX_EPOCH)
349343
}
350344

351345
/// This file’s created timestamp.
352-
/// If the file's time is invalid, assume it was created today
353-
pub fn created_time(&self) -> Duration {
354-
match self.metadata.created() {
355-
Ok(system_time) => system_time.duration_since(UNIX_EPOCH).unwrap(),
356-
Err(_) => Duration::new(0, 0),
357-
}
346+
/// If the file's time is invalid, assume it was created at the epoch
347+
pub fn created_time(&self) -> SystemTime {
348+
self.metadata.created().unwrap_or(UNIX_EPOCH)
358349
}
359350

360351
/// This file’s ‘type’.

src/output/render/times.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub trait Render {
1111
format: &TimeFormat) -> TextCell;
1212
}
1313

14-
impl Render for std::time::Duration {
14+
impl Render for std::time::SystemTime {
1515
fn render(self, style: Style,
1616
tz: &Option<TimeZone>,
1717
format: &TimeFormat) -> TextCell {

src/output/time.rs

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Timestamp formatting.
22
3-
use std::time::Duration;
3+
use std::time::{SystemTime, UNIX_EPOCH};
44

55
use datetime::{LocalDateTime, TimeZone, DatePiece, TimePiece};
66
use datetime::fmt::DateFormat;
@@ -51,7 +51,7 @@ pub enum TimeFormat {
5151
// timestamps are separate types.
5252

5353
impl TimeFormat {
54-
pub fn format_local(&self, time: Duration) -> String {
54+
pub fn format_local(&self, time: SystemTime) -> String {
5555
match *self {
5656
TimeFormat::DefaultFormat(ref fmt) => fmt.format_local(time),
5757
TimeFormat::ISOFormat(ref iso) => iso.format_local(time),
@@ -60,7 +60,7 @@ impl TimeFormat {
6060
}
6161
}
6262

63-
pub fn format_zoned(&self, time: Duration, zone: &TimeZone) -> String {
63+
pub fn format_zoned(&self, time: SystemTime, zone: &TimeZone) -> String {
6464
match *self {
6565
TimeFormat::DefaultFormat(ref fmt) => fmt.format_zoned(time, zone),
6666
TimeFormat::ISOFormat(ref iso) => iso.format_zoned(time, zone),
@@ -146,11 +146,11 @@ impl DefaultFormat {
146146
}
147147

148148
#[allow(trivial_numeric_casts)]
149-
fn format_local(&self, time: Duration) -> String {
150-
if time.as_nanos() == 0 {
149+
fn format_local(&self, time: SystemTime) -> String {
150+
if time == UNIX_EPOCH {
151151
return "-".to_string();
152152
}
153-
let date = LocalDateTime::at(time.as_secs() as i64);
153+
let date = LocalDateTime::at(systemtime_epoch(time));
154154

155155
if self.is_recent(date) {
156156
format!("{:2} {} {:02}:{:02}",
@@ -163,12 +163,12 @@ impl DefaultFormat {
163163
}
164164

165165
#[allow(trivial_numeric_casts)]
166-
fn format_zoned(&self, time: Duration, zone: &TimeZone) -> String {
167-
if time.as_nanos() == 0 {
166+
fn format_zoned(&self, time: SystemTime, zone: &TimeZone) -> String {
167+
if time == UNIX_EPOCH {
168168
return "-".to_string();
169169
}
170170

171-
let date = zone.to_zoned(LocalDateTime::at(time.as_secs() as i64));
171+
let date = zone.to_zoned(LocalDateTime::at(systemtime_epoch(time)));
172172

173173
if self.is_recent(date) {
174174
format!("{:2} {} {:02}:{:02}",
@@ -181,43 +181,55 @@ impl DefaultFormat {
181181
}
182182
}
183183

184+
fn systemtime_epoch(time: SystemTime) -> i64 {
185+
time
186+
.duration_since(UNIX_EPOCH)
187+
.map(|t| t.as_secs() as i64)
188+
.unwrap_or_else(|e| -(e.duration().as_secs() as i64))
189+
}
184190

191+
fn systemtime_nanos(time: SystemTime) -> u32 {
192+
time
193+
.duration_since(UNIX_EPOCH)
194+
.map(|t| t.subsec_nanos())
195+
.unwrap_or_else(|e| e.duration().subsec_nanos())
196+
}
185197

186198
#[allow(trivial_numeric_casts)]
187-
fn long_local(time: Duration) -> String {
188-
let date = LocalDateTime::at(time.as_secs() as i64);
199+
fn long_local(time: SystemTime) -> String {
200+
let date = LocalDateTime::at(systemtime_epoch(time));
189201
format!("{:04}-{:02}-{:02} {:02}:{:02}",
190202
date.year(), date.month() as usize, date.day(),
191203
date.hour(), date.minute())
192204
}
193205

194206
#[allow(trivial_numeric_casts)]
195-
fn long_zoned(time: Duration, zone: &TimeZone) -> String {
196-
let date = zone.to_zoned(LocalDateTime::at(time.as_secs() as i64));
207+
fn long_zoned(time: SystemTime, zone: &TimeZone) -> String {
208+
let date = zone.to_zoned(LocalDateTime::at(systemtime_epoch(time)));
197209
format!("{:04}-{:02}-{:02} {:02}:{:02}",
198210
date.year(), date.month() as usize, date.day(),
199211
date.hour(), date.minute())
200212
}
201213

202214

203215
#[allow(trivial_numeric_casts)]
204-
fn full_local(time: Duration) -> String {
205-
let date = LocalDateTime::at(time.as_secs() as i64);
216+
fn full_local(time: SystemTime) -> String {
217+
let date = LocalDateTime::at(systemtime_epoch(time));
206218
format!("{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:09}",
207219
date.year(), date.month() as usize, date.day(),
208-
date.hour(), date.minute(), date.second(), time.subsec_nanos())
220+
date.hour(), date.minute(), date.second(), systemtime_nanos(time))
209221
}
210222

211223
#[allow(trivial_numeric_casts)]
212-
fn full_zoned(time: Duration, zone: &TimeZone) -> String {
224+
fn full_zoned(time: SystemTime, zone: &TimeZone) -> String {
213225
use datetime::Offset;
214226

215-
let local = LocalDateTime::at(time.as_secs() as i64);
227+
let local = LocalDateTime::at(systemtime_epoch(time));
216228
let date = zone.to_zoned(local);
217229
let offset = Offset::of_seconds(zone.offset(local) as i32).expect("Offset out of range");
218230
format!("{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:09} {:+03}{:02}",
219231
date.year(), date.month() as usize, date.day(),
220-
date.hour(), date.minute(), date.second(), time.subsec_nanos(),
232+
date.hour(), date.minute(), date.second(), systemtime_nanos(time),
221233
offset.hours(), offset.minutes().abs())
222234
}
223235

@@ -244,8 +256,8 @@ impl ISOFormat {
244256
}
245257

246258
#[allow(trivial_numeric_casts)]
247-
fn format_local(&self, time: Duration) -> String {
248-
let date = LocalDateTime::at(time.as_secs() as i64);
259+
fn format_local(&self, time: SystemTime) -> String {
260+
let date = LocalDateTime::at(systemtime_epoch(time));
249261

250262
if self.is_recent(date) {
251263
format!("{:02}-{:02} {:02}:{:02}",
@@ -259,8 +271,8 @@ impl ISOFormat {
259271
}
260272

261273
#[allow(trivial_numeric_casts)]
262-
fn format_zoned(&self, time: Duration, zone: &TimeZone) -> String {
263-
let date = zone.to_zoned(LocalDateTime::at(time.as_secs() as i64));
274+
fn format_zoned(&self, time: SystemTime, zone: &TimeZone) -> String {
275+
let date = zone.to_zoned(LocalDateTime::at(systemtime_epoch(time)));
264276

265277
if self.is_recent(date) {
266278
format!("{:02}-{:02} {:02}:{:02}",

0 commit comments

Comments
 (0)