use core::fmt::{Display, Error as FmtError, Formatter};
use core::hash::Hash;
use core::num::ParseIntError;
use core::ops::{Add, Sub};
use core::str::FromStr;
use core::time::Duration;
use displaydoc::Display;
use tendermint::Time;
use time::OffsetDateTime;
use crate::prelude::*;
pub const ZERO_DURATION: Duration = Duration::from_secs(0);
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(PartialEq, Eq, Copy, Clone, Debug, PartialOrd, Ord, Hash)]
pub struct Timestamp {
#[cfg_attr(feature = "schema", schemars(with = "u64"))]
time: Option<Time>,
}
#[cfg(feature = "borsh")]
impl borsh::BorshSerialize for Timestamp {
fn serialize<W: borsh::maybestd::io::Write>(
&self,
writer: &mut W,
) -> borsh::maybestd::io::Result<()> {
let timestamp = self.nanoseconds();
borsh::BorshSerialize::serialize(×tamp, writer)
}
}
#[cfg(feature = "borsh")]
impl borsh::BorshDeserialize for Timestamp {
fn deserialize_reader<R: borsh::maybestd::io::Read>(
reader: &mut R,
) -> borsh::maybestd::io::Result<Self> {
let timestamp = u64::deserialize_reader(reader)?;
Ok(Self::from_nanoseconds(timestamp).map_err(|_| borsh::maybestd::io::ErrorKind::Other)?)
}
}
#[cfg(feature = "parity-scale-codec")]
impl parity_scale_codec::Encode for Timestamp {
fn encode_to<T: parity_scale_codec::Output + ?Sized>(&self, writer: &mut T) {
let timestamp = self.nanoseconds();
timestamp.encode_to(writer);
}
}
#[cfg(feature = "parity-scale-codec")]
impl parity_scale_codec::Decode for Timestamp {
fn decode<I: parity_scale_codec::Input>(
input: &mut I,
) -> Result<Self, parity_scale_codec::Error> {
let timestamp = u64::decode(input)?;
Self::from_nanoseconds(timestamp)
.map_err(|_| parity_scale_codec::Error::from("from nanoseconds error"))
}
}
#[cfg(feature = "parity-scale-codec")]
impl scale_info::TypeInfo for Timestamp {
type Identity = Self;
fn type_info() -> scale_info::Type {
scale_info::Type::builder()
.path(scale_info::Path::new("Timestamp", module_path!()))
.composite(
scale_info::build::Fields::named()
.field(|f| f.ty::<u64>().name("time").type_name("u64")),
)
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)]
pub enum Expiry {
Expired,
NotExpired,
InvalidTimestamp,
}
impl Timestamp {
pub fn from_nanoseconds(nanoseconds: u64) -> Result<Self, ParseTimestampError> {
if nanoseconds == 0 {
Ok(Self { time: None })
} else {
let ts = OffsetDateTime::from_unix_timestamp_nanos(nanoseconds.into())
.map_err(|e: time::error::ComponentRange| {
ParseTimestampError::DataOutOfRange(e.to_string())
})?
.try_into()
.map_err(|e: tendermint::error::Error| {
ParseTimestampError::DataOutOfRange(e.to_string())
})?;
Ok(Self { time: Some(ts) })
}
}
#[cfg(feature = "std")]
pub fn now() -> Self {
Time::now().into()
}
pub fn none() -> Self {
Self { time: None }
}
pub fn duration_since(&self, other: &Self) -> Option<Duration> {
match (self.time, other.time) {
(Some(time1), Some(time2)) => time1.duration_since(time2).ok(),
_ => None,
}
}
#[deprecated(since = "0.9.1", note = "use `nanoseconds` instead")]
pub fn as_nanoseconds(&self) -> u64 {
(*self).nanoseconds()
}
pub fn nanoseconds(self) -> u64 {
self.time.map_or(0, |time| {
let t: OffsetDateTime = time.into();
let s = t.unix_timestamp_nanos();
assert!(s >= 0, "time {time:?} has negative `.timestamp()`");
s.try_into().expect(
"Fails UNIX timestamp is negative, but we don't allow that to be constructed",
)
})
}
pub fn into_datetime(self) -> Option<OffsetDateTime> {
self.time.map(Into::into)
}
pub fn into_tm_time(self) -> Option<Time> {
self.time
}
pub fn check_expiry(&self, other: &Self) -> Expiry {
match (self.time, other.time) {
(Some(time1), Some(time2)) => {
if time1 > time2 {
Expiry::Expired
} else {
Expiry::NotExpired
}
}
_ => Expiry::InvalidTimestamp,
}
}
pub fn is_set(&self) -> bool {
self.time.is_some()
}
}
impl Display for Timestamp {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
write!(
f,
"Timestamp({})",
self.time
.map_or("NoTimestamp".to_string(), |time| time.to_rfc3339())
)
}
}
#[derive(Debug, Display)]
pub enum TimestampOverflowError {
TimestampOverflow,
}
#[cfg(feature = "std")]
impl std::error::Error for TimestampOverflowError {}
impl Add<Duration> for Timestamp {
type Output = Result<Self, TimestampOverflowError>;
fn add(self, duration: Duration) -> Result<Self, TimestampOverflowError> {
self.time
.map(|time| time + duration)
.transpose()
.map(|time| Self { time })
.map_err(|_| TimestampOverflowError::TimestampOverflow)
}
}
impl Sub<Duration> for Timestamp {
type Output = Result<Self, TimestampOverflowError>;
fn sub(self, duration: Duration) -> Result<Self, TimestampOverflowError> {
self.time
.map(|time| time - duration)
.transpose()
.map(|time| Self { time })
.map_err(|_| TimestampOverflowError::TimestampOverflow)
}
}
#[derive(Debug, Display)]
pub enum ParseTimestampError {
ParseInt(ParseIntError),
DataOutOfRange(String),
}
#[cfg(feature = "std")]
impl std::error::Error for ParseTimestampError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self {
Self::ParseInt(e) => Some(e),
Self::DataOutOfRange(_) => None,
}
}
}
impl FromStr for Timestamp {
type Err = ParseTimestampError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let nanoseconds = u64::from_str(s).map_err(ParseTimestampError::ParseInt)?;
Self::from_nanoseconds(nanoseconds)
}
}
impl From<Time> for Timestamp {
fn from(tendermint_time: Time) -> Self {
Self {
time: Some(tendermint_time),
}
}
}
#[cfg(test)]
mod tests {
use core::time::Duration;
use std::thread::sleep;
use time::OffsetDateTime;
use super::{Expiry, Timestamp, ZERO_DURATION};
#[test]
fn test_timestamp_comparisons() {
let nil_timestamp = Timestamp::from_nanoseconds(0).unwrap();
assert_eq!(nil_timestamp.time, None);
assert_eq!(nil_timestamp.nanoseconds(), 0);
let timestamp1 = Timestamp::from_nanoseconds(1).unwrap();
let dt: OffsetDateTime = timestamp1.time.unwrap().into();
assert_eq!(dt.unix_timestamp_nanos(), 1);
assert_eq!(timestamp1.nanoseconds(), 1);
let timestamp2 = Timestamp::from_nanoseconds(1_000_000_000).unwrap();
let dt: OffsetDateTime = timestamp2.time.unwrap().into();
assert_eq!(dt.unix_timestamp_nanos(), 1_000_000_000);
assert_eq!(timestamp2.nanoseconds(), 1_000_000_000);
assert!(Timestamp::from_nanoseconds(u64::MAX).is_ok());
assert!(Timestamp::from_nanoseconds(i64::MAX.try_into().unwrap()).is_ok());
assert_eq!(timestamp1.check_expiry(×tamp2), Expiry::NotExpired);
assert_eq!(timestamp1.check_expiry(×tamp1), Expiry::NotExpired);
assert_eq!(timestamp2.check_expiry(×tamp2), Expiry::NotExpired);
assert_eq!(timestamp2.check_expiry(×tamp1), Expiry::Expired);
assert_eq!(
timestamp1.check_expiry(&nil_timestamp),
Expiry::InvalidTimestamp
);
assert_eq!(
nil_timestamp.check_expiry(×tamp2),
Expiry::InvalidTimestamp
);
assert_eq!(
nil_timestamp.check_expiry(&nil_timestamp),
Expiry::InvalidTimestamp
);
}
#[test]
fn test_timestamp_arithmetic() {
let time0 = Timestamp::none();
let time1 = Timestamp::from_nanoseconds(100).unwrap();
let time2 = Timestamp::from_nanoseconds(150).unwrap();
let time3 = Timestamp::from_nanoseconds(50).unwrap();
let duration = Duration::from_nanos(50);
assert_eq!(time1, (time1 + ZERO_DURATION).unwrap());
assert_eq!(time2, (time1 + duration).unwrap());
assert_eq!(time3, (time1 - duration).unwrap());
assert_eq!(time0, (time0 + duration).unwrap());
assert_eq!(time0, (time0 - duration).unwrap());
}
#[test]
fn subtract_compare() {
let sleep_duration = Duration::from_micros(100);
let start = Timestamp::now();
sleep(sleep_duration);
let end = Timestamp::now();
let res = end.duration_since(&start);
assert!(res.is_some());
let inner = res.unwrap();
assert!(inner > sleep_duration);
}
#[test]
#[cfg(feature = "borsh")]
fn test_timestamp_borsh_ser_der() {
use borsh::{BorshDeserialize, BorshSerialize};
let timestamp = Timestamp::now();
let encode_timestamp = timestamp.try_to_vec().unwrap();
let _ = Timestamp::try_from_slice(&encode_timestamp).unwrap();
}
#[test]
#[cfg(feature = "parity-scale-codec")]
fn test_timestamp_parity_scale_codec_ser_der() {
use parity_scale_codec::{Decode, Encode};
let timestamp = Timestamp::now();
let encode_timestamp = timestamp.encode();
let _ = Timestamp::decode(&mut encode_timestamp.as_slice()).unwrap();
}
}