1 //! Timestamps for files in Rust
2 //!
3 //! This library provides platform-agnostic inspection of the various timestamps
4 //! present in the standard `fs::Metadata` structure.
5 //!
6 //! # Installation
7 //!
8 //! Add this to your `Cargo.toml`:
9 //!
10 //! ```toml
11 //! [dependencies]
12 //! filetime = "0.1"
13 //! ```
14 //!
15 //! # Usage
16 //!
17 //! ```no_run
18 //! use std::fs;
19 //! use filetime::FileTime;
20 //!
21 //! let metadata = fs::metadata("foo.txt").unwrap();
22 //!
23 //! let mtime = FileTime::from_last_modification_time(&metadata);
24 //! println!("{}", mtime);
25 //!
26 //! let atime = FileTime::from_last_access_time(&metadata);
27 //! assert!(mtime < atime);
28 //!
29 //! // Inspect values that can be interpreted across platforms
30 //! println!("{}", mtime.unix_seconds());
31 //! println!("{}", mtime.nanoseconds());
32 //!
33 //! // Print the platform-specific value of seconds
34 //! println!("{}", mtime.seconds());
35 //! ```
36 
37 use std::fmt;
38 use std::fs;
39 use std::io;
40 use std::path::Path;
41 use std::time::{Duration, SystemTime, UNIX_EPOCH};
42 
43 cfg_if::cfg_if! {
44     if #[cfg(target_os = "redox")] {
45         #[path = "redox.rs"]
46         mod imp;
47     } else if #[cfg(windows)] {
48         #[path = "windows.rs"]
49         mod imp;
50     } else if #[cfg(target_arch = "wasm32")] {
51         #[path = "wasm.rs"]
52         mod imp;
53     } else {
54         #[path = "unix/mod.rs"]
55         mod imp;
56     }
57 }
58 
59 /// A helper structure to represent a timestamp for a file.
60 ///
61 /// The actual value contined within is platform-specific and does not have the
62 /// same meaning across platforms, but comparisons and stringification can be
63 /// significant among the same platform.
64 #[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Copy, Clone, Hash)]
65 pub struct FileTime {
66     seconds: i64,
67     nanos: u32,
68 }
69 
70 impl FileTime {
71     /// Creates a new timestamp representing a 0 time.
72     ///
73     /// Useful for creating the base of a cmp::max chain of times.
zero() -> FileTime74     pub fn zero() -> FileTime {
75         FileTime {
76             seconds: 0,
77             nanos: 0,
78         }
79     }
80 
emulate_second_only_system(self) -> FileTime81     fn emulate_second_only_system(self) -> FileTime {
82         if cfg!(emulate_second_only_system) {
83             FileTime {
84                 seconds: self.seconds,
85                 nanos: 0,
86             }
87         } else {
88             self
89         }
90     }
91 
92     /// Creates a new instance of `FileTime` with a number of seconds and
93     /// nanoseconds relative to the Unix epoch, 1970-01-01T00:00:00Z.
94     ///
95     /// Negative seconds represent times before the Unix epoch, and positive
96     /// values represent times after it. Nanos always count forwards in time.
97     ///
98     /// Note that this is typically the relative point that Unix time stamps are
99     /// from, but on Windows the native time stamp is relative to January 1,
100     /// 1601 so the return value of `seconds` from the returned `FileTime`
101     /// instance may not be the same as that passed in.
from_unix_time(seconds: i64, nanos: u32) -> FileTime102     pub fn from_unix_time(seconds: i64, nanos: u32) -> FileTime {
103         FileTime {
104             seconds: seconds + if cfg!(windows) { 11644473600 } else { 0 },
105             nanos,
106         }
107         .emulate_second_only_system()
108     }
109 
110     /// Creates a new timestamp from the last modification time listed in the
111     /// specified metadata.
112     ///
113     /// The returned value corresponds to the `mtime` field of `stat` on Unix
114     /// platforms and the `ftLastWriteTime` field on Windows platforms.
from_last_modification_time(meta: &fs::Metadata) -> FileTime115     pub fn from_last_modification_time(meta: &fs::Metadata) -> FileTime {
116         imp::from_last_modification_time(meta).emulate_second_only_system()
117     }
118 
119     /// Creates a new timestamp from the last access time listed in the
120     /// specified metadata.
121     ///
122     /// The returned value corresponds to the `atime` field of `stat` on Unix
123     /// platforms and the `ftLastAccessTime` field on Windows platforms.
from_last_access_time(meta: &fs::Metadata) -> FileTime124     pub fn from_last_access_time(meta: &fs::Metadata) -> FileTime {
125         imp::from_last_access_time(meta).emulate_second_only_system()
126     }
127 
128     /// Creates a new timestamp from the creation time listed in the specified
129     /// metadata.
130     ///
131     /// The returned value corresponds to the `birthtime` field of `stat` on
132     /// Unix platforms and the `ftCreationTime` field on Windows platforms. Note
133     /// that not all Unix platforms have this field available and may return
134     /// `None` in some circumstances.
from_creation_time(meta: &fs::Metadata) -> Option<FileTime>135     pub fn from_creation_time(meta: &fs::Metadata) -> Option<FileTime> {
136         imp::from_creation_time(meta).map(|x| x.emulate_second_only_system())
137     }
138 
139     /// Creates a new timestamp from the given SystemTime.
140     ///
141     /// Windows counts file times since 1601-01-01T00:00:00Z, and cannot
142     /// represent times before this, but it's possible to create a SystemTime
143     /// that does. This function will error if passed such a SystemTime.
from_system_time(time: SystemTime) -> FileTime144     pub fn from_system_time(time: SystemTime) -> FileTime {
145         let epoch = if cfg!(windows) {
146             UNIX_EPOCH - Duration::from_secs(11644473600)
147         } else {
148             UNIX_EPOCH
149         };
150 
151         time.duration_since(epoch)
152             .map(|d| FileTime {
153                 seconds: d.as_secs() as i64,
154                 nanos: d.subsec_nanos(),
155             })
156             .unwrap_or_else(|e| {
157                 let until_epoch = e.duration();
158                 let (sec_offset, nanos) = if until_epoch.subsec_nanos() == 0 {
159                     (0, 0)
160                 } else {
161                     (-1, 1_000_000_000 - until_epoch.subsec_nanos())
162                 };
163 
164                 FileTime {
165                     seconds: -1 * until_epoch.as_secs() as i64 + sec_offset,
166                     nanos,
167                 }
168             })
169             .emulate_second_only_system()
170     }
171 
172     /// Returns the whole number of seconds represented by this timestamp.
173     ///
174     /// Note that this value's meaning is **platform specific**. On Unix
175     /// platform time stamps are typically relative to January 1, 1970, but on
176     /// Windows platforms time stamps are relative to January 1, 1601.
seconds(&self) -> i64177     pub fn seconds(&self) -> i64 {
178         self.seconds
179     }
180 
181     /// Returns the whole number of seconds represented by this timestamp,
182     /// relative to the Unix epoch start of January 1, 1970.
183     ///
184     /// Note that this does not return the same value as `seconds` for Windows
185     /// platforms as seconds are relative to a different date there.
unix_seconds(&self) -> i64186     pub fn unix_seconds(&self) -> i64 {
187         self.seconds - if cfg!(windows) { 11644473600 } else { 0 }
188     }
189 
190     /// Returns the nanosecond precision of this timestamp.
191     ///
192     /// The returned value is always less than one billion and represents a
193     /// portion of a second forward from the seconds returned by the `seconds`
194     /// method.
nanoseconds(&self) -> u32195     pub fn nanoseconds(&self) -> u32 {
196         self.nanos
197     }
198 }
199 
200 impl fmt::Display for FileTime {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result201     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
202         write!(f, "{}.{:09}s", self.seconds, self.nanos)
203     }
204 }
205 
206 impl From<SystemTime> for FileTime {
from(time: SystemTime) -> FileTime207     fn from(time: SystemTime) -> FileTime {
208         FileTime::from_system_time(time)
209     }
210 }
211 
212 /// Set the last access and modification times for a file on the filesystem.
213 ///
214 /// This function will set the `atime` and `mtime` metadata fields for a file
215 /// on the local filesystem, returning any error encountered.
set_file_times<P>(p: P, atime: FileTime, mtime: FileTime) -> io::Result<()> where P: AsRef<Path>,216 pub fn set_file_times<P>(p: P, atime: FileTime, mtime: FileTime) -> io::Result<()>
217 where
218     P: AsRef<Path>,
219 {
220     imp::set_file_times(p.as_ref(), atime, mtime)
221 }
222 
223 /// Set the last access and modification times for a file handle.
224 ///
225 /// This function will either or both of  the `atime` and `mtime` metadata
226 /// fields for a file handle , returning any error encountered. If `None` is
227 /// specified then the time won't be updated. If `None` is specified for both
228 /// options then no action is taken.
set_file_handle_times( f: &fs::File, atime: Option<FileTime>, mtime: Option<FileTime>, ) -> io::Result<()>229 pub fn set_file_handle_times(
230     f: &fs::File,
231     atime: Option<FileTime>,
232     mtime: Option<FileTime>,
233 ) -> io::Result<()> {
234     imp::set_file_handle_times(f, atime, mtime)
235 }
236 
237 /// Set the last access and modification times for a file on the filesystem.
238 /// This function does not follow symlink.
239 ///
240 /// This function will set the `atime` and `mtime` metadata fields for a file
241 /// on the local filesystem, returning any error encountered.
set_symlink_file_times<P>(p: P, atime: FileTime, mtime: FileTime) -> io::Result<()> where P: AsRef<Path>,242 pub fn set_symlink_file_times<P>(p: P, atime: FileTime, mtime: FileTime) -> io::Result<()>
243 where
244     P: AsRef<Path>,
245 {
246     imp::set_symlink_file_times(p.as_ref(), atime, mtime)
247 }
248 
249 /// Set the last modification time for a file on the filesystem.
250 ///
251 /// This function will set the `mtime` metadata field for a file on the local
252 /// filesystem, returning any error encountered.
253 ///
254 /// # Platform support
255 ///
256 /// Where supported this will attempt to issue just one syscall to update only
257 /// the `mtime`, but where not supported this may issue one syscall to learn the
258 /// existing `atime` so only the `mtime` can be configured.
set_file_mtime<P>(p: P, mtime: FileTime) -> io::Result<()> where P: AsRef<Path>,259 pub fn set_file_mtime<P>(p: P, mtime: FileTime) -> io::Result<()>
260 where
261     P: AsRef<Path>,
262 {
263     imp::set_file_mtime(p.as_ref(), mtime)
264 }
265 
266 /// Set the last access time for a file on the filesystem.
267 ///
268 /// This function will set the `atime` metadata field for a file on the local
269 /// filesystem, returning any error encountered.
270 ///
271 /// # Platform support
272 ///
273 /// Where supported this will attempt to issue just one syscall to update only
274 /// the `atime`, but where not supported this may issue one syscall to learn the
275 /// existing `mtime` so only the `atime` can be configured.
set_file_atime<P>(p: P, atime: FileTime) -> io::Result<()> where P: AsRef<Path>,276 pub fn set_file_atime<P>(p: P, atime: FileTime) -> io::Result<()>
277 where
278     P: AsRef<Path>,
279 {
280     imp::set_file_atime(p.as_ref(), atime)
281 }
282 
283 #[cfg(test)]
284 mod tests {
285     use super::{set_file_handle_times, set_file_times, set_symlink_file_times, FileTime};
286     use std::fs::{self, File};
287     use std::io;
288     use std::path::Path;
289     use std::time::{Duration, UNIX_EPOCH};
290     use tempdir::TempDir;
291 
292     #[cfg(unix)]
make_symlink<P, Q>(src: P, dst: Q) -> io::Result<()> where P: AsRef<Path>, Q: AsRef<Path>,293     fn make_symlink<P, Q>(src: P, dst: Q) -> io::Result<()>
294     where
295         P: AsRef<Path>,
296         Q: AsRef<Path>,
297     {
298         use std::os::unix::fs::symlink;
299         symlink(src, dst)
300     }
301 
302     #[cfg(windows)]
make_symlink<P, Q>(src: P, dst: Q) -> io::Result<()> where P: AsRef<Path>, Q: AsRef<Path>,303     fn make_symlink<P, Q>(src: P, dst: Q) -> io::Result<()>
304     where
305         P: AsRef<Path>,
306         Q: AsRef<Path>,
307     {
308         use std::os::windows::fs::symlink_file;
309         symlink_file(src, dst)
310     }
311 
312     #[test]
313     #[cfg(windows)]
from_unix_time_test()314     fn from_unix_time_test() {
315         let time = FileTime::from_unix_time(10, 100_000_000);
316         assert_eq!(11644473610, time.seconds);
317         assert_eq!(100_000_000, time.nanos);
318 
319         let time = FileTime::from_unix_time(-10, 100_000_000);
320         assert_eq!(11644473590, time.seconds);
321         assert_eq!(100_000_000, time.nanos);
322 
323         let time = FileTime::from_unix_time(-12_000_000_000, 0);
324         assert_eq!(-355526400, time.seconds);
325         assert_eq!(0, time.nanos);
326     }
327 
328     #[test]
329     #[cfg(not(windows))]
from_unix_time_test()330     fn from_unix_time_test() {
331         let time = FileTime::from_unix_time(10, 100_000_000);
332         assert_eq!(10, time.seconds);
333         assert_eq!(100_000_000, time.nanos);
334 
335         let time = FileTime::from_unix_time(-10, 100_000_000);
336         assert_eq!(-10, time.seconds);
337         assert_eq!(100_000_000, time.nanos);
338 
339         let time = FileTime::from_unix_time(-12_000_000_000, 0);
340         assert_eq!(-12_000_000_000, time.seconds);
341         assert_eq!(0, time.nanos);
342     }
343 
344     #[test]
345     #[cfg(windows)]
from_system_time_test()346     fn from_system_time_test() {
347         let time = FileTime::from_system_time(UNIX_EPOCH + Duration::from_secs(10));
348         assert_eq!(11644473610, time.seconds);
349         assert_eq!(0, time.nanos);
350 
351         let time = FileTime::from_system_time(UNIX_EPOCH - Duration::from_secs(10));
352         assert_eq!(11644473590, time.seconds);
353         assert_eq!(0, time.nanos);
354 
355         let time = FileTime::from_system_time(UNIX_EPOCH - Duration::from_millis(1100));
356         assert_eq!(11644473598, time.seconds);
357         assert_eq!(900_000_000, time.nanos);
358 
359         let time = FileTime::from_system_time(UNIX_EPOCH - Duration::from_secs(12_000_000_000));
360         assert_eq!(-355526400, time.seconds);
361         assert_eq!(0, time.nanos);
362     }
363 
364     #[test]
365     #[cfg(not(windows))]
from_system_time_test()366     fn from_system_time_test() {
367         let time = FileTime::from_system_time(UNIX_EPOCH + Duration::from_secs(10));
368         assert_eq!(10, time.seconds);
369         assert_eq!(0, time.nanos);
370 
371         let time = FileTime::from_system_time(UNIX_EPOCH - Duration::from_secs(10));
372         assert_eq!(-10, time.seconds);
373         assert_eq!(0, time.nanos);
374 
375         let time = FileTime::from_system_time(UNIX_EPOCH - Duration::from_millis(1100));
376         assert_eq!(-2, time.seconds);
377         assert_eq!(900_000_000, time.nanos);
378 
379         let time = FileTime::from_system_time(UNIX_EPOCH - Duration::from_secs(12_000_000));
380         assert_eq!(-12_000_000, time.seconds);
381         assert_eq!(0, time.nanos);
382     }
383 
384     #[test]
set_file_times_test() -> io::Result<()>385     fn set_file_times_test() -> io::Result<()> {
386         let td = TempDir::new("filetime")?;
387         let path = td.path().join("foo.txt");
388         let mut f = File::create(&path)?;
389 
390         let metadata = fs::metadata(&path)?;
391         let mtime = FileTime::from_last_modification_time(&metadata);
392         let atime = FileTime::from_last_access_time(&metadata);
393         set_file_times(&path, atime, mtime)?;
394 
395         let new_mtime = FileTime::from_unix_time(10_000, 0);
396         set_file_times(&path, atime, new_mtime)?;
397 
398         let metadata = fs::metadata(&path)?;
399         let mtime = FileTime::from_last_modification_time(&metadata);
400         assert_eq!(mtime, new_mtime, "modification should be updated");
401 
402         // Update just mtime
403         let new_mtime = FileTime::from_unix_time(20_000, 0);
404         set_file_handle_times(&mut f, None, Some(new_mtime))?;
405         let metadata = f.metadata()?;
406         let mtime = FileTime::from_last_modification_time(&metadata);
407         assert_eq!(mtime, new_mtime, "modification time should be updated");
408         let new_atime = FileTime::from_last_access_time(&metadata);
409         assert_eq!(atime, new_atime, "accessed time should not be updated");
410 
411         // Update just atime
412         let new_atime = FileTime::from_unix_time(30_000, 0);
413         set_file_handle_times(&mut f, Some(new_atime), None)?;
414         let metadata = f.metadata()?;
415         let mtime = FileTime::from_last_modification_time(&metadata);
416         assert_eq!(mtime, new_mtime, "modification time should not be updated");
417         let atime = FileTime::from_last_access_time(&metadata);
418         assert_eq!(atime, new_atime, "accessed time should be updated");
419 
420         let spath = td.path().join("bar.txt");
421         make_symlink(&path, &spath)?;
422         let metadata = fs::symlink_metadata(&spath)?;
423         let smtime = FileTime::from_last_modification_time(&metadata);
424 
425         set_file_times(&spath, atime, mtime)?;
426 
427         let metadata = fs::metadata(&path)?;
428         let cur_mtime = FileTime::from_last_modification_time(&metadata);
429         assert_eq!(mtime, cur_mtime);
430 
431         let metadata = fs::symlink_metadata(&spath)?;
432         let cur_mtime = FileTime::from_last_modification_time(&metadata);
433         assert_eq!(smtime, cur_mtime);
434 
435         set_file_times(&spath, atime, new_mtime)?;
436 
437         let metadata = fs::metadata(&path)?;
438         let mtime = FileTime::from_last_modification_time(&metadata);
439         assert_eq!(mtime, new_mtime);
440 
441         let metadata = fs::symlink_metadata(&spath)?;
442         let mtime = FileTime::from_last_modification_time(&metadata);
443         assert_eq!(mtime, smtime);
444         Ok(())
445     }
446 
447     #[test]
set_file_times_pre_unix_epoch_test()448     fn set_file_times_pre_unix_epoch_test() {
449         let td = TempDir::new("filetime").unwrap();
450         let path = td.path().join("foo.txt");
451         File::create(&path).unwrap();
452 
453         let metadata = fs::metadata(&path).unwrap();
454         let mtime = FileTime::from_last_modification_time(&metadata);
455         let atime = FileTime::from_last_access_time(&metadata);
456         set_file_times(&path, atime, mtime).unwrap();
457 
458         let new_mtime = FileTime::from_unix_time(-10_000, 0);
459         set_file_times(&path, atime, new_mtime).unwrap();
460 
461         let metadata = fs::metadata(&path).unwrap();
462         let mtime = FileTime::from_last_modification_time(&metadata);
463         assert_eq!(mtime, new_mtime);
464     }
465 
466     #[test]
467     #[cfg(windows)]
set_file_times_pre_windows_epoch_test()468     fn set_file_times_pre_windows_epoch_test() {
469         let td = TempDir::new("filetime").unwrap();
470         let path = td.path().join("foo.txt");
471         File::create(&path).unwrap();
472 
473         let metadata = fs::metadata(&path).unwrap();
474         let mtime = FileTime::from_last_modification_time(&metadata);
475         let atime = FileTime::from_last_access_time(&metadata);
476         set_file_times(&path, atime, mtime).unwrap();
477 
478         let new_mtime = FileTime::from_unix_time(-12_000_000_000, 0);
479         assert!(set_file_times(&path, atime, new_mtime).is_err());
480     }
481 
482     #[test]
set_symlink_file_times_test()483     fn set_symlink_file_times_test() {
484         let td = TempDir::new("filetime").unwrap();
485         let path = td.path().join("foo.txt");
486         File::create(&path).unwrap();
487 
488         let metadata = fs::metadata(&path).unwrap();
489         let mtime = FileTime::from_last_modification_time(&metadata);
490         let atime = FileTime::from_last_access_time(&metadata);
491         set_symlink_file_times(&path, atime, mtime).unwrap();
492 
493         let new_mtime = FileTime::from_unix_time(10_000, 0);
494         set_symlink_file_times(&path, atime, new_mtime).unwrap();
495 
496         let metadata = fs::metadata(&path).unwrap();
497         let mtime = FileTime::from_last_modification_time(&metadata);
498         assert_eq!(mtime, new_mtime);
499 
500         let spath = td.path().join("bar.txt");
501         make_symlink(&path, &spath).unwrap();
502 
503         let metadata = fs::symlink_metadata(&spath).unwrap();
504         let smtime = FileTime::from_last_modification_time(&metadata);
505         let satime = FileTime::from_last_access_time(&metadata);
506         set_symlink_file_times(&spath, smtime, satime).unwrap();
507 
508         let metadata = fs::metadata(&path).unwrap();
509         let mtime = FileTime::from_last_modification_time(&metadata);
510         assert_eq!(mtime, new_mtime);
511 
512         let new_smtime = FileTime::from_unix_time(20_000, 0);
513         set_symlink_file_times(&spath, atime, new_smtime).unwrap();
514 
515         let metadata = fs::metadata(&spath).unwrap();
516         let mtime = FileTime::from_last_modification_time(&metadata);
517         assert_eq!(mtime, new_mtime);
518 
519         let metadata = fs::symlink_metadata(&spath).unwrap();
520         let mtime = FileTime::from_last_modification_time(&metadata);
521         assert_eq!(mtime, new_smtime);
522     }
523 
524     #[test]
set_single_time_test()525     fn set_single_time_test() {
526         use super::{set_file_atime, set_file_mtime};
527 
528         let td = TempDir::new("filetime").unwrap();
529         let path = td.path().join("foo.txt");
530         File::create(&path).unwrap();
531 
532         let metadata = fs::metadata(&path).unwrap();
533         let mtime = FileTime::from_last_modification_time(&metadata);
534         let atime = FileTime::from_last_access_time(&metadata);
535         set_file_times(&path, atime, mtime).unwrap();
536 
537         let new_mtime = FileTime::from_unix_time(10_000, 0);
538         set_file_mtime(&path, new_mtime).unwrap();
539 
540         let metadata = fs::metadata(&path).unwrap();
541         let mtime = FileTime::from_last_modification_time(&metadata);
542         assert_eq!(mtime, new_mtime, "modification time should be updated");
543         assert_eq!(
544             atime,
545             FileTime::from_last_access_time(&metadata),
546             "access time should not be updated",
547         );
548 
549         let new_atime = FileTime::from_unix_time(20_000, 0);
550         set_file_atime(&path, new_atime).unwrap();
551 
552         let metadata = fs::metadata(&path).unwrap();
553         let atime = FileTime::from_last_access_time(&metadata);
554         assert_eq!(atime, new_atime, "access time should be updated");
555         assert_eq!(
556             mtime,
557             FileTime::from_last_modification_time(&metadata),
558             "modification time should not be updated"
559         );
560     }
561 }
562