proto_types/duration/
base.rs

1// From (prost-types)[https://github.com/tokio-rs/prost/blob/master/prost-types/src/duration.rs]
2use super::super::*;
3
4impl Duration {
5  /// Normalizes the duration to a canonical format.
6  ///
7  /// Based on [`google::protobuf::util::CreateNormalized`][1].
8  ///
9  /// [1]: https://github.com/google/protobuf/blob/v3.3.2/src/google/protobuf/util/time_util.cc#L79-L100
10  pub fn normalize(&mut self) {
11    // Make sure nanos is in the range.
12    if self.nanos <= -NANOS_PER_SECOND || self.nanos >= NANOS_PER_SECOND {
13      if let Some(seconds) = self
14        .seconds
15        .checked_add((self.nanos / NANOS_PER_SECOND) as i64)
16      {
17        self.seconds = seconds;
18
19        self.nanos %= NANOS_PER_SECOND;
20      } else if self.nanos < 0 {
21        // Negative overflow! Set to the least normal value.
22
23        self.seconds = i64::MIN;
24
25        self.nanos = -NANOS_MAX;
26      } else {
27        // Positive overflow! Set to the greatest normal value.
28
29        self.seconds = i64::MAX;
30
31        self.nanos = NANOS_MAX;
32      }
33    }
34
35    // nanos should have the same sign as seconds.
36
37    if self.seconds < 0 && self.nanos > 0 {
38      if let Some(seconds) = self.seconds.checked_add(1) {
39        self.seconds = seconds;
40
41        self.nanos -= NANOS_PER_SECOND;
42      } else {
43        // Positive overflow! Set to the greatest normal value.
44
45        debug_assert_eq!(self.seconds, i64::MAX);
46
47        self.nanos = NANOS_MAX;
48      }
49    } else if self.seconds > 0 && self.nanos < 0 {
50      if let Some(seconds) = self.seconds.checked_sub(1) {
51        self.seconds = seconds;
52
53        self.nanos += NANOS_PER_SECOND;
54      } else {
55        // Negative overflow! Set to the least normal value.
56
57        debug_assert_eq!(self.seconds, i64::MIN);
58
59        self.nanos = -NANOS_MAX;
60      }
61    }
62
63    // TODO: should this be checked?
64
65    // debug_assert!(self.seconds >= -315_576_000_000 && self.seconds <= 315_576_000_000,
66
67    //               "invalid duration: {:?}", self);
68  }
69
70  /// Returns a normalized copy of the duration to a canonical format.
71  ///
72  /// Based on [`google::protobuf::util::CreateNormalized`][1].
73  ///
74  /// [1]: https://github.com/google/protobuf/blob/v3.3.2/src/google/protobuf/util/time_util.cc#L79-L100
75  pub fn normalized(&self) -> Self {
76    let mut result = *self;
77
78    result.normalize();
79
80    result
81  }
82}
83
84impl Name for Duration {
85  const PACKAGE: &'static str = PACKAGE_PREFIX;
86
87  const NAME: &'static str = "Duration";
88
89  fn type_url() -> String {
90    type_url_for::<Self>()
91  }
92}
93
94impl TryFrom<time::Duration> for Duration {
95  type Error = DurationError;
96
97  /// Converts a `std::time::Duration` to a `Duration`, failing if the duration is too large.
98  fn try_from(duration: time::Duration) -> Result<Duration, DurationError> {
99    let seconds = i64::try_from(duration.as_secs()).map_err(|_| DurationError::OutOfRange)?;
100
101    let nanos = duration.subsec_nanos() as i32;
102
103    let duration = Duration { seconds, nanos };
104
105    Ok(duration.normalized())
106  }
107}
108
109impl TryFrom<Duration> for time::Duration {
110  type Error = DurationError;
111
112  /// Converts a `Duration` to a `std::time::Duration`, failing if the duration is negative.
113  fn try_from(mut duration: Duration) -> Result<time::Duration, DurationError> {
114    duration.normalize();
115
116    if duration.seconds >= 0 && duration.nanos >= 0 {
117      Ok(time::Duration::new(
118        duration.seconds as u64,
119        duration.nanos as u32,
120      ))
121    } else {
122      Err(DurationError::NegativeDuration(time::Duration::new(
123        (-duration.seconds) as u64,
124        (-duration.nanos) as u32,
125      )))
126    }
127  }
128}
129
130/// A duration handling error.
131#[derive(Debug, PartialEq)]
132#[non_exhaustive]
133pub enum DurationError {
134  /// Indicates failure to parse a [`Duration`] from a string.
135  ///
136  /// The [`Duration`] string format is specified in the [Protobuf JSON mapping specification][1].
137  ///
138  /// [1]: https://developers.google.com/protocol-buffers/docs/proto3#json
139  ParseFailure,
140
141  /// Indicates failure to convert a `prost_types::Duration` to a `std::time::Duration` because
142  /// the duration is negative. The included `std::time::Duration` matches the magnitude of the
143  /// original negative `prost_types::Duration`.
144  NegativeDuration(time::Duration),
145
146  /// Indicates failure to convert a `std::time::Duration` to a `prost_types::Duration`.
147  ///
148  /// Converting a `std::time::Duration` to a `prost_types::Duration` fails if the magnitude
149  /// exceeds that representable by `prost_types::Duration`.
150  OutOfRange,
151}
152
153impl fmt::Display for DurationError {
154  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155    match self {
156      DurationError::ParseFailure => write!(f, "failed to parse duration"),
157
158      DurationError::NegativeDuration(duration) => {
159        write!(f, "failed to convert negative duration: {:?}", duration)
160      }
161
162      DurationError::OutOfRange => {
163        write!(f, "failed to convert duration out of range")
164      }
165    }
166  }
167}
168
169impl std::error::Error for DurationError {}
170
171impl FromStr for Duration {
172  type Err = DurationError;
173
174  fn from_str(s: &str) -> Result<Duration, DurationError> {
175    datetime::parse_duration(s).ok_or(DurationError::ParseFailure)
176  }
177}
178
179#[cfg(feature = "chrono")]
180mod chrono {
181  use chrono::TimeDelta;
182
183  use crate::{Duration, DurationError};
184
185  impl From<::chrono::TimeDelta> for Duration {
186    fn from(value: ::chrono::TimeDelta) -> Self {
187      let mut result = Self {
188        seconds: value.num_seconds(),
189
190        nanos: value.subsec_nanos(),
191      };
192
193      result.normalize();
194
195      result
196    }
197  }
198
199  impl TryFrom<Duration> for ::chrono::TimeDelta {
200    type Error = DurationError;
201
202    fn try_from(mut value: Duration) -> Result<TimeDelta, DurationError> {
203      value.normalize();
204
205      let seconds = TimeDelta::try_seconds(value.seconds).ok_or(DurationError::OutOfRange)?;
206
207      let nanos = TimeDelta::nanoseconds(value.nanos.into());
208
209      seconds.checked_add(&nanos).ok_or(DurationError::OutOfRange)
210    }
211  }
212}