cfb/internal/time.rs
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);
}
}
//===========================================================================//