rc_zip/parse/
date_time.rs

1use chrono::{
2    offset::{LocalResult, TimeZone, Utc},
3    DateTime, Timelike,
4};
5use ownable::{IntoOwned, ToOwned};
6use std::fmt;
7use winnow::{
8    binary::{le_u16, le_u64},
9    seq, PResult, Parser, Partial,
10};
11
12/// A timestamp in MS-DOS format
13///
14/// Represents dates from year 1980 to 2180, with 2 second precision.
15#[derive(Clone, Copy, Eq, PartialEq, IntoOwned, ToOwned)]
16pub struct MsdosTimestamp {
17    /// Time in 2-second intervals
18    pub time: u16,
19
20    /// Date in MS-DOS format, cf. <https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-dosdatetimetofiletime>
21    pub date: u16,
22}
23
24impl fmt::Debug for MsdosTimestamp {
25    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
26        match self.to_datetime() {
27            Some(dt) => write!(f, "MsdosTimestamp({})", dt),
28            None => write!(f, "MsdosTimestamp(?)"),
29        }
30    }
31}
32
33impl MsdosTimestamp {
34    /// Parser for MS-DOS timestamps
35    pub fn parser(i: &mut Partial<&'_ [u8]>) -> PResult<Self> {
36        seq! {Self {
37            time: le_u16,
38            date: le_u16,
39        }}
40        .parse_next(i)
41    }
42
43    /// Attempts to convert to a chrono UTC date time
44    pub fn to_datetime(&self) -> Option<DateTime<Utc>> {
45        // see https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-dosdatetimetofiletime
46        let res = {
47            // bits 0-4: day of the month (1-31)
48            let d = (self.date & 0b1_1111) as u32;
49            // bits 5-8: month (1 = january, 2 = february and so on)
50            let m = ((self.date >> 5) & 0b1111) as u32;
51            // bits 9-15: year offset from 1980
52            let y = ((self.date >> 9) + 1980) as i32;
53            Utc.with_ymd_and_hms(y, m, d, 0, 0, 0)
54        };
55
56        let date = match res {
57            LocalResult::Single(date) => date,
58            _ => return None,
59        };
60
61        // bits 0-4: second divided by 2
62        let s = (self.time & 0b1_1111) as u32 * 2;
63        // bits 5-10: minute (0-59)
64        let m = (self.time >> 5 & 0b11_1111) as u32;
65        // bits 11-15: hour (0-23 on a 24-hour clock)
66        let h = (self.time >> 11) as u32;
67        date.with_hour(h)?.with_minute(m)?.with_second(s)
68    }
69}
70
71/// A timestamp in NTFS format.
72#[derive(Clone, Copy, Eq, PartialEq)]
73pub struct NtfsTimestamp {
74    /// Timestamp in 100ns intervals since 1601-01-01 00:00:00 UTC
75    pub timestamp: u64,
76}
77
78impl fmt::Debug for NtfsTimestamp {
79    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
80        match self.to_datetime() {
81            Some(dt) => write!(f, "NtfsTimestamp({})", dt),
82            None => write!(f, "NtfsTimestamp(?)"),
83        }
84    }
85}
86
87impl NtfsTimestamp {
88    /// Parse an MS-DOS timestamp from a byte slice
89    pub fn parser(i: &mut Partial<&'_ [u8]>) -> PResult<Self> {
90        le_u64.map(|timestamp| Self { timestamp }).parse_next(i)
91    }
92
93    /// Attempts to convert to a chrono UTC date time
94    pub fn to_datetime(&self) -> Option<DateTime<Utc>> {
95        // windows timestamp resolution
96        let ticks_per_second = 10_000_000;
97        let secs = (self.timestamp / ticks_per_second) as i64;
98        let nsecs = ((self.timestamp % ticks_per_second) * 100) as u32;
99        let epoch = Utc.with_ymd_and_hms(1601, 1, 1, 0, 0, 0).single()?;
100        match Utc.timestamp_opt(epoch.timestamp() + secs, nsecs) {
101            LocalResult::Single(date) => Some(date),
102            _ => None,
103        }
104    }
105}
106
107pub(crate) fn zero_datetime() -> chrono::DateTime<chrono::offset::Utc> {
108    chrono::DateTime::<Utc>::from_timestamp_millis(0).unwrap()
109}