use std::time::{SystemTime, UNIX_EPOCH};
pub const EPICS_EPOCH_OFFSET: u64 = 631_152_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct EpicsTimestamp {
pub sec: u32,
pub nsec: u32,
}
impl EpicsTimestamp {
pub fn now() -> Self {
Self::from(SystemTime::now())
}
pub fn as_f64(&self) -> f64 {
self.sec as f64 + self.nsec as f64 * 1e-9
}
}
impl EpicsTimestamp {
pub fn to_system_time(&self) -> SystemTime {
let unix_secs = self.sec as u64 + EPICS_EPOCH_OFFSET;
UNIX_EPOCH + std::time::Duration::new(unix_secs, self.nsec)
}
}
impl From<SystemTime> for EpicsTimestamp {
fn from(st: SystemTime) -> Self {
match st.duration_since(UNIX_EPOCH) {
Ok(d) => Self {
sec: d.as_secs().wrapping_sub(EPICS_EPOCH_OFFSET) as u32,
nsec: d.subsec_nanos(),
},
Err(_) => Self::default(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_now_nonzero() {
let ts = EpicsTimestamp::now();
assert!(ts.sec > 0);
}
#[test]
fn test_as_f64() {
let ts = EpicsTimestamp {
sec: 100,
nsec: 500_000_000,
};
assert!((ts.as_f64() - 100.5).abs() < 1e-9);
}
#[test]
fn test_from_system_time() {
use std::time::Duration;
let st = UNIX_EPOCH + Duration::from_secs(EPICS_EPOCH_OFFSET + 1000);
let ts = EpicsTimestamp::from(st);
assert_eq!(ts.sec, 1000);
assert_eq!(ts.nsec, 0);
}
#[test]
fn pre_1990_wraps_exactly_as_c_wraps_it() {
use std::time::Duration;
let st = UNIX_EPOCH + Duration::from_secs(EPICS_EPOCH_OFFSET - 1);
assert_eq!(EpicsTimestamp::from(st).sec, u32::MAX);
}
#[test]
fn the_epics_epoch_itself_is_still_zero() {
use std::time::Duration;
let st = UNIX_EPOCH + Duration::from_secs(EPICS_EPOCH_OFFSET);
assert_eq!(EpicsTimestamp::from(st).sec, 0);
}
#[test]
fn the_range_ends_wrap_and_do_not_pin() {
use std::time::Duration;
let last = UNIX_EPOCH + Duration::from_secs(EPICS_EPOCH_OFFSET + u32::MAX as u64);
assert_eq!(EpicsTimestamp::from(last).sec, u32::MAX);
let past = UNIX_EPOCH + Duration::from_secs(EPICS_EPOCH_OFFSET + u32::MAX as u64 + 1);
assert_eq!(EpicsTimestamp::from(past).sec, 0);
}
#[test]
fn test_default_is_zero() {
let ts = EpicsTimestamp::default();
assert_eq!(ts.sec, 0);
assert_eq!(ts.nsec, 0);
}
}