tenderdash_proto/serializers/
timestamp.rs1#[cfg(not(feature = "std"))]
3use core::fmt::{self, Debug};
4#[cfg(feature = "std")]
5use std::fmt::{self, Debug};
6
7use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _, ser::Error};
8use time::{
9 OffsetDateTime, format_description::well_known::Rfc3339 as Rfc3339Format, macros::offset,
10};
11
12use crate::{google::protobuf::Timestamp, prelude::*};
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
32#[deprecated = "use crate::prelude::FromMillis instead"]
34pub use crate::time::FromMillis as FromMilis;
35#[deprecated = "use crate::prelude::ToMillis instead"]
36pub use crate::time::ToMillis as ToMilis;
37
38pub fn deserialize<'de, D>(deserializer: D) -> Result<Timestamp, D::Error>
40where
41 D: Deserializer<'de>,
42{
43 let value_string = String::deserialize(deserializer)?;
44 let t = OffsetDateTime::parse(&value_string, &Rfc3339Format).map_err(D::Error::custom)?;
45 let t = t.to_offset(offset!(UTC));
46 if !matches!(t.year(), 1..=9999) {
47 return Err(D::Error::custom("date is out of range"));
48 }
49 let seconds = t.unix_timestamp();
50 let nanos = t.nanosecond() as i32;
53 Ok(Timestamp { seconds, nanos })
54}
55
56pub fn serialize<S>(value: &Timestamp, serializer: S) -> Result<S::Ok, S::Error>
58where
59 S: Serializer,
60{
61 if value.nanos < 0 || value.nanos > 999_999_999 {
62 return Err(S::Error::custom("invalid nanoseconds in time"));
63 }
64 let total_nanos = value.seconds as i128 * 1_000_000_000 + value.nanos as i128;
65 let datetime = OffsetDateTime::from_unix_timestamp_nanos(total_nanos)
66 .map_err(|_| S::Error::custom("invalid time"))?;
67 to_rfc3339_nanos(datetime).serialize(serializer)
68}
69
70pub fn to_rfc3339_nanos(t: OffsetDateTime) -> String {
77 let mut buf = String::with_capacity(20);
83
84 fmt_as_rfc3339_nanos(t, &mut buf).unwrap();
85
86 buf
87}
88
89pub fn fmt_as_rfc3339_nanos(t: OffsetDateTime, f: &mut impl fmt::Write) -> fmt::Result {
101 let t = t.to_offset(offset!(UTC));
102 let nanos = t.nanosecond();
103 if nanos == 0 {
104 write!(
105 f,
106 "{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}Z",
107 year = t.year(),
108 month = t.month() as u8,
109 day = t.day(),
110 hour = t.hour(),
111 minute = t.minute(),
112 second = t.second(),
113 )
114 } else {
115 let mut secfrac = nanos;
116 let mut secfrac_width = 9;
117 while secfrac % 10 == 0 {
118 secfrac /= 10;
119 secfrac_width -= 1;
120 }
121 write!(
122 f,
123 "{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}.{secfrac:0sfw$}Z",
124 year = t.year(),
125 month = t.month() as u8,
126 day = t.day(),
127 hour = t.hour(),
128 minute = t.minute(),
129 second = t.second(),
130 secfrac = secfrac,
131 sfw = secfrac_width,
132 )
133 }
134}
135
136#[allow(warnings)]
137#[cfg(test)]
138mod test {
139 use serde::{Deserialize, Serialize};
140
141 use super::*;
142 use crate::google::protobuf::Timestamp;
143
144 #[test]
183 fn json_timestamp_precision() {
184 let test_timestamps = vec![
185 "1970-01-01T00:00:00Z",
186 "0001-01-01T00:00:00Z",
187 "2020-09-14T16:33:00Z",
188 "2020-09-14T16:33:00.1Z",
189 "2020-09-14T16:33:00.211914212Z",
190 "2020-09-14T16:33:54.21191421Z",
191 "2021-01-07T20:25:56.045576Z",
192 "2021-01-07T20:25:57.039219Z",
193 "2021-01-07T20:26:05.00509Z",
194 "2021-01-07T20:26:05.005096Z",
195 "2021-01-07T20:26:05.0005096Z",
196 ];
197
198 for timestamp in test_timestamps {
199 let json = format!("\"{}\"", timestamp);
200 let rfc = serde_json::from_str::<Rfc3339>(&json).unwrap();
201 assert_eq!(json, serde_json::to_string(&rfc).unwrap());
202 }
203 }
204
205 #[test]
206 fn timestamp_from_to() {
207 let time_ms = 1687848809533;
208
209 let from = Timestamp::from_milis(time_ms);
210 let to = from.to_milis();
211
212 assert_eq!(to, time_ms);
213 }
214
215 #[test]
216 #[should_panic]
217 fn timestamp_millis_out_of_range() {
218 let time_ms = u64::MAX - 1;
219
220 let from = Timestamp::from_milis(time_ms);
221 }
222
223 #[test]
224 #[should_panic]
225 fn timestamp_negative() {
226 let ts = Timestamp {
227 nanos: 1000,
228 seconds: -12,
229 };
230
231 let to = ts.to_milis();
232 }
233}