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 #[must_use]
113 pub fn parse_rfc3339(s: &str) -> Option<Self> {
114 OffsetDateTime::parse(s, &Rfc3339).map(Self::from_offset_datetime).ok()
115 }
116
117 pub fn duration_since(&self, earlier: Timestamp) -> Duration {
119 self.0 - earlier.0
120 }
121
122 pub fn checked_add(&self, delta: Duration) -> Option<Timestamp> {
124 self.0.checked_add(delta).map(Timestamp)
125 }
126}
127
128impl fmt::Display for Timestamp {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 f.write_str(&self.to_rfc3339())
131 }
132}
133
134impl From<OffsetDateTime> for Timestamp {
135 fn from(dt: OffsetDateTime) -> Self {
136 Self::from_offset_datetime(dt)
137 }
138}
139
140impl From<Timestamp> for OffsetDateTime {
141 fn from(ts: Timestamp) -> Self {
142 ts.0
143 }
144}
145
146impl Serialize for Timestamp {
147 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
148 let s = self.0.format(&Rfc3339).map_err(serde::ser::Error::custom)?;
152 serializer.serialize_str(&s)
153 }
154}
155
156impl<'de> Deserialize<'de> for Timestamp {
157 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
158 let s = String::deserialize(deserializer)?;
159 OffsetDateTime::parse(&s, &Rfc3339)
160 .map(Self::from_offset_datetime)
161 .map_err(serde::de::Error::custom)
162 }
163}