1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
use std::time::{Duration, SystemTime, UNIX_EPOCH};

//===========================================================================//

/// The CFB timestamp value for the Unix epoch (Jan 1, 1970 UTC).
const UNIX_EPOCH_TIMESTAMP: u64 = 116444736000000000;

/// Returns the current time as a CFB file timestamp (the number of
/// 100-nanosecond intervals since January 1, 1601 UTC).
pub fn current_timestamp() -> u64 {
    timestamp_from_system_time(SystemTime::now())
}

/// Converts a local `SystemTime` to a CFB file timestamp.
fn timestamp_from_system_time(system_time: SystemTime) -> u64 {
    match system_time.duration_since(UNIX_EPOCH) {
        Ok(duration) => {
            let delta = duration_to_timestamp_delta(duration);
            UNIX_EPOCH_TIMESTAMP.saturating_add(delta)
        }
        Err(err) => {
            let delta = duration_to_timestamp_delta(err.duration());
            UNIX_EPOCH_TIMESTAMP.saturating_sub(delta)
        }
    }
}

/// Converts a CFB file timestamp to a local `SystemTime`.
pub fn system_time_from_timestamp(timestamp: u64) -> SystemTime {
    // The maximum range of SystemTime varies by system, and some systems
    // (e.g. 32-bit Linux) can't represent, say, a zero CFB timestamp.  So we
    // center our calculations around UNIX_EPOCH (the one value we can be sure
    // that SystemTime can represent), and use checked_add and checked_sub to
    // avoid panicking on overflow.
    //
    // TODO: If SystemTime ever gains saturating_add and saturing_sub (see
    // https://github.com/rust-lang/rust/issues/71224) we should use those
    // instead.
    let system_time = if timestamp >= UNIX_EPOCH_TIMESTAMP {
        UNIX_EPOCH.checked_add(timestamp_delta_to_duration(
            timestamp - UNIX_EPOCH_TIMESTAMP,
        ))
    } else {
        UNIX_EPOCH.checked_sub(timestamp_delta_to_duration(
            UNIX_EPOCH_TIMESTAMP - timestamp,
        ))
    };
    // If overflow does occur, just return UNIX_EPOCH; this will be totally
    // wrong, but at least it will allow us to continue reading the CFB file
    // without panicking.
    system_time.unwrap_or(UNIX_EPOCH)
}

fn duration_to_timestamp_delta(duration: Duration) -> u64 {
    duration
        .as_secs()
        .saturating_mul(10_000_000)
        .saturating_add((duration.subsec_nanos() / 100) as u64)
}

fn timestamp_delta_to_duration(delta: u64) -> Duration {
    Duration::new(delta / 10_000_000, (delta % 10_000_000) as u32 * 100)
}

//===========================================================================//

#[cfg(test)]
mod tests {
    use super::{
        duration_to_timestamp_delta, system_time_from_timestamp,
        timestamp_delta_to_duration, timestamp_from_system_time,
        UNIX_EPOCH_TIMESTAMP,
    };
    use std::time::{Duration, UNIX_EPOCH};

    #[test]
    fn extreme_timestamp_delta() {
        // The maximum representable CFB timestamp:
        let timestamp = u64::MAX;
        let duration = timestamp_delta_to_duration(timestamp);
        assert_eq!(duration.as_secs(), 1844674407370);
        assert_eq!(duration.subsec_nanos(), 955161500);
        assert_eq!(duration_to_timestamp_delta(duration), timestamp);
    }

    #[test]
    fn extreme_duration() {
        // The maximum representable duration:
        let duration = Duration::new(u64::MAX, 999_999_999);
        // This duration will not fit in a 64-bit CFB timestamp delta.  Rather
        // than overflow, we should return a saturated result.
        assert_eq!(duration_to_timestamp_delta(duration), u64::MAX);
    }

    #[test]
    fn unix_epoch() {
        assert_eq!(
            UNIX_EPOCH_TIMESTAMP,
            timestamp_from_system_time(UNIX_EPOCH)
        );
        assert_eq!(
            system_time_from_timestamp(UNIX_EPOCH_TIMESTAMP),
            UNIX_EPOCH
        );
    }

    #[test]
    fn after_unix_epoch() {
        let sat_18_mar_2017_at_18_46_36_utc =
            UNIX_EPOCH + Duration::from_secs(1489862796);
        assert_eq!(
            timestamp_from_system_time(sat_18_mar_2017_at_18_46_36_utc),
            131343363960000000,
        );
        assert_eq!(
            system_time_from_timestamp(131343363960000000),
            sat_18_mar_2017_at_18_46_36_utc
        );
    }

    #[test]
    fn before_unix_epoch() {
        let sun_20_jul_1969_at_20_17_00_utc =
            UNIX_EPOCH - Duration::from_secs(14182980);
        assert_eq!(
            timestamp_from_system_time(sun_20_jul_1969_at_20_17_00_utc),
            116302906200000000,
        );
        assert_eq!(
            system_time_from_timestamp(116302906200000000),
            sun_20_jul_1969_at_20_17_00_utc
        );
    }

    #[test]
    fn extreme_timestamps() {
        // If the system we're on can't represent these timestamps in a
        // SystemTime, then we'll get incorrect values, but we shouldn't panic.
        let min_time = system_time_from_timestamp(u64::MIN);
        let max_time = system_time_from_timestamp(u64::MAX);
        assert!(min_time <= max_time);
    }
}

//===========================================================================//