#[allow(missing_docs)]
mod error;
use std::{
convert::TryFrom,
fmt,
ops::{Add, Sub},
str::FromStr,
};
use crate::prelude::*;
pub use error::{TimestampError, TimestampResult};
#[derive(
Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize, SerializedBytes,
)]
pub struct Timestamp(
pub i64, pub u32, );
impl fmt::Display for Timestamp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let ce = -62167219200_i64..=253402214400_i64;
if ce.contains(&self.0) {
if let Ok(ts) = chrono::DateTime::<chrono::Utc>::try_from(self) {
return write!(
f,
"{}",
ts.to_rfc3339_opts(chrono::SecondsFormat::AutoSi, true)
);
}
}
write!(f, "({},{})", self.0, self.1)
}
}
impl fmt::Debug for Timestamp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Timestamp({})", self)
}
}
impl From<i64> for Timestamp {
fn from(secs: i64) -> Self {
Self(secs, 0)
}
}
impl From<i32> for Timestamp {
fn from(secs: i32) -> Self {
Self(secs.into(), 0)
}
}
impl From<u32> for Timestamp {
fn from(secs: u32) -> Self {
Self(secs.into(), 0)
}
}
impl From<chrono::DateTime<chrono::Utc>> for Timestamp {
fn from(t: chrono::DateTime<chrono::Utc>) -> Self {
std::convert::From::from(&t)
}
}
impl From<&chrono::DateTime<chrono::Utc>> for Timestamp {
fn from(t: &chrono::DateTime<chrono::Utc>) -> Self {
let t = t.naive_utc();
Timestamp(t.timestamp(), t.timestamp_subsec_nanos())
}
}
impl TryFrom<Timestamp> for chrono::DateTime<chrono::Utc> {
type Error = TimestampError;
fn try_from(t: Timestamp) -> Result<Self, Self::Error> {
std::convert::TryFrom::try_from(&t)
}
}
impl TryFrom<&Timestamp> for chrono::DateTime<chrono::Utc> {
type Error = TimestampError;
fn try_from(t: &Timestamp) -> Result<Self, Self::Error> {
let t = chrono::naive::NaiveDateTime::from_timestamp_opt(t.0, t.1)
.ok_or(TimestampError::Overflow)?;
Ok(chrono::DateTime::from_utc(t, chrono::Utc))
}
}
impl FromStr for Timestamp {
type Err = TimestampError;
fn from_str(t: &str) -> Result<Self, Self::Err> {
let t = chrono::DateTime::parse_from_rfc3339(t)?;
let t = chrono::DateTime::from_utc(t.naive_utc(), chrono::Utc);
Ok(t.into())
}
}
impl TryFrom<String> for Timestamp {
type Error = TimestampError;
fn try_from(t: String) -> Result<Self, Self::Error> {
Timestamp::from_str(t.as_ref())
}
}
impl TryFrom<&String> for Timestamp {
type Error = TimestampError;
fn try_from(t: &String) -> Result<Self, Self::Error> {
Timestamp::from_str(t.as_ref())
}
}
impl TryFrom<&str> for Timestamp {
type Error = TimestampError;
fn try_from(t: &str) -> Result<Self, Self::Error> {
Timestamp::from_str(t)
}
}
impl<D: Into<core::time::Duration>> Add<D> for Timestamp {
type Output = TimestampResult<Timestamp>;
fn add(self, rhs: D) -> Self::Output {
Ok(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 {
Ok(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
}
}
macro_rules! try_opt {
($e:expr) => {
match $e {
Some(v) => v,
None => return None,
}
};
}
impl Timestamp {
pub fn normalize(secs: i64, nanos: i64) -> Option<Timestamp> {
let seconds = try_opt!(secs.checked_add(nanos / 1_000_000_000));
let nanos = nanos % 1_000_000_000; let ts = if nanos < 0 {
let seconds = try_opt!(secs.checked_sub(1));
let nanos = try_opt!(nanos.checked_add(1_000_000_000));
let nanos = try_opt!(u32::try_from(nanos).ok()); Timestamp(seconds, nanos)
} else {
let nanos = try_opt!(u32::try_from(nanos).ok());
Timestamp(seconds, nanos)
};
Some(ts)
}
pub fn checked_difference_signed(&self, rhs: &Timestamp) -> Option<chrono::Duration> {
let dif_secs = try_opt!(self.0.checked_sub(rhs.0));
let dif_nano = try_opt!(i64::from(self.1).checked_sub(i64::from(rhs.1)));
let dif = try_opt!(Timestamp::normalize(dif_secs, dif_nano));
let dur_milli = chrono::Duration::milliseconds(try_opt!(dif.0.checked_mul(1_000)));
let dur_nanos = chrono::Duration::nanoseconds(dif.1.into()); let dur = try_opt!(dur_milli.checked_add(&dur_nanos));
Some(dur)
}
pub fn checked_add_signed(&self, rhs: &chrono::Duration) -> Option<Timestamp> {
let dur_millis: i64 = rhs.num_milliseconds();
let rhs_remains = try_opt!(rhs.checked_sub(&chrono::Duration::milliseconds(dur_millis)));
let dur_nanos: i64 =
try_opt!(rhs_remains.num_nanoseconds()) + (dur_millis % 1_000) * 1_000_000;
let dur_seconds: i64 = dur_millis / 1_000;
let seconds: i64 = try_opt!(self.0.checked_add(dur_seconds));
let nanos: i64 = try_opt!(i64::from(self.1).checked_add(dur_nanos));
Some(try_opt!(Timestamp::normalize(seconds, nanos)))
}
pub fn checked_sub_signed(&self, rhs: &chrono::Duration) -> Option<Timestamp> {
self.checked_add_signed(&-*rhs)
}
pub fn checked_add(&self, rhs: &core::time::Duration) -> Option<Timestamp> {
let dur_seconds: i64 = try_opt!(i64::try_from(rhs.as_secs()).ok());
let dur_nanos: i64 = i64::from(rhs.subsec_nanos());
let seconds: i64 = try_opt!(self.0.checked_add(dur_seconds));
let nanos: i64 = try_opt!(i64::from(self.1).checked_add(dur_nanos));
Some(try_opt!(Timestamp::normalize(seconds, nanos)))
}
pub fn checked_sub(&self, rhs: &core::time::Duration) -> Option<Timestamp> {
let dur_seconds: i64 = try_opt!(i64::try_from(rhs.as_secs()).ok());
let dur_nanos: i64 = i64::from(rhs.subsec_nanos());
let seconds: i64 = try_opt!(self.0.checked_sub(dur_seconds));
let nanos: i64 = try_opt!(i64::from(self.1).checked_sub(dur_nanos));
Some(try_opt!(Timestamp::normalize(seconds, nanos)))
}
}
impl Sub<Timestamp> for Timestamp {
type Output = TimestampResult<chrono::Duration>;
fn sub(self, rhs: Timestamp) -> Self::Output {
Ok(self
.checked_difference_signed(&rhs)
.ok_or(TimestampError::Overflow)?)
}
}
#[cfg(test)]
pub mod tests {
use super::*;
#[test]
fn timestamp_distance() {
let t1 = Timestamp((2_i64.pow(31) + 1) * 86_400, 1_000_000_000); let d1: TimestampResult<chrono::DateTime<chrono::Utc>> = t1.try_into();
assert_eq!(d1, Err(TimestampError::Overflow));
let t2 = Timestamp(0, 0) + core::time::Duration::new(0, 1);
assert_eq!(t2, Ok(Timestamp(0, 1)));
}
}