tendermint_proto/google/protobuf/
duration.rs

1// Original code from <https://github.com/influxdata/pbjson/blob/main/pbjson-types/src/duration.rs>
2// Copyright 2022 Dan Burkert & Tokio Contributors
3//
4// Original serialization code from <https://github.com/influxdata/pbjson/blob/main/pbjson-types/src/duration.rs>
5// Copyright (c) 2020 InfluxData
6
7use core::convert::TryFrom;
8use core::fmt;
9
10use prost::Name;
11
12use crate::prelude::*;
13
14use super::type_url::type_url_for;
15use super::PACKAGE;
16
17/// A Duration represents a signed, fixed-length span of time represented
18/// as a count of seconds and fractions of seconds at nanosecond
19/// resolution. It is independent of any calendar and concepts like "day"
20/// or "month". It is related to Timestamp in that the difference between
21/// two Timestamp values is a Duration and it can be added or subtracted
22/// from a Timestamp. Range is approximately +-10,000 years.
23#[derive(Copy, Clone, PartialEq, Eq, ::prost::Message)]
24#[cfg_attr(feature = "json-schema", derive(::schemars::JsonSchema))]
25pub struct Duration {
26    /// Signed seconds of the span of time. Must be from -315,576,000,000
27    /// to +315,576,000,000 inclusive. Note: these bounds are computed from:
28    /// 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
29    #[prost(int64, tag = "1")]
30    pub seconds: i64,
31    /// Signed fractions of a second at nanosecond resolution of the span
32    /// of time. Durations less than one second are represented with a 0
33    /// `seconds` field and a positive or negative `nanos` field. For durations
34    /// of one second or more, a non-zero value for the `nanos` field must be
35    /// of the same sign as the `seconds` field. Must be from -999,999,999
36    /// to +999,999,999 inclusive.
37    #[prost(int32, tag = "2")]
38    pub nanos: i32,
39}
40
41impl Name for Duration {
42    const PACKAGE: &'static str = PACKAGE;
43    const NAME: &'static str = "Duration";
44
45    fn type_url() -> String {
46        type_url_for::<Self>()
47    }
48}
49
50const NANOS_PER_SECOND: i32 = 1_000_000_000;
51const NANOS_MAX: i32 = NANOS_PER_SECOND - 1;
52
53impl Duration {
54    /// Normalizes the duration to a canonical format.
55    pub fn normalize(&mut self) {
56        // Make sure nanos is in the range.
57        if self.nanos <= -NANOS_PER_SECOND || self.nanos >= NANOS_PER_SECOND {
58            if let Some(seconds) = self
59                .seconds
60                .checked_add((self.nanos / NANOS_PER_SECOND) as i64)
61            {
62                self.seconds = seconds;
63                self.nanos %= NANOS_PER_SECOND;
64            } else if self.nanos < 0 {
65                // Negative overflow! Set to the least normal value.
66                self.seconds = i64::MIN;
67                self.nanos = -NANOS_MAX;
68            } else {
69                // Positive overflow! Set to the greatest normal value.
70                self.seconds = i64::MAX;
71                self.nanos = NANOS_MAX;
72            }
73        }
74
75        // nanos should have the same sign as seconds.
76        if self.seconds < 0 && self.nanos > 0 {
77            if let Some(seconds) = self.seconds.checked_add(1) {
78                self.seconds = seconds;
79                self.nanos -= NANOS_PER_SECOND;
80            } else {
81                // Positive overflow! Set to the greatest normal value.
82                debug_assert_eq!(self.seconds, i64::MAX);
83                self.nanos = NANOS_MAX;
84            }
85        } else if self.seconds > 0 && self.nanos < 0 {
86            if let Some(seconds) = self.seconds.checked_sub(1) {
87                self.seconds = seconds;
88                self.nanos += NANOS_PER_SECOND;
89            } else {
90                // Negative overflow! Set to the least normal value.
91                debug_assert_eq!(self.seconds, i64::MIN);
92                self.nanos = -NANOS_MAX;
93            }
94        }
95    }
96}
97
98/// A duration handling error.
99#[derive(Debug, PartialEq, Eq)]
100#[non_exhaustive]
101pub enum DurationError {
102    /// Indicates failure to convert a [`Duration`] to a [`core::time::Duration`] because
103    /// the duration is negative. The included [`core::time::Duration`] matches the magnitude of the
104    /// original negative [`Duration`].
105    NegativeDuration(core::time::Duration),
106
107    /// Indicates failure to convert a [`core::time::Duration`] to a [`Duration`].
108    ///
109    /// Converting a [`core::time::Duration`] to a [`Duration`] fails if the magnitude
110    /// exceeds that representable by [`Duration`].
111    OutOfRange,
112}
113
114impl fmt::Display for DurationError {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        match self {
117            DurationError::NegativeDuration(duration) => {
118                write!(f, "failed to convert negative duration: {duration:?}")
119            },
120            DurationError::OutOfRange => {
121                write!(f, "failed to convert duration out of range")
122            },
123        }
124    }
125}
126
127#[cfg(feature = "std")]
128impl std::error::Error for DurationError {}
129
130impl TryFrom<Duration> for core::time::Duration {
131    type Error = DurationError;
132
133    /// Converts a `Duration` to a `core::time::Duration`, failing if the duration is negative.
134    fn try_from(mut duration: Duration) -> Result<core::time::Duration, DurationError> {
135        duration.normalize();
136        if duration.seconds >= 0 && duration.nanos >= 0 {
137            Ok(core::time::Duration::new(
138                duration.seconds as u64,
139                duration.nanos as u32,
140            ))
141        } else {
142            Err(DurationError::NegativeDuration(core::time::Duration::new(
143                (-duration.seconds) as u64,
144                (-duration.nanos) as u32,
145            )))
146        }
147    }
148}
149
150impl TryFrom<core::time::Duration> for Duration {
151    type Error = DurationError;
152
153    /// Converts a `core::time::Duration` to a `Duration`, failing if the duration is too large.
154    fn try_from(duration: core::time::Duration) -> Result<Duration, DurationError> {
155        let seconds = i64::try_from(duration.as_secs()).map_err(|_| DurationError::OutOfRange)?;
156        let nanos = duration.subsec_nanos() as i32;
157
158        let mut duration = Duration { seconds, nanos };
159        duration.normalize();
160        Ok(duration)
161    }
162}
163
164impl serde::Serialize for Duration {
165    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
166    where
167        S: serde::Serializer,
168    {
169        if self.seconds != 0 && self.nanos != 0 && (self.nanos < 0) != (self.seconds < 0) {
170            return Err(serde::ser::Error::custom("Duration has inconsistent signs"));
171        }
172
173        let mut s = if self.seconds == 0 {
174            if self.nanos < 0 {
175                "-0".to_string()
176            } else {
177                "0".to_string()
178            }
179        } else {
180            self.seconds.to_string()
181        };
182
183        if self.nanos != 0 {
184            s.push('.');
185            let f = match split_nanos(self.nanos.unsigned_abs()) {
186                (millis, 0, 0) => format!("{:03}", millis),
187                (millis, micros, 0) => format!("{:03}{:03}", millis, micros),
188                (millis, micros, nanos) => format!("{:03}{:03}{:03}", millis, micros, nanos),
189            };
190            s.push_str(&f);
191        }
192
193        s.push('s');
194        serializer.serialize_str(&s)
195    }
196}
197
198struct DurationVisitor;
199
200impl serde::de::Visitor<'_> for DurationVisitor {
201    type Value = Duration;
202
203    fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
204        formatter.write_str("a duration string")
205    }
206
207    fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
208    where
209        E: serde::de::Error,
210    {
211        let s = s
212            .strip_suffix('s')
213            .ok_or_else(|| serde::de::Error::custom("missing 's' suffix"))?;
214
215        let (negative, s) = match s.strip_prefix('-') {
216            Some(s) => (true, s),
217            None => (false, s),
218        };
219
220        let duration = match s.split_once('.') {
221            Some((seconds_str, decimal_str)) => {
222                let exp = 9_u32
223                    .checked_sub(decimal_str.len() as u32)
224                    .ok_or_else(|| serde::de::Error::custom("too many decimal places"))?;
225
226                let pow = 10_u32.pow(exp);
227                let seconds = seconds_str.parse().map_err(serde::de::Error::custom)?;
228                let decimal: u32 = decimal_str.parse().map_err(serde::de::Error::custom)?;
229
230                Duration {
231                    seconds,
232                    nanos: (decimal * pow) as i32,
233                }
234            },
235            None => Duration {
236                seconds: s.parse().map_err(serde::de::Error::custom)?,
237                nanos: 0,
238            },
239        };
240
241        Ok(match negative {
242            true => Duration {
243                seconds: -duration.seconds,
244                nanos: -duration.nanos,
245            },
246            false => duration,
247        })
248    }
249}
250
251impl<'de> serde::Deserialize<'de> for Duration {
252    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
253    where
254        D: serde::Deserializer<'de>,
255    {
256        deserializer.deserialize_str(DurationVisitor)
257    }
258}
259
260/// Splits nanoseconds into whole milliseconds, microseconds, and nanoseconds
261fn split_nanos(mut nanos: u32) -> (u32, u32, u32) {
262    let millis = nanos / 1_000_000;
263    nanos -= millis * 1_000_000;
264    let micros = nanos / 1_000;
265    nanos -= micros * 1_000;
266    (millis, micros, nanos)
267}
268
269#[cfg(test)]
270mod tests {
271    use super::*;
272
273    #[test]
274    fn test_duration() {
275        let verify = |duration: &Duration, expected: &str| {
276            assert_eq!(serde_json::to_string(duration).unwrap().as_str(), expected);
277            assert_eq!(
278                &serde_json::from_str::<Duration>(expected).unwrap(),
279                duration
280            )
281        };
282
283        let duration = Duration {
284            seconds: 0,
285            nanos: 0,
286        };
287        verify(&duration, "\"0s\"");
288
289        let duration = Duration {
290            seconds: 0,
291            nanos: 123,
292        };
293        verify(&duration, "\"0.000000123s\"");
294
295        let duration = Duration {
296            seconds: 0,
297            nanos: 123456,
298        };
299        verify(&duration, "\"0.000123456s\"");
300
301        let duration = Duration {
302            seconds: 0,
303            nanos: 123456789,
304        };
305        verify(&duration, "\"0.123456789s\"");
306
307        let duration = Duration {
308            seconds: 0,
309            nanos: -67088,
310        };
311        verify(&duration, "\"-0.000067088s\"");
312
313        let duration = Duration {
314            seconds: 121,
315            nanos: 3454,
316        };
317        verify(&duration, "\"121.000003454s\"");
318
319        let duration = Duration {
320            seconds: -90,
321            nanos: -2456301,
322        };
323        verify(&duration, "\"-90.002456301s\"");
324
325        let duration = Duration {
326            seconds: -90,
327            nanos: 234,
328        };
329        serde_json::to_string(&duration).unwrap_err();
330
331        let duration = Duration {
332            seconds: 90,
333            nanos: -234,
334        };
335        serde_json::to_string(&duration).unwrap_err();
336
337        serde_json::from_str::<Duration>("90.1234567891s").unwrap_err();
338    }
339}