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