1#[derive(Clone, Debug, Default, PartialEq, PartialOrd)]
46#[non_exhaustive]
47pub struct Timestamp {
48 seconds: i64,
52
53 nanos: i32,
58}
59
60#[derive(thiserror::Error, Debug, PartialEq)]
62pub enum TimestampError {
63 #[error("seconds and/or nanoseconds out of range")]
65 OutOfRange(),
66
67 #[error("cannot serialize timestamp: {0}")]
68 Serialize(String),
69
70 #[error("cannot deserialize timestamp: {0}")]
71 Deserialize(String),
72}
73
74type Error = TimestampError;
75
76impl Timestamp {
77 const NS: i32 = 1_000_000_000;
78
79 pub const MIN_SECONDS: i64 = -62135596800;
82
83 pub const MAX_SECONDS: i64 = 253402300799;
86
87 pub const MIN_NANOS: i32 = 0;
89
90 pub const MAX_NANOS: i32 = Self::NS - 1;
92
93 pub fn new(seconds: i64, nanos: i32) -> Result<Self, Error> {
102 if !(Self::MIN_SECONDS..=Self::MAX_SECONDS).contains(&seconds) {
103 return Err(Error::OutOfRange());
104 }
105 if !(Self::MIN_NANOS..=Self::MAX_NANOS).contains(&nanos) {
106 return Err(Error::OutOfRange());
107 }
108 Ok(Self { seconds, nanos })
109 }
110
111 pub fn clamp(seconds: i64, nanos: i32) -> Self {
126 let (seconds, nanos) = match nanos.cmp(&0_i32) {
127 std::cmp::Ordering::Equal => (seconds, nanos),
128 std::cmp::Ordering::Greater => (
129 seconds.saturating_add((nanos / Self::NS) as i64),
130 nanos % Self::NS,
131 ),
132 std::cmp::Ordering::Less => (
133 seconds.saturating_sub(1 - (nanos / Self::NS) as i64),
134 Self::NS + nanos % Self::NS,
135 ),
136 };
137 if seconds < Self::MIN_SECONDS {
138 return Self {
139 seconds: Self::MIN_SECONDS,
140 nanos: 0,
141 };
142 } else if seconds > Self::MAX_SECONDS {
143 return Self {
144 seconds: Self::MAX_SECONDS,
145 nanos: 0,
146 };
147 }
148 Self { seconds, nanos }
149 }
150
151 pub fn seconds(&self) -> i64 {
155 self.seconds
156 }
157
158 pub fn nanos(&self) -> i32 {
164 self.nanos
165 }
166}
167
168impl crate::message::Message for Timestamp {
169 fn typename() -> &'static str {
170 "type.googleapis.com/google.protobuf.Timestamp"
171 }
172 fn to_map(&self) -> Result<crate::message::Map, crate::AnyError> {
173 crate::message::to_json_string(self)
174 }
175 fn from_map(map: &crate::message::Map) -> Result<Self, crate::AnyError> {
176 crate::message::from_value(map)
177 }
178}
179
180use time::format_description::well_known::Rfc3339;
181const NS: i128 = 1_000_000_000;
182
183impl serde::ser::Serialize for Timestamp {
185 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
186 where
187 S: serde::ser::Serializer,
188 {
189 use serde::ser::Error as _;
190 String::try_from(self)
191 .map_err(S::Error::custom)?
192 .serialize(serializer)
193 }
194}
195
196struct TimestampVisitor;
197
198impl serde::de::Visitor<'_> for TimestampVisitor {
199 type Value = Timestamp;
200
201 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
202 formatter.write_str("a string with a timestamp in RFC 3339 format")
203 }
204
205 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
206 where
207 E: serde::de::Error,
208 {
209 Timestamp::try_from(value).map_err(E::custom)
210 }
211}
212
213impl<'de> serde::de::Deserialize<'de> for Timestamp {
215 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
216 where
217 D: serde::Deserializer<'de>,
218 {
219 deserializer.deserialize_str(TimestampVisitor)
220 }
221}
222
223#[cfg(feature = "time")]
227impl TryFrom<time::OffsetDateTime> for Timestamp {
228 type Error = TimestampError;
229
230 fn try_from(value: time::OffsetDateTime) -> Result<Self, Self::Error> {
231 use time::convert::{Nanosecond, Second};
232
233 let seconds = value.unix_timestamp();
234 let nanos = (value.unix_timestamp_nanos()
235 - seconds as i128 * Nanosecond::per(Second) as i128) as i32;
236 Self::new(seconds, nanos)
237 }
238}
239
240#[cfg(feature = "time")]
244impl TryFrom<Timestamp> for time::OffsetDateTime {
245 type Error = time::error::ComponentRange;
246 fn try_from(value: Timestamp) -> Result<Self, Self::Error> {
247 let ts = time::OffsetDateTime::from_unix_timestamp(value.seconds())?;
248 Ok(ts + time::Duration::nanoseconds(value.nanos() as i64))
249 }
250}
251
252impl TryFrom<&Timestamp> for String {
254 type Error = TimestampError;
255 fn try_from(timestamp: &Timestamp) -> Result<Self, Self::Error> {
256 let ts = time::OffsetDateTime::from_unix_timestamp_nanos(
257 timestamp.seconds as i128 * NS + timestamp.nanos as i128,
258 )
259 .map_err(|e| TimestampError::Serialize(format!("{e}")))?;
260 ts.format(&Rfc3339)
261 .map_err(|e| TimestampError::Serialize(format!("{e}")))
262 }
263}
264
265impl TryFrom<&str> for Timestamp {
267 type Error = TimestampError;
268 fn try_from(value: &str) -> Result<Self, Self::Error> {
269 let odt = time::OffsetDateTime::parse(value, &Rfc3339)
270 .map_err(|e| TimestampError::Deserialize(format!("{e}")))?;
271 let nanos_since_epoch = odt.unix_timestamp_nanos();
272 let seconds = (nanos_since_epoch / NS) as i64;
273 let nanos = (nanos_since_epoch % NS) as i32;
274 if nanos < 0 {
275 return Timestamp::new(seconds - 1, Self::NS + nanos);
276 }
277 Timestamp::new(seconds, nanos)
278 }
279}
280
281#[cfg(feature = "chrono")]
285impl TryFrom<chrono::DateTime<chrono::Utc>> for Timestamp {
286 type Error = TimestampError;
287
288 fn try_from(value: chrono::DateTime<chrono::Utc>) -> Result<Self, Self::Error> {
289 assert!(value.timestamp_subsec_nanos() <= (i32::MAX as u32));
290 Timestamp::new(value.timestamp(), value.timestamp_subsec_nanos() as i32)
291 }
292}
293
294#[cfg(feature = "chrono")]
296impl TryFrom<Timestamp> for chrono::DateTime<chrono::Utc> {
297 type Error = TimestampError;
298 fn try_from(value: Timestamp) -> Result<Self, Self::Error> {
299 let ts = chrono::DateTime::from_timestamp(value.seconds, 0).unwrap();
300 Ok(ts + chrono::Duration::nanoseconds(value.nanos as i64))
301 }
302}
303
304#[cfg(test)]
305mod test {
306 use super::*;
307 use serde_json::json;
308 use test_case::test_case;
309 type Result = std::result::Result<(), Box<dyn std::error::Error>>;
310
311 #[test]
313 fn unix_epoch() -> Result {
314 let proto = Timestamp::default();
315 let json = serde_json::to_value(&proto)?;
316 let expected = json!("1970-01-01T00:00:00Z");
317 assert_eq!(json, expected);
318 let roundtrip = serde_json::from_value::<Timestamp>(json)?;
319 assert_eq!(proto, roundtrip);
320 Ok(())
321 }
322
323 fn get_seconds(input: &str) -> i64 {
324 let odt = time::OffsetDateTime::parse(input, &Rfc3339);
325 let odt = odt.unwrap();
326 odt.unix_timestamp()
327 }
328
329 fn get_min_seconds() -> i64 {
330 self::get_seconds("0001-01-01T00:00:00Z")
331 }
332
333 fn get_max_seconds() -> i64 {
334 self::get_seconds("9999-12-31T23:59:59Z")
335 }
336
337 #[test_case(get_min_seconds() - 1, 0; "seconds below range")]
338 #[test_case(get_max_seconds() + 1, 0; "seconds above range")]
339 #[test_case(0, -1; "nanos below range")]
340 #[test_case(0, 1_000_000_000; "nanos above range")]
341 fn new_out_of_range(seconds: i64, nanos: i32) -> Result {
342 let t = Timestamp::new(seconds, nanos);
343 assert_eq!(t, Err(Error::OutOfRange()));
344 Ok(())
345 }
346
347 #[test_case(0, 0, 0, 0; "zero")]
348 #[test_case(0, 1_234_567_890, 1, 234_567_890; "nanos overflow")]
349 #[test_case(0, 2_100_000_123, 2, 100_000_123; "nanos overflow x2")]
350 #[test_case(0, -1_400_000_000, -2, 600_000_000; "nanos underflow")]
351 #[test_case(0, -2_100_000_000, -3, 900_000_000; "nanos underflow x2")]
352 #[test_case(self::get_max_seconds() + 1, 0, get_max_seconds(), 0; "seconds over range")]
353 #[test_case(self::get_min_seconds() - 1, 0, get_min_seconds(), 0; "seconds below range")]
354 #[test_case(self::get_max_seconds() - 1, 2_000_000_001, get_max_seconds(), 0; "nanos overflow range"
355 )]
356 #[test_case(self::get_min_seconds() + 1, -1_500_000_000, get_min_seconds(), 0; "nanos underflow range"
357 )]
358 fn clamp(seconds: i64, nanos: i32, want_seconds: i64, want_nanos: i32) {
359 let got = Timestamp::clamp(seconds, nanos);
360 let want = Timestamp {
361 seconds: want_seconds,
362 nanos: want_nanos,
363 };
364 assert_eq!(got, want);
365 }
366
367 #[test_case("0001-01-01T00:00:00.123456789Z")]
369 #[test_case("0001-01-01T00:00:00.123456Z")]
370 #[test_case("0001-01-01T00:00:00.123Z")]
371 #[test_case("0001-01-01T00:00:00Z")]
372 #[test_case("1960-01-01T00:00:00.123456789Z")]
373 #[test_case("1960-01-01T00:00:00.123456Z")]
374 #[test_case("1960-01-01T00:00:00.123Z")]
375 #[test_case("1960-01-01T00:00:00Z")]
376 #[test_case("1970-01-01T00:00:00.123456789Z")]
377 #[test_case("1970-01-01T00:00:00.123456Z")]
378 #[test_case("1970-01-01T00:00:00.123Z")]
379 #[test_case("1970-01-01T00:00:00Z")]
380 #[test_case("9999-12-31T23:59:59.999999999Z")]
381 #[test_case("9999-12-31T23:59:59.123456789Z")]
382 #[test_case("9999-12-31T23:59:59.123456Z")]
383 #[test_case("9999-12-31T23:59:59.123Z")]
384 #[test_case("2024-10-19T12:34:56Z")]
385 #[test_case("2024-10-19T12:34:56.789Z")]
386 #[test_case("2024-10-19T12:34:56.789123456Z")]
387 fn roundtrip(input: &str) -> Result {
388 let json = serde_json::Value::String(input.to_string());
389 let timestamp = serde_json::from_value::<Timestamp>(json)?;
390 let roundtrip = serde_json::to_string(×tamp)?;
391 assert_eq!(
392 format!("\"{input}\""),
393 roundtrip,
394 "mismatched value for input={input}"
395 );
396 Ok(())
397 }
398
399 #[test_case(
402 "0001-01-01T00:00:00.123456789Z",
403 Timestamp::clamp(Timestamp::MIN_SECONDS, 123_456_789)
404 )]
405 #[test_case(
406 "0001-01-01T00:00:00.123456Z",
407 Timestamp::clamp(Timestamp::MIN_SECONDS, 123_456_000)
408 )]
409 #[test_case(
410 "0001-01-01T00:00:00.123Z",
411 Timestamp::clamp(Timestamp::MIN_SECONDS, 123_000_000)
412 )]
413 #[test_case("0001-01-01T00:00:00Z", Timestamp::clamp(Timestamp::MIN_SECONDS, 0))]
414 #[test_case("1970-01-01T00:00:00.123456789Z", Timestamp::clamp(0, 123_456_789))]
415 #[test_case("1970-01-01T00:00:00.123456Z", Timestamp::clamp(0, 123_456_000))]
416 #[test_case("1970-01-01T00:00:00.123Z", Timestamp::clamp(0, 123_000_000))]
417 #[test_case("1970-01-01T00:00:00Z", Timestamp::clamp(0, 0))]
418 #[test_case(
419 "9999-12-31T23:59:59.123456789Z",
420 Timestamp::clamp(Timestamp::MAX_SECONDS, 123_456_789)
421 )]
422 #[test_case(
423 "9999-12-31T23:59:59.123456Z",
424 Timestamp::clamp(Timestamp::MAX_SECONDS, 123_456_000)
425 )]
426 #[test_case(
427 "9999-12-31T23:59:59.123Z",
428 Timestamp::clamp(Timestamp::MAX_SECONDS, 123_000_000)
429 )]
430 #[test_case("9999-12-31T23:59:59Z", Timestamp::clamp(Timestamp::MAX_SECONDS, 0))]
431 fn well_known(input: &str, want: Timestamp) -> Result {
432 let json = serde_json::Value::String(input.to_string());
433 let got = serde_json::from_value::<Timestamp>(json)?;
434 assert_eq!(want, got);
435 Ok(())
436 }
437
438 #[test_case("1970-01-01T00:00:00Z", Timestamp::clamp(0, 0); "zulu offset")]
439 #[test_case("1970-01-01T00:00:00+02:00", Timestamp::clamp(-2 * 60 * 60, 0); "2h positive")]
440 #[test_case("1970-01-01T00:00:00+02:45", Timestamp::clamp(-2 * 60 * 60 - 45 * 60, 0); "2h45m positive"
441 )]
442 #[test_case("1970-01-01T00:00:00-02:00", Timestamp::clamp(2 * 60 * 60, 0); "2h negative")]
443 #[test_case("1970-01-01T00:00:00-02:45", Timestamp::clamp(2 * 60 * 60 + 45 * 60, 0); "2h45m negative"
444 )]
445 fn deserialize_offsets(input: &str, want: Timestamp) -> Result {
446 let json = serde_json::Value::String(input.to_string());
447 let got = serde_json::from_value::<Timestamp>(json)?;
448 assert_eq!(want, got);
449 Ok(())
450 }
451
452 #[test_case("0000-01-01T00:00:00Z"; "below range")]
453 #[test_case("10000-01-01T00:00:00Z"; "above range")]
454 fn deserialize_out_of_range(input: &str) -> Result {
455 let value = serde_json::to_value(input)?;
456 let got = serde_json::from_value::<Timestamp>(value);
457 assert!(got.is_err());
458 Ok(())
459 }
460
461 #[test]
462 fn deserialize_unexpected_input_type() -> Result {
463 let got = serde_json::from_value::<Timestamp>(serde_json::json!({}));
464 assert!(got.is_err());
465 let msg = format!("{got:?}");
466 assert!(msg.contains("RFC 3339"), "message={}", msg);
467 Ok(())
468 }
469
470 #[serde_with::skip_serializing_none]
471 #[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)]
472 #[serde(rename_all = "camelCase")]
473 struct Helper {
474 pub create_time: Option<Timestamp>,
475 }
476
477 #[test]
478 fn access() {
479 let ts = Timestamp::default();
480 assert_eq!(ts.nanos(), 0);
481 assert_eq!(ts.seconds(), 0);
482 }
483
484 #[test]
485 fn serialize_in_struct() -> Result {
486 let input = Helper {
487 ..Default::default()
488 };
489 let json = serde_json::to_value(input)?;
490 assert_eq!(json, json!({}));
491
492 let input = Helper {
493 create_time: Some(Timestamp::new(12, 345_678_900)?),
494 };
495
496 let json = serde_json::to_value(input)?;
497 assert_eq!(
498 json,
499 json!({ "createTime": "1970-01-01T00:00:12.3456789Z" })
500 );
501 Ok(())
502 }
503
504 #[test]
505 fn deserialize_in_struct() -> Result {
506 let input = json!({});
507 let want = Helper {
508 ..Default::default()
509 };
510 let got = serde_json::from_value::<Helper>(input)?;
511 assert_eq!(want, got);
512
513 let input = json!({ "createTime": "1970-01-01T00:00:12.3456789Z" });
514 let want = Helper {
515 create_time: Some(Timestamp::new(12, 345678900)?),
516 };
517 let got = serde_json::from_value::<Helper>(input)?;
518 assert_eq!(want, got);
519 Ok(())
520 }
521
522 #[test]
523 fn compare() -> Result {
524 let ts0 = Timestamp::default();
525 let ts1 = Timestamp::new(1, 100)?;
526 let ts2 = Timestamp::new(1, 200)?;
527 let ts3 = Timestamp::new(2, 0)?;
528 assert_eq!(ts0.partial_cmp(&ts0), Some(std::cmp::Ordering::Equal));
529 assert_eq!(ts0.partial_cmp(&ts1), Some(std::cmp::Ordering::Less));
530 assert_eq!(ts2.partial_cmp(&ts3), Some(std::cmp::Ordering::Less));
531 Ok(())
532 }
533
534 #[test]
535 fn convert_from_time() -> Result {
536 let ts = time::OffsetDateTime::from_unix_timestamp(123)?
537 + time::Duration::nanoseconds(456789012);
538 let got = Timestamp::try_from(ts)?;
539 let want = Timestamp::new(123, 456789012)?;
540 assert_eq!(got, want);
541 Ok(())
542 }
543
544 #[test]
545 fn convert_to_time() -> Result {
546 let ts = Timestamp::new(123, 456789012)?;
547 let got = time::OffsetDateTime::try_from(ts)?;
548 let want = time::OffsetDateTime::from_unix_timestamp(123)?
549 + time::Duration::nanoseconds(456789012);
550 assert_eq!(got, want);
551 Ok(())
552 }
553
554 #[test]
555 fn convert_from_chrono_time() -> Result {
556 let ts = chrono::DateTime::from_timestamp(123, 456789012).unwrap();
557 let got = Timestamp::try_from(ts)?;
558 let want = Timestamp::new(123, 456789012)?;
559 assert_eq!(got, want);
560 Ok(())
561 }
562
563 #[test]
564 fn convert_to_chrono_time() -> Result {
565 let ts = Timestamp::new(123, 456789012)?;
566 let got = chrono::DateTime::try_from(ts)?;
567 let want = chrono::DateTime::from_timestamp(123, 456789012).unwrap();
568 assert_eq!(got, want);
569 Ok(())
570 }
571}