#![deny(missing_docs)]
#[allow(missing_docs)]
mod error;
#[cfg(feature = "chrono")]
mod human;
#[cfg(feature = "chrono")]
pub use human::*;
use core::ops::{Add, Sub};
use serde::{Deserialize, Serialize};
use std::convert::{TryFrom, TryInto};
pub use crate::error::{TimestampError, TimestampResult};
#[cfg(feature = "chrono")]
pub(crate) use chrono_ext::*;
#[cfg(feature = "chrono")]
mod chrono_ext;
pub const MM: i64 = 1_000_000;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(not(feature = "chrono"), derive(Debug))]
pub struct Timestamp(
pub i64,
);
impl<D: Into<core::time::Duration>> Add<D> for Timestamp {
type Output = TimestampResult<Timestamp>;
fn add(self, rhs: D) -> Self::Output {
self.checked_add(&rhs.into())
.ok_or(TimestampError::Overflow)
}
}
impl<D: Into<core::time::Duration>> Add<D> for &Timestamp {
type Output = TimestampResult<Timestamp>;
fn add(self, rhs: D) -> Self::Output {
self.to_owned() + rhs
}
}
impl<D: Into<core::time::Duration>> Sub<D> for Timestamp {
type Output = TimestampResult<Timestamp>;
fn sub(self, rhs: D) -> Self::Output {
self.checked_sub(&rhs.into())
.ok_or(TimestampError::Overflow)
}
}
impl<D: Into<core::time::Duration>> Sub<D> for &Timestamp {
type Output = TimestampResult<Timestamp>;
fn sub(self, rhs: D) -> Self::Output {
self.to_owned() - rhs
}
}
impl Timestamp {
pub const ZERO: Timestamp = Timestamp(0);
pub const MIN: Timestamp = Timestamp(i64::MIN);
pub const MAX: Timestamp = Timestamp(i64::MAX);
pub const HOLOCHAIN_EPOCH: Timestamp = Timestamp(1640995200000000);
pub fn max() -> Timestamp {
Timestamp(i64::MAX)
}
pub fn from_micros(micros: i64) -> Self {
Self(micros)
}
pub fn as_micros(&self) -> i64 {
self.0
}
pub fn as_millis(&self) -> i64 {
self.0 / 1000
}
pub fn as_seconds_and_nanos(&self) -> (i64, u32) {
let secs = self.0 / MM;
let nsecs = (self.0 % 1_000_000) * 1000;
(secs, nsecs as u32)
}
pub fn checked_add(&self, rhs: &core::time::Duration) -> Option<Timestamp> {
let micros = rhs.as_micros();
if micros <= i64::MAX as u128 {
Some(Self(self.0.checked_add(micros as i64)?))
} else {
None
}
}
pub fn checked_sub(&self, rhs: &core::time::Duration) -> Option<Timestamp> {
let micros = rhs.as_micros();
if micros <= i64::MAX as u128 {
Some(Self(self.0.checked_sub(micros as i64)?))
} else {
None
}
}
pub fn saturating_add(&self, rhs: &core::time::Duration) -> Timestamp {
self.checked_add(rhs).unwrap_or(Self::MAX)
}
pub fn saturating_sub(&self, rhs: &core::time::Duration) -> Timestamp {
self.checked_sub(rhs).unwrap_or(Self::MIN)
}
pub fn saturating_from_dur(duration: &core::time::Duration) -> Self {
Timestamp(std::cmp::min(duration.as_micros(), i64::MAX as u128) as i64)
}
}
impl TryFrom<core::time::Duration> for Timestamp {
type Error = error::TimestampError;
fn try_from(value: core::time::Duration) -> Result<Self, Self::Error> {
Ok(Timestamp(
value
.as_micros()
.try_into()
.map_err(|_| error::TimestampError::Overflow)?,
))
}
}
#[cfg(feature = "rusqlite")]
impl rusqlite::ToSql for Timestamp {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
Ok(rusqlite::types::ToSqlOutput::Owned(self.0.into()))
}
}
#[cfg(feature = "rusqlite")]
impl rusqlite::types::FromSql for Timestamp {
fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
match value {
rusqlite::types::ValueRef::Integer(i) => Ok(Self::from_micros(i)),
_ => Err(rusqlite::types::FromSqlError::InvalidType),
}
}
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
pub struct InclusiveTimestampInterval {
start: Timestamp,
end: Timestamp,
}
impl InclusiveTimestampInterval {
pub fn try_new(start: Timestamp, end: Timestamp) -> TimestampResult<Self> {
if start > end {
Err(TimestampError::OutOfOrder)
} else {
Ok(Self { start, end })
}
}
pub fn start(&self) -> Timestamp {
self.start
}
pub fn end(&self) -> Timestamp {
self.end
}
}
#[cfg(test)]
mod tests {
use std::convert::TryInto;
use super::*;
const TEST_TS: &str = "2020-05-05T19:16:04.266431Z";
#[test]
fn timestamp_distance() {
let t1 = Timestamp(i64::MAX); let d1: TimestampResult<chrono::DateTime<chrono::Utc>> = t1.try_into();
assert_eq!(d1, Err(TimestampError::Overflow));
let t2 = Timestamp(0) + core::time::Duration::new(0, 1000);
assert_eq!(t2, Ok(Timestamp(1)));
}
#[test]
fn micros_roundtrip() {
for t in [Timestamp(1234567890), Timestamp(987654321)] {
let micros = t.clone().as_micros();
let r = Timestamp::from_micros(micros);
assert_eq!(t.0, r.0);
assert_eq!(t, r);
}
}
#[test]
fn test_timestamp_serialization() {
use holochain_serialized_bytes::prelude::*;
let t: Timestamp = TEST_TS.try_into().unwrap();
let (secs, nsecs) = t.as_seconds_and_nanos();
assert_eq!(secs, 1588706164);
assert_eq!(nsecs, 266431000);
assert_eq!(TEST_TS, &t.to_string());
#[derive(Debug, serde::Serialize, serde::Deserialize, SerializedBytes)]
struct S(Timestamp);
let s = S(t);
let sb = SerializedBytes::try_from(s).unwrap();
let s: S = sb.try_into().unwrap();
let t = s.0;
assert_eq!(TEST_TS, &t.to_string());
}
#[test]
fn test_timestamp_alternate_forms() {
use holochain_serialized_bytes::prelude::*;
decode::<_, Timestamp>(&encode(&(0u64)).unwrap()).unwrap();
decode::<_, Timestamp>(&encode(&(i64::MAX as u64)).unwrap()).unwrap();
assert!(decode::<_, Timestamp>(&encode(&(i64::MAX as u64 + 1)).unwrap()).is_err());
}
#[test]
fn inclusive_timestamp_interval_test_new() {
for (start, end) in [(0, 0), (-1, 0), (0, 1), (i64::MIN, i64::MAX)] {
InclusiveTimestampInterval::try_new(Timestamp(start), Timestamp(end)).unwrap();
}
for (start, end) in [(0, -1), (1, 0), (i64::MAX, i64::MIN)] {
assert!(
super::InclusiveTimestampInterval::try_new(Timestamp(start), Timestamp(end))
.is_err()
);
}
}
}