#![allow(clippy::field_reassign_with_default)]
use alloc::vec::Vec;
use core::{
ops::{Add, AddAssign, Div, Mul, Rem, Shl, Shr, Sub, SubAssign},
time::Duration,
};
#[cfg(any(feature = "std", test))]
use std::{
fmt::{self, Display, Formatter},
str::FromStr,
time::SystemTime,
};
#[cfg(feature = "datasize")]
use datasize::DataSize;
#[cfg(any(feature = "std", test))]
use humantime::{DurationError, TimestampError};
#[cfg(any(feature = "testing", test))]
use rand::Rng;
#[cfg(feature = "json-schema")]
use schemars::JsonSchema;
#[cfg(any(feature = "std", test))]
use serde::{de::Error as SerdeError, Deserialize, Deserializer, Serialize, Serializer};
use crate::bytesrepr::{self, FromBytes, ToBytes};
#[cfg(any(feature = "testing", test))]
use crate::testing::TestRng;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "datasize", derive(DataSize))]
#[cfg_attr(
feature = "json-schema",
derive(JsonSchema),
schemars(with = "String", description = "Timestamp formatted as per RFC 3339")
)]
pub struct Timestamp(u64);
impl Timestamp {
pub const MAX: Timestamp = Timestamp(u64::MAX);
#[cfg(any(feature = "std", test))]
pub fn now() -> Self {
let millis = SystemTime::UNIX_EPOCH.elapsed().unwrap().as_millis() as u64;
Timestamp(millis)
}
#[cfg(any(feature = "std", test))]
pub fn elapsed(&self) -> TimeDiff {
TimeDiff(Timestamp::now().0.saturating_sub(self.0))
}
pub fn zero() -> Self {
Timestamp(0)
}
pub fn millis(&self) -> u64 {
self.0
}
pub fn saturating_diff(self, other: Timestamp) -> TimeDiff {
TimeDiff(self.0.saturating_sub(other.0))
}
#[must_use]
pub fn saturating_sub(self, other: TimeDiff) -> Timestamp {
Timestamp(self.0.saturating_sub(other.0))
}
#[must_use]
pub fn saturating_add(self, other: TimeDiff) -> Timestamp {
Timestamp(self.0.saturating_add(other.0))
}
pub fn trailing_zeros(&self) -> u8 {
self.0.trailing_zeros() as u8
}
}
#[cfg(any(feature = "testing", test))]
impl Timestamp {
pub fn random(rng: &mut TestRng) -> Self {
Timestamp(1_596_763_000_000 + rng.gen_range(200_000..1_000_000))
}
pub fn checked_sub(self, other: TimeDiff) -> Option<Timestamp> {
self.0.checked_sub(other.0).map(Timestamp)
}
}
#[cfg(any(feature = "std", test))]
impl Display for Timestamp {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match SystemTime::UNIX_EPOCH.checked_add(Duration::from_millis(self.0)) {
Some(system_time) => write!(f, "{}", humantime::format_rfc3339_millis(system_time))
.or_else(|e| write!(f, "Invalid timestamp: {}: {}", e, self.0)),
None => write!(f, "invalid Timestamp: {} ms after the Unix epoch", self.0),
}
}
}
#[cfg(any(feature = "std", test))]
impl FromStr for Timestamp {
type Err = TimestampError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let system_time = humantime::parse_rfc3339_weak(value)?;
let inner = system_time
.duration_since(SystemTime::UNIX_EPOCH)
.map_err(|_| TimestampError::OutOfRange)?
.as_millis() as u64;
Ok(Timestamp(inner))
}
}
impl Add<TimeDiff> for Timestamp {
type Output = Timestamp;
fn add(self, diff: TimeDiff) -> Timestamp {
Timestamp(self.0 + diff.0)
}
}
impl AddAssign<TimeDiff> for Timestamp {
fn add_assign(&mut self, rhs: TimeDiff) {
self.0 += rhs.0;
}
}
#[cfg(any(feature = "testing", test))]
impl std::ops::Sub<TimeDiff> for Timestamp {
type Output = Timestamp;
fn sub(self, diff: TimeDiff) -> Timestamp {
Timestamp(self.0 - diff.0)
}
}
impl Rem<TimeDiff> for Timestamp {
type Output = TimeDiff;
fn rem(self, diff: TimeDiff) -> TimeDiff {
TimeDiff(self.0 % diff.0)
}
}
impl<T> Shl<T> for Timestamp
where
u64: Shl<T, Output = u64>,
{
type Output = Timestamp;
fn shl(self, rhs: T) -> Timestamp {
Timestamp(self.0 << rhs)
}
}
impl<T> Shr<T> for Timestamp
where
u64: Shr<T, Output = u64>,
{
type Output = Timestamp;
fn shr(self, rhs: T) -> Timestamp {
Timestamp(self.0 >> rhs)
}
}
#[cfg(any(feature = "std", test))]
impl Serialize for Timestamp {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if serializer.is_human_readable() {
self.to_string().serialize(serializer)
} else {
self.0.serialize(serializer)
}
}
}
#[cfg(any(feature = "std", test))]
impl<'de> Deserialize<'de> for Timestamp {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
if deserializer.is_human_readable() {
let value_as_string = String::deserialize(deserializer)?;
Timestamp::from_str(&value_as_string).map_err(SerdeError::custom)
} else {
let inner = u64::deserialize(deserializer)?;
Ok(Timestamp(inner))
}
}
}
impl ToBytes for Timestamp {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
self.0.to_bytes()
}
fn serialized_length(&self) -> usize {
self.0.serialized_length()
}
}
impl FromBytes for Timestamp {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
u64::from_bytes(bytes).map(|(inner, remainder)| (Timestamp(inner), remainder))
}
}
impl From<u64> for Timestamp {
fn from(milliseconds_since_epoch: u64) -> Timestamp {
Timestamp(milliseconds_since_epoch)
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "datasize", derive(DataSize))]
#[cfg_attr(
feature = "json-schema",
derive(JsonSchema),
schemars(with = "String", description = "Human-readable duration.")
)]
pub struct TimeDiff(u64);
#[cfg(any(feature = "std", test))]
impl Display for TimeDiff {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", humantime::format_duration(Duration::from(*self)))
}
}
#[cfg(any(feature = "std", test))]
impl FromStr for TimeDiff {
type Err = DurationError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let inner = humantime::parse_duration(value)?.as_millis() as u64;
Ok(TimeDiff(inner))
}
}
impl TimeDiff {
pub fn millis(&self) -> u64 {
self.0
}
pub const fn from_seconds(seconds: u32) -> Self {
TimeDiff(seconds as u64 * 1_000)
}
pub const fn from_millis(millis: u64) -> Self {
TimeDiff(millis)
}
#[must_use]
pub fn saturating_add(self, rhs: u64) -> Self {
TimeDiff(self.0.saturating_add(rhs))
}
#[must_use]
pub fn saturating_mul(self, rhs: u64) -> Self {
TimeDiff(self.0.saturating_mul(rhs))
}
#[must_use]
pub fn checked_mul(self, rhs: u64) -> Option<Self> {
Some(TimeDiff(self.0.checked_mul(rhs)?))
}
}
impl Add<TimeDiff> for TimeDiff {
type Output = TimeDiff;
fn add(self, rhs: TimeDiff) -> TimeDiff {
TimeDiff(self.0 + rhs.0)
}
}
impl AddAssign<TimeDiff> for TimeDiff {
fn add_assign(&mut self, rhs: TimeDiff) {
self.0 += rhs.0;
}
}
impl Sub<TimeDiff> for TimeDiff {
type Output = TimeDiff;
fn sub(self, rhs: TimeDiff) -> TimeDiff {
TimeDiff(self.0 - rhs.0)
}
}
impl SubAssign<TimeDiff> for TimeDiff {
fn sub_assign(&mut self, rhs: TimeDiff) {
self.0 -= rhs.0;
}
}
impl Mul<u64> for TimeDiff {
type Output = TimeDiff;
fn mul(self, rhs: u64) -> TimeDiff {
TimeDiff(self.0 * rhs)
}
}
impl Div<u64> for TimeDiff {
type Output = TimeDiff;
fn div(self, rhs: u64) -> TimeDiff {
TimeDiff(self.0 / rhs)
}
}
impl Div<TimeDiff> for TimeDiff {
type Output = u64;
fn div(self, rhs: TimeDiff) -> u64 {
self.0 / rhs.0
}
}
impl From<TimeDiff> for Duration {
fn from(diff: TimeDiff) -> Duration {
Duration::from_millis(diff.0)
}
}
#[cfg(any(feature = "std", test))]
impl Serialize for TimeDiff {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if serializer.is_human_readable() {
self.to_string().serialize(serializer)
} else {
self.0.serialize(serializer)
}
}
}
#[cfg(any(feature = "std", test))]
impl<'de> Deserialize<'de> for TimeDiff {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
if deserializer.is_human_readable() {
let value_as_string = String::deserialize(deserializer)?;
TimeDiff::from_str(&value_as_string).map_err(SerdeError::custom)
} else {
let inner = u64::deserialize(deserializer)?;
Ok(TimeDiff(inner))
}
}
}
impl ToBytes for TimeDiff {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
self.0.to_bytes()
}
fn serialized_length(&self) -> usize {
self.0.serialized_length()
}
}
impl FromBytes for TimeDiff {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
u64::from_bytes(bytes).map(|(inner, remainder)| (TimeDiff(inner), remainder))
}
}
impl From<Duration> for TimeDiff {
fn from(duration: Duration) -> TimeDiff {
TimeDiff(duration.as_millis() as u64)
}
}
#[cfg(any(feature = "std", test))]
pub mod serde_option_time_diff {
use super::*;
pub fn serialize<S: Serializer>(
maybe_td: &Option<TimeDiff>,
serializer: S,
) -> Result<S::Ok, S::Error> {
maybe_td
.unwrap_or_else(|| TimeDiff::from_millis(0))
.serialize(serializer)
}
pub fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Option<TimeDiff>, D::Error> {
let td = TimeDiff::deserialize(deserializer)?;
if td.0 == 0 {
Ok(None)
} else {
Ok(Some(td))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn timestamp_serialization_roundtrip() {
let timestamp = Timestamp::now();
let timestamp_as_string = timestamp.to_string();
assert_eq!(
timestamp,
Timestamp::from_str(×tamp_as_string).unwrap()
);
let serialized_json = serde_json::to_string(×tamp).unwrap();
assert_eq!(timestamp, serde_json::from_str(&serialized_json).unwrap());
let serialized_bincode = bincode::serialize(×tamp).unwrap();
assert_eq!(
timestamp,
bincode::deserialize(&serialized_bincode).unwrap()
);
bytesrepr::test_serialization_roundtrip(×tamp);
}
#[test]
fn timediff_serialization_roundtrip() {
let mut rng = TestRng::new();
let timediff = TimeDiff(rng.gen());
let timediff_as_string = timediff.to_string();
assert_eq!(timediff, TimeDiff::from_str(&timediff_as_string).unwrap());
let serialized_json = serde_json::to_string(&timediff).unwrap();
assert_eq!(timediff, serde_json::from_str(&serialized_json).unwrap());
let serialized_bincode = bincode::serialize(&timediff).unwrap();
assert_eq!(timediff, bincode::deserialize(&serialized_bincode).unwrap());
bytesrepr::test_serialization_roundtrip(&timediff);
}
#[test]
fn does_not_crash_for_big_timestamp_value() {
assert!(Timestamp::MAX.to_string().starts_with("Invalid timestamp:"));
}
}