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