use std::fmt;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use time::format_description::well_known::Rfc3339;
use time::{Duration, OffsetDateTime, PrimitiveDateTime, UtcOffset};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Timestamp(OffsetDateTime);
impl Timestamp {
pub fn now() -> Self {
Self(OffsetDateTime::now_utc())
}
pub fn from_offset_datetime(dt: OffsetDateTime) -> Self {
Self(dt.to_offset(UtcOffset::UTC))
}
pub const fn as_offset_datetime(&self) -> &OffsetDateTime {
&self.0
}
pub const fn into_offset_datetime(self) -> OffsetDateTime {
self.0
}
pub fn from_unix_millis_opt(millis: i64) -> Option<Self> {
OffsetDateTime::from_unix_timestamp_nanos(millis as i128 * 1_000_000).ok().map(Self)
}
pub fn from_unix_millis(millis: i64) -> Self {
Self::from_unix_millis_opt(millis).unwrap_or(Self::saturated(millis >= 0))
}
pub fn from_unix_seconds_opt(secs: i64) -> Option<Self> {
OffsetDateTime::from_unix_timestamp(secs).ok().map(Self)
}
pub fn from_unix_seconds(secs: i64) -> Self {
Self::from_unix_seconds_opt(secs).unwrap_or(Self::saturated(secs >= 0))
}
fn saturated(non_negative: bool) -> Self {
if non_negative {
Self(PrimitiveDateTime::MAX.assume_utc())
} else {
Self(PrimitiveDateTime::MIN.assume_utc())
}
}
pub fn unix_millis(&self) -> i64 {
(self.0.unix_timestamp_nanos() / 1_000_000) as i64
}
pub fn unix_seconds(&self) -> i64 {
self.0.unix_timestamp()
}
#[allow(
clippy::expect_used,
reason = "formatting an in-range UTC value with a static format description is infallible"
)]
pub fn to_rfc3339(&self) -> String {
let fmt = time::macros::format_description!(
"[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:3]Z"
);
self.0.format(fmt).expect("formatting a UTC timestamp with a fixed description cannot fail")
}
#[must_use]
pub fn parse_rfc3339(s: &str) -> Option<Self> {
OffsetDateTime::parse(s, &Rfc3339).map(Self::from_offset_datetime).ok()
}
pub fn duration_since(&self, earlier: Timestamp) -> Duration {
self.0 - earlier.0
}
pub fn checked_add(&self, delta: Duration) -> Option<Timestamp> {
self.0.checked_add(delta).map(Timestamp)
}
}
impl fmt::Display for Timestamp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.to_rfc3339())
}
}
impl From<OffsetDateTime> for Timestamp {
fn from(dt: OffsetDateTime) -> Self {
Self::from_offset_datetime(dt)
}
}
impl From<Timestamp> for OffsetDateTime {
fn from(ts: Timestamp) -> Self {
ts.0
}
}
impl Serialize for Timestamp {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let s = self.0.format(&Rfc3339).map_err(serde::ser::Error::custom)?;
serializer.serialize_str(&s)
}
}
impl<'de> Deserialize<'de> for Timestamp {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
OffsetDateTime::parse(&s, &Rfc3339)
.map(Self::from_offset_datetime)
.map_err(serde::de::Error::custom)
}
}