prost_wkt_types/pbtime/
timestamp.rs1use super::*;
2
3impl Timestamp {
8 pub fn normalize(&mut self) {
14 if self.nanos <= -NANOS_PER_SECOND || self.nanos >= NANOS_PER_SECOND {
16 if let Some(seconds) = self
17 .seconds
18 .checked_add((self.nanos / NANOS_PER_SECOND) as i64)
19 {
20 self.seconds = seconds;
21 self.nanos %= NANOS_PER_SECOND;
22 } else if self.nanos < 0 {
23 self.seconds = i64::MIN;
25 self.nanos = 0;
26 } else {
27 self.seconds = i64::MAX;
29 self.nanos = 999_999_999;
30 }
31 }
32
33 if self.nanos < 0 {
35 if let Some(seconds) = self.seconds.checked_sub(1) {
36 self.seconds = seconds;
37 self.nanos += NANOS_PER_SECOND;
38 } else {
39 debug_assert_eq!(self.seconds, i64::MIN);
41 self.nanos = 0;
42 }
43 }
44
45 }
49
50 pub fn try_normalize(mut self) -> Result<Timestamp, Timestamp> {
57 let before = self;
58 self.normalize();
59 if (self.seconds == i64::MAX || self.seconds == i64::MIN) && self.seconds != before.seconds
62 {
63 Err(before)
64 } else {
65 Ok(self)
66 }
67 }
68
69 pub fn date(year: i64, month: u8, day: u8) -> Result<Timestamp, TimestampError> {
71 Timestamp::date_time_nanos(year, month, day, 0, 0, 0, 0)
72 }
73
74 pub fn date_time(
76 year: i64,
77 month: u8,
78 day: u8,
79 hour: u8,
80 minute: u8,
81 second: u8,
82 ) -> Result<Timestamp, TimestampError> {
83 Timestamp::date_time_nanos(year, month, day, hour, minute, second, 0)
84 }
85
86 pub fn date_time_nanos(
88 year: i64,
89 month: u8,
90 day: u8,
91 hour: u8,
92 minute: u8,
93 second: u8,
94 nanos: u32,
95 ) -> Result<Timestamp, TimestampError> {
96 let date_time = datetime::DateTime {
97 year,
98 month,
99 day,
100 hour,
101 minute,
102 second,
103 nanos,
104 };
105
106 Timestamp::try_from(date_time)
107 }
108}
109
110#[cfg(feature = "std")]
120impl From<std::time::SystemTime> for Timestamp {
121 fn from(system_time: std::time::SystemTime) -> Timestamp {
122 let (seconds, nanos) = match system_time.duration_since(std::time::UNIX_EPOCH) {
123 Ok(duration) => {
124 let seconds = i64::try_from(duration.as_secs()).unwrap();
125 (seconds, duration.subsec_nanos() as i32)
126 }
127 Err(error) => {
128 let duration = error.duration();
129 let seconds = i64::try_from(duration.as_secs()).unwrap();
130 let nanos = duration.subsec_nanos() as i32;
131 if nanos == 0 {
132 (-seconds, 0)
133 } else {
134 (-seconds - 1, 1_000_000_000 - nanos)
135 }
136 }
137 };
138 Timestamp { seconds, nanos }
139 }
140}
141
142#[allow(clippy::derive_partial_eq_without_eq)]
144#[derive(Debug, PartialEq)]
145#[non_exhaustive]
146pub enum TimestampError {
147 OutOfSystemRange(Timestamp),
155
156 ParseFailure,
158
159 InvalidDateTime,
161}
162
163impl fmt::Display for TimestampError {
164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 match self {
166 TimestampError::OutOfSystemRange(timestamp) => {
167 write!(
168 f,
169 "{} is not representable as a `SystemTime` because it is out of range",
170 timestamp
171 )
172 }
173 TimestampError::ParseFailure => {
174 write!(f, "failed to parse RFC-3339 formatted timestamp")
175 }
176 TimestampError::InvalidDateTime => {
177 write!(f, "invalid date or time")
178 }
179 }
180 }
181}
182
183#[cfg(feature = "std")]
184impl std::error::Error for TimestampError {}
185
186#[cfg(feature = "std")]
187impl TryFrom<Timestamp> for std::time::SystemTime {
188 type Error = TimestampError;
189
190 fn try_from(mut timestamp: Timestamp) -> Result<std::time::SystemTime, Self::Error> {
191 let orig_timestamp = timestamp;
192 timestamp.normalize();
193
194 let system_time = if timestamp.seconds >= 0 {
195 std::time::UNIX_EPOCH.checked_add(time::Duration::from_secs(timestamp.seconds as u64))
196 } else {
197 std::time::UNIX_EPOCH.checked_sub(time::Duration::from_secs(
198 timestamp
199 .seconds
200 .checked_neg()
201 .ok_or(TimestampError::OutOfSystemRange(timestamp))? as u64,
202 ))
203 };
204
205 let system_time = system_time.and_then(|system_time| {
206 system_time.checked_add(time::Duration::from_nanos(timestamp.nanos as u64))
207 });
208
209 system_time.ok_or(TimestampError::OutOfSystemRange(orig_timestamp))
210 }
211}
212
213impl FromStr for Timestamp {
214 type Err = TimestampError;
215
216 fn from_str(s: &str) -> Result<Timestamp, TimestampError> {
217 datetime::parse_timestamp(s).ok_or(TimestampError::ParseFailure)
218 }
219}
220
221impl fmt::Display for Timestamp {
222 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223 datetime::DateTime::from(*self).fmt(f)
224 }
225}
226
227impl From<NaiveDateTime> for Timestamp {
233 fn from(dt: NaiveDateTime) -> Self {
234 Timestamp {
235 seconds: dt.and_utc().timestamp(),
236 nanos: dt.and_utc().timestamp_subsec_nanos() as i32,
237 }
238 }
239}
240
241impl From<DateTime<Utc>> for Timestamp {
243 fn from(dt: DateTime<Utc>) -> Self {
244 Timestamp {
245 seconds: dt.timestamp(),
246 nanos: dt.timestamp_subsec_nanos() as i32,
247 }
248 }
249}
250
251impl From<Timestamp> for DateTime<Utc> {
253 fn from(val: Timestamp) -> Self {
254 let mut value = val;
255 value.normalize();
260 DateTime::from_timestamp(value.seconds, value.nanos as u32)
261 .expect("invalid or out-of-range datetime")
262 }
263}
264
265impl Serialize for Timestamp {
266 fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
267 where
268 S: Serializer,
269 {
270 let mut ts = Timestamp {
271 seconds: self.seconds,
272 nanos: self.nanos,
273 };
274 ts.normalize();
275 let dt: DateTime<Utc> = ts.try_into().map_err(serde::ser::Error::custom)?;
276 serializer.serialize_str(format!("{dt:?}").as_str())
277 }
278}
279
280impl<'de> Deserialize<'de> for Timestamp {
281 fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
282 where
283 D: Deserializer<'de>,
284 {
285 struct TimestampVisitor;
286
287 impl<'de> Visitor<'de> for TimestampVisitor {
288 type Value = Timestamp;
289
290 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
291 formatter.write_str("Timestamp in RFC3339 format")
292 }
293
294 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
295 where
296 E: de::Error,
297 {
298 let utc: DateTime<Utc> = chrono::DateTime::from_str(value).map_err(|err| {
299 serde::de::Error::custom(format!(
300 "Failed to parse {value} as datetime: {err:?}"
301 ))
302 })?;
303 let ts = Timestamp::from(utc);
304 Ok(ts)
305 }
306 }
307 deserializer.deserialize_str(TimestampVisitor)
308 }
309}
310
311#[cfg(feature = "schemars")]
312mod schemars_impl {
313 use super::Timestamp;
314 use schemars::generate::SchemaGenerator;
315 use schemars::{json_schema, JsonSchema, Schema};
316 use std::borrow::Cow;
317
318 impl JsonSchema for Timestamp {
319 fn schema_name() -> Cow<'static, str> {
320 Cow::Borrowed("Timestamp")
321 }
322
323 fn schema_id() -> Cow<'static, str> {
324 Cow::Borrowed("prost_wkt_types::Timestamp")
325 }
326
327 fn json_schema(_gen: &mut SchemaGenerator) -> Schema {
328 json_schema!({
329 "type": "string",
330 "description": "A timestamp in RFC 3339 format",
331 "examples": [
332 "2025-04-11T12:00:00Z",
333 "2025-04-11T12:00:00.123456789Z",
334 ],
335 })
336 }
337 }
338}