Skip to content

Instantly share code, notes, and snippets.

@ssokolow
Last active July 23, 2018 09:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ssokolow/0da765bebc79254c970d2f1e9e8bd076 to your computer and use it in GitHub Desktop.
Save ssokolow/0da765bebc79254c970d2f1e9e8bd076 to your computer and use it in GitHub Desktop.
Quick semi-hack to provide a means to serde-serialize file modification times prior to UNIX_EPOCH
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use std::panic::catch_unwind;
/// Variation on `std::time::Duration` that sacrifices a bit of precision on the positive
/// side of the in order to provide a nicely serializable form for `SystemTime` values.
///
/// (Workaround for https://github.com/serde-rs/json/issues/464)
#[derive(Eq, PartialEq, Debug, Serialize, Deserialize)]
pub struct Timestamp {
secs_since_epoch: i128,
nanos_since_epoch: u32,
}
impl Timestamp {
/// Construct a `Timestamp` from the provided `SystemTime`
///
/// The use of `i128` inside `Timestamp` should make this operation infallible on all target
/// platforms.
pub fn from_system_time(stime: &SystemTime) -> Timestamp {
match stime.duration_since(UNIX_EPOCH) {
Ok(dur) => Timestamp {
secs_since_epoch: dur.as_secs() as i128,
nanos_since_epoch: dur.subsec_nanos(),
},
Err(err) => {
let dur = err.duration();
Timestamp {
secs_since_epoch: dur.as_secs() as i128 * -1,
nanos_since_epoch: dur.subsec_nanos(),
}
}
}
}
/// Construct a `SystemTime` corresponding to the timestamp or `None` if the conversion fails.
///
/// **Note:** Some failure states are returned via `std::panic::catch_unwind` and will turn
/// into panics if not using unwinding-based panics.
#[allow(dead_code)] // Used in round-trip tests but not yet by main()
pub fn to_system_time(&self) -> Option<SystemTime> {
catch_unwind(|| {
let duration = match self.secs_since_epoch.checked_abs() {
Some(secs) => Duration::new(secs as u64, self.nanos_since_epoch),
None => Duration::new(i64::max_value() as u64 + 1, self.nanos_since_epoch),
};
if self.secs_since_epoch < 0 {
UNIX_EPOCH - duration
} else {
UNIX_EPOCH + duration
}
}).ok() // TODO: Return a proper Result
}
}
#[cfg(test)]
mod tests {
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use super::Timestamp;
/// Test that SystemTime->Timestamp conversion works properly
#[test]
fn test_from_system_time() {
for (s, ns, val) in &[
(-1 << 40, 1, UNIX_EPOCH - Duration::new(1 << 40, 1)), // Outside i32 range
(-1, 1, UNIX_EPOCH - Duration::new(1, 1)), // Negative case (outside u64 range)
(0, 0, UNIX_EPOCH), // Zero case
(1, 1, UNIX_EPOCH + Duration::new(1, 1)), // Positive case
(1 >> 40, 1, UNIX_EPOCH + Duration::new(1 >> 40, 1)) // Outside u32 range
] {
let ts = Timestamp::from_system_time(&val);
assert_eq!(Timestamp { secs_since_epoch: *s, nanos_since_epoch: *ns }, ts);
}
}
/// Test that SystemTime->Timestamp conversion works properly
#[test]
fn test_to_system_time() {
for (s, ns, val) in &[
(-1 << 40, 1, UNIX_EPOCH - Duration::new(1 << 40, 1)), // Outside i32 range
(-1, 1, UNIX_EPOCH - Duration::new(1, 1)), // Negative case (outside u64 range)
(0, 0, UNIX_EPOCH), // Zero case
(1, 1, UNIX_EPOCH + Duration::new(1, 1)), // Positive case
(1 >> 40, 1, UNIX_EPOCH + Duration::new(1 >> 40, 1)) // Outside u32 range
] {
let ts = Timestamp { secs_since_epoch: *s, nanos_since_epoch: *ns };
assert_eq!(ts.to_system_time().unwrap(), *val);
}
}
/// Test that SystemTime->Timestamp->SystemTime conversion results in equal values
#[test]
fn test_sts_round_tripping() {
for val in &[UNIX_EPOCH, SystemTime::now()] {
let ts = Timestamp::from_system_time(&val);
assert_eq!(&ts.to_system_time().unwrap(), val);
}
}
// TODO: Verify proper handling of nanos when the balance of nanos and secs may change
// during conversion.
/// Test that Timestamp->SystemTime->Timestamp conversion results in equal values
#[test]
fn test_tst_round_tripping() {
for val in &[
// Minimum value of naive i64::abs()
(i64::min_value() as i128 + 1, u32::max_value() - 3_300_000_000),
// Basic negative case
(-1, 2), // (Note: abs(-1) != 2 to catch certain potential bugs)
// Zero case
(0, 0),
// Basic positive case
(1, 3), // (Note: abs(1) != 3 to catch certain potential bugs)
(i64::max_value() as i128, 0), // Maximum value of i64
//(i64::max_value() as i128 + 1, 0), // Maximum value of i64
//(u64::max_value() as i128, 0) // Maximum value of u64
] {
let ts = Timestamp {
secs_since_epoch: val.0,
nanos_since_epoch: val.1
};
let st = ts.to_system_time().expect(&format!("{:?}", val));
assert_eq!(Timestamp::from_system_time(&st), ts);
}
}
/// Test that out-of-range conditions don't panic
#[test]
fn test_out_of_range() {
for val in &[
(i64::min_value() as i128, 0), // Minimum value of smart i64::abs()
(i64::max_value() as i128 + 1, 0), // One past maximum value of i64
(u64::max_value() as i128, 0) // Maximum value of u64
] {
let ts = Timestamp {
secs_since_epoch: val.0,
nanos_since_epoch: val.1
};
// Ensure the potential to panic can't be optimized away
println!("{:?}", ts.to_system_time());
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment