printobserver_types/
timestamp.rs1use core::fmt;
10use core::str::FromStr;
11use std::borrow::Cow;
12
13use chrono::{DateTime, SecondsFormat, TimeDelta, Utc};
14use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
15use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _};
16
17#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct TimestampError {
20 detail: String,
22}
23
24impl TimestampError {
25 #[must_use]
27 pub fn detail(&self) -> &str {
28 &self.detail
29 }
30}
31
32impl fmt::Display for TimestampError {
33 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
34 write!(formatter, "not an RFC 3339 instant: {}", self.detail)
35 }
36}
37
38impl core::error::Error for TimestampError {}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
42pub struct Timestamp(DateTime<Utc>);
43
44impl Timestamp {
45 #[must_use]
47 pub fn now() -> Self {
48 Self(Utc::now())
49 }
50
51 pub fn from_unix_seconds(seconds: i64) -> Result<Self, TimestampError> {
58 DateTime::from_timestamp(seconds, 0)
59 .map(Self)
60 .ok_or_else(|| TimestampError {
61 detail: format!(
62 "{seconds} seconds from the Unix epoch is not a representable instant"
63 ),
64 })
65 }
66
67 pub fn plus_seconds(self, seconds: i64) -> Result<Self, TimestampError> {
77 TimeDelta::try_seconds(seconds)
81 .and_then(|span| self.0.checked_add_signed(span))
82 .map(Self)
83 .ok_or_else(|| TimestampError {
84 detail: format!("{seconds} seconds after {self} is not a representable instant"),
85 })
86 }
87
88 #[must_use]
90 pub const fn as_utc(&self) -> &DateTime<Utc> {
91 &self.0
92 }
93}
94
95impl From<DateTime<Utc>> for Timestamp {
96 fn from(instant: DateTime<Utc>) -> Self {
97 Self(instant)
98 }
99}
100
101impl fmt::Display for Timestamp {
102 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
103 write!(
107 formatter,
108 "{}",
109 self.0.to_rfc3339_opts(SecondsFormat::AutoSi, true)
110 )
111 }
112}
113
114impl FromStr for Timestamp {
115 type Err = TimestampError;
116
117 fn from_str(text: &str) -> Result<Self, Self::Err> {
118 DateTime::parse_from_rfc3339(text)
119 .map(|offset| Self(offset.with_timezone(&Utc)))
120 .map_err(|error| TimestampError {
121 detail: format!("{text:?}: {error}"),
122 })
123 }
124}
125
126impl Serialize for Timestamp {
127 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
128 serializer.collect_str(self)
129 }
130}
131
132impl<'de> Deserialize<'de> for Timestamp {
133 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
134 let text = String::deserialize(deserializer)?;
135 text.parse().map_err(D::Error::custom)
136 }
137}
138
139impl JsonSchema for Timestamp {
140 fn schema_name() -> Cow<'static, str> {
141 Cow::Borrowed("Timestamp")
142 }
143
144 fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
145 json_schema!({
146 "type": "string",
147 "format": "date-time",
148 "title": "Timestamp",
149 "description": "An instant in UTC, as an RFC 3339 string with a zero offset."
150 })
151 }
152}