tendermint_proto/serializers/
timestamp.rs1use core::fmt;
4
5use serde::{de::Error as _, ser::Error, Deserialize, Deserializer, Serialize, Serializer};
6use time::{
7 format_description::well_known::Rfc3339 as Rfc3339Format, macros::offset, OffsetDateTime,
8};
9
10use crate::{google::protobuf::Timestamp, prelude::*};
11
12#[derive(Debug, Serialize, Deserialize)]
16#[serde(transparent)]
17pub struct Rfc3339(#[serde(with = "crate::serializers::timestamp")] Timestamp);
18
19impl From<Timestamp> for Rfc3339 {
20 fn from(value: Timestamp) -> Self {
21 Rfc3339(value)
22 }
23}
24impl From<Rfc3339> for Timestamp {
25 fn from(value: Rfc3339) -> Self {
26 value.0
27 }
28}
29
30pub fn deserialize<'de, D>(deserializer: D) -> Result<Timestamp, D::Error>
32where
33 D: Deserializer<'de>,
34{
35 let value_string = String::deserialize(deserializer)?;
36 let t = OffsetDateTime::parse(&value_string, &Rfc3339Format).map_err(D::Error::custom)?;
37 let t = t.to_offset(offset!(UTC));
38 if !matches!(t.year(), 1..=9999) {
39 return Err(D::Error::custom("date is out of range"));
40 }
41 let seconds = t.unix_timestamp();
42 let nanos = t.nanosecond() as i32;
45 Ok(Timestamp { seconds, nanos })
46}
47
48pub fn serialize<S>(value: &Timestamp, serializer: S) -> Result<S::Ok, S::Error>
50where
51 S: Serializer,
52{
53 if value.nanos < 0 || value.nanos > 999_999_999 {
54 return Err(S::Error::custom("invalid nanoseconds in time"));
55 }
56 let total_nanos = value.seconds as i128 * 1_000_000_000 + value.nanos as i128;
57 let datetime = OffsetDateTime::from_unix_timestamp_nanos(total_nanos)
58 .map_err(|_| S::Error::custom("invalid time"))?;
59 to_rfc3339_nanos(datetime).serialize(serializer)
60}
61
62pub fn to_rfc3339_nanos(t: OffsetDateTime) -> String {
68 let mut buf = String::with_capacity(20);
74
75 fmt_as_rfc3339_nanos(t, &mut buf).unwrap();
76
77 buf
78}
79
80pub fn fmt_as_rfc3339_nanos(t: OffsetDateTime, f: &mut impl fmt::Write) -> fmt::Result {
92 let t = t.to_offset(offset!(UTC));
93 let nanos = t.nanosecond();
94 if nanos == 0 {
95 write!(
96 f,
97 "{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}Z",
98 year = t.year(),
99 month = t.month() as u8,
100 day = t.day(),
101 hour = t.hour(),
102 minute = t.minute(),
103 second = t.second(),
104 )
105 } else {
106 let mut secfrac = nanos;
107 let mut secfrac_width = 9;
108 while secfrac % 10 == 0 {
109 secfrac /= 10;
110 secfrac_width -= 1;
111 }
112 write!(
113 f,
114 "{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}.{secfrac:0sfw$}Z",
115 year = t.year(),
116 month = t.month() as u8,
117 day = t.day(),
118 hour = t.hour(),
119 minute = t.minute(),
120 second = t.second(),
121 secfrac = secfrac,
122 sfw = secfrac_width,
123 )
124 }
125}
126
127#[allow(warnings)]
128#[cfg(test)]
129mod test {
130 use serde::{Deserialize, Serialize};
131
132 use super::*;
133 use crate::google::protobuf::Timestamp;
134
135 #[test]
174 fn json_timestamp_precision() {
175 let test_timestamps = vec![
176 "1970-01-01T00:00:00Z",
177 "0001-01-01T00:00:00Z",
178 "2020-09-14T16:33:00Z",
179 "2020-09-14T16:33:00.1Z",
180 "2020-09-14T16:33:00.211914212Z",
181 "2020-09-14T16:33:54.21191421Z",
182 "2021-01-07T20:25:56.045576Z",
183 "2021-01-07T20:25:57.039219Z",
184 "2021-01-07T20:26:05.00509Z",
185 "2021-01-07T20:26:05.005096Z",
186 "2021-01-07T20:26:05.0005096Z",
187 ];
188
189 for timestamp in test_timestamps {
190 let json = format!("\"{}\"", timestamp);
191 let rfc = serde_json::from_str::<Rfc3339>(&json).unwrap();
192 assert_eq!(json, serde_json::to_string(&rfc).unwrap());
193 }
194 }
195}