use serde::{Deserialize, Serialize};
use std::{
cmp::Ordering,
convert::TryFrom,
time::{SystemTime, UNIX_EPOCH},
};
use time::OffsetDateTime;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Timestamp {
pub sec: i64,
pub nsec: u32,
}
impl Timestamp {
pub fn new(sec: i64, nsec: u32) -> Result<Self, &'static str> {
if sec > 0 && (sec < i64::MAX / 1_000_000_001) && nsec < 1_000_000_000 {
Ok(Self { sec, nsec })
} else {
Err(INVALID_DATETIME)
}
}
pub fn now() -> Timestamp {
SystemTime::now().into()
}
pub fn as_nanos(&self) -> u128 {
(self.sec as u128 * 1_000_000_000) + self.nsec as u128
}
}
impl Default for Timestamp {
fn default() -> Timestamp {
Self::now()
}
}
impl Ord for Timestamp {
fn cmp(&self, other: &Self) -> Ordering {
match self.sec.cmp(&other.sec) {
Ordering::Equal => self.nsec.cmp(&other.nsec),
ord => ord,
}
}
}
impl PartialOrd for Timestamp {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
pub const INVALID_DATETIME: &str = "Invalid DateTime";
impl TryFrom<Timestamp> for OffsetDateTime {
type Error = &'static str;
fn try_from(ts: Timestamp) -> Result<OffsetDateTime, Self::Error> {
Ok(
OffsetDateTime::from_unix_timestamp(ts.sec).map_err(|_| INVALID_DATETIME)?
+ std::time::Duration::from_nanos(ts.nsec as u64),
)
}
}
impl From<OffsetDateTime> for Timestamp {
fn from(dt: OffsetDateTime) -> Timestamp {
const NANOSECONDS_PER_SECOND: i128 = 1_000_000_000;
let nanos = dt.unix_timestamp_nanos();
Timestamp {
sec: (nanos / NANOSECONDS_PER_SECOND) as i64,
nsec: (nanos % NANOSECONDS_PER_SECOND) as u32,
}
}
}
impl From<SystemTime> for Timestamp {
fn from(st: SystemTime) -> Timestamp {
let d = st
.duration_since(UNIX_EPOCH)
.expect("system time before Unix epoch");
Timestamp {
sec: d.as_secs() as i64,
nsec: d.subsec_nanos(),
}
}
}
impl TryInto<SystemTime> for Timestamp {
type Error = &'static str;
fn try_into(self) -> Result<SystemTime, Self::Error> {
use std::time::Duration;
let sys_now = SystemTime::now();
let now = Self::from(sys_now).as_nanos();
let then = self.as_nanos();
if now >= then {
let delta_past = now - then;
sys_now
.checked_sub(Duration::from_nanos(delta_past as u64))
.ok_or(INVALID_DATETIME)
} else {
let delta_fut = then - now; if delta_fut > i64::MAX as u128 {
Err(INVALID_DATETIME)
} else {
sys_now
.checked_add(Duration::from_nanos(delta_fut as u64))
.ok_or(INVALID_DATETIME)
}
}
}
}
#[test]
fn timestamp_updates() {
let now = Timestamp::now();
std::thread::sleep(std::time::Duration::from_nanos(5_000));
let then = Timestamp::now();
assert!(then.sec > now.sec || (then.sec == now.sec && then.nsec > now.nsec));
}
#[test]
fn timestamp_to_datetime() {
use time::OffsetDateTime;
let start: Timestamp = Timestamp { sec: 2_000_000_000, nsec: 100_000 };
let dt: OffsetDateTime = OffsetDateTime::try_from(start).unwrap();
let next: Timestamp = dt.into();
assert_eq!(&start.sec, &next.sec);
assert_eq!(&start.nsec, &next.nsec);
}
#[test]
fn timestamp_system_time() {
let st = SystemTime::now()
.checked_sub(std::time::Duration::from_secs(86_400 * 365 * 5))
.unwrap();
let t = Timestamp::from(st);
assert_eq!(t.try_into(), Ok(st));
let st = SystemTime::now()
.checked_add(std::time::Duration::from_secs(86_400 * 365))
.unwrap();
let t = Timestamp::from(st);
let st_check: SystemTime = t.try_into().unwrap();
assert_eq!(st_check, st);
}
#[test]
fn timestamp_default() {
let t1 = Timestamp::default();
let t2 = Timestamp::now();
assert!(t1 <= t2);
assert!(t1.sec > 1600000000);
assert!(t1.sec < 3000000000);
assert!(t2.sec > 1600000000);
assert!(t2.sec < 3000000000);
}
#[test]
fn timestamp_ordering() {
let t1 = Timestamp { sec: 100, nsec: 100 };
let t2 = Timestamp { sec: 100, nsec: 100 };
assert_eq!(t1, t2);
let t3 = Timestamp { sec: 99, nsec: 400 };
assert!(t1 > t3);
let t3 = Timestamp { sec: 101, nsec: 40 };
assert!(t1 < t3);
let t4 = Timestamp { sec: 100, nsec: 400 };
assert!(t1 < t4);
let t4 = Timestamp { sec: 100, nsec: 40 };
assert!(t1 > t4);
assert_ne!(t1, t4);
}