klauthed_core/time/
timestamp.rs1use std::fmt;
4
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6use time::format_description::well_known::Rfc3339;
7use time::{Duration, OffsetDateTime, PrimitiveDateTime, UtcOffset};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
14pub struct Timestamp(OffsetDateTime);
15
16impl Timestamp {
17 pub fn now() -> Self {
22 Self(OffsetDateTime::now_utc())
23 }
24
25 pub fn from_offset_datetime(dt: OffsetDateTime) -> Self {
27 Self(dt.to_offset(UtcOffset::UTC))
28 }
29
30 pub const fn as_offset_datetime(&self) -> &OffsetDateTime {
32 &self.0
33 }
34
35 pub const fn into_offset_datetime(self) -> OffsetDateTime {
37 self.0
38 }
39
40 pub fn from_unix_millis_opt(millis: i64) -> Option<Self> {
47 OffsetDateTime::from_unix_timestamp_nanos(millis as i128 * 1_000_000).ok().map(Self)
48 }
49
50 pub fn from_unix_millis(millis: i64) -> Self {
59 Self::from_unix_millis_opt(millis).unwrap_or(Self::saturated(millis >= 0))
60 }
61
62 pub fn from_unix_seconds_opt(secs: i64) -> Option<Self> {
65 OffsetDateTime::from_unix_timestamp(secs).ok().map(Self)
66 }
67
68 pub fn from_unix_seconds(secs: i64) -> Self {
71 Self::from_unix_seconds_opt(secs).unwrap_or(Self::saturated(secs >= 0))
72 }
73
74 fn saturated(non_negative: bool) -> Self {
77 if non_negative {
78 Self(PrimitiveDateTime::MAX.assume_utc())
79 } else {
80 Self(PrimitiveDateTime::MIN.assume_utc())
81 }
82 }
83
84 pub fn unix_millis(&self) -> i64 {
86 (self.0.unix_timestamp_nanos() / 1_000_000) as i64
87 }
88
89 pub fn unix_seconds(&self) -> i64 {
91 self.0.unix_timestamp()
92 }
93
94 #[allow(
96 clippy::expect_used,
97 reason = "formatting an in-range UTC value with a static format description is infallible"
98 )]
99 pub fn to_rfc3339(&self) -> String {
100 let fmt = time::macros::format_description!(
103 "[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:3]Z"
104 );
105 self.0.format(fmt).expect("formatting a UTC timestamp with a fixed description cannot fail")
106 }
107
108 pub fn duration_since(&self, earlier: Timestamp) -> Duration {
110 self.0 - earlier.0
111 }
112
113 pub fn checked_add(&self, delta: Duration) -> Option<Timestamp> {
115 self.0.checked_add(delta).map(Timestamp)
116 }
117}
118
119impl fmt::Display for Timestamp {
120 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121 f.write_str(&self.to_rfc3339())
122 }
123}
124
125impl From<OffsetDateTime> for Timestamp {
126 fn from(dt: OffsetDateTime) -> Self {
127 Self::from_offset_datetime(dt)
128 }
129}
130
131impl From<Timestamp> for OffsetDateTime {
132 fn from(ts: Timestamp) -> Self {
133 ts.0
134 }
135}
136
137impl Serialize for Timestamp {
138 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
139 let s = self.0.format(&Rfc3339).map_err(serde::ser::Error::custom)?;
143 serializer.serialize_str(&s)
144 }
145}
146
147impl<'de> Deserialize<'de> for Timestamp {
148 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
149 let s = String::deserialize(deserializer)?;
150 OffsetDateTime::parse(&s, &Rfc3339)
151 .map(Self::from_offset_datetime)
152 .map_err(serde::de::Error::custom)
153 }
154}