prost_wkt_types/pbtime/
duration.rs1use super::*;
2
3#[cfg(feature = "std")]
8impl std::hash::Hash for Duration {
9 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
10 self.seconds.hash(state);
11 self.nanos.hash(state);
12 }
13}
14
15impl Duration {
16 pub fn normalize(&mut self) {
22 if self.nanos <= -NANOS_PER_SECOND || self.nanos >= NANOS_PER_SECOND {
24 if let Some(seconds) = self
25 .seconds
26 .checked_add((self.nanos / NANOS_PER_SECOND) as i64)
27 {
28 self.seconds = seconds;
29 self.nanos %= NANOS_PER_SECOND;
30 } else if self.nanos < 0 {
31 self.seconds = i64::MIN;
33 self.nanos = -NANOS_MAX;
34 } else {
35 self.seconds = i64::MAX;
37 self.nanos = NANOS_MAX;
38 }
39 }
40
41 if self.seconds < 0 && self.nanos > 0 {
43 if let Some(seconds) = self.seconds.checked_add(1) {
44 self.seconds = seconds;
45 self.nanos -= NANOS_PER_SECOND;
46 } else {
47 debug_assert_eq!(self.seconds, i64::MAX);
49 self.nanos = NANOS_MAX;
50 }
51 } else if self.seconds > 0 && self.nanos < 0 {
52 if let Some(seconds) = self.seconds.checked_sub(1) {
53 self.seconds = seconds;
54 self.nanos += NANOS_PER_SECOND;
55 } else {
56 debug_assert_eq!(self.seconds, i64::MIN);
58 self.nanos = -NANOS_MAX;
59 }
60 }
61 }
65}
66
67impl TryFrom<time::Duration> for Duration {
77 type Error = DurationError;
78
79 fn try_from(duration: time::Duration) -> Result<Duration, DurationError> {
81 let seconds = i64::try_from(duration.as_secs()).map_err(|_| DurationError::OutOfRange)?;
82 let nanos = duration.subsec_nanos() as i32;
83
84 let mut duration = Duration { seconds, nanos };
85 duration.normalize();
86 Ok(duration)
87 }
88}
89
90impl TryFrom<Duration> for time::Duration {
91 type Error = DurationError;
92
93 fn try_from(mut duration: Duration) -> Result<time::Duration, DurationError> {
95 duration.normalize();
96 if duration.seconds >= 0 && duration.nanos >= 0 {
97 Ok(time::Duration::new(
98 duration.seconds as u64,
99 duration.nanos as u32,
100 ))
101 } else {
102 Err(DurationError::NegativeDuration(time::Duration::new(
103 (-duration.seconds) as u64,
104 (-duration.nanos) as u32,
105 )))
106 }
107 }
108}
109
110impl fmt::Display for Duration {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 let mut d = *self;
113 d.normalize();
114 if self.seconds < 0 && self.nanos < 0 {
115 write!(f, "-")?;
116 }
117 write!(f, "{}", d.seconds.abs())?;
118
119 let nanos = d.nanos.abs();
121 if nanos == 0 {
122 write!(f, "s")
123 } else if nanos % 1_000_000 == 0 {
124 write!(f, ".{:03}s", nanos / 1_000_000)
125 } else if nanos % 1_000 == 0 {
126 write!(f, ".{:06}s", nanos / 1_000)
127 } else {
128 write!(f, ".{:09}s", nanos)
129 }
130 }
131}
132
133#[allow(clippy::derive_partial_eq_without_eq)]
135#[derive(Debug, PartialEq)]
136#[non_exhaustive]
137pub enum DurationError {
138 ParseFailure,
144
145 NegativeDuration(time::Duration),
149
150 OutOfRange,
155}
156
157impl fmt::Display for DurationError {
158 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159 match self {
160 DurationError::ParseFailure => write!(f, "failed to parse duration"),
161 DurationError::NegativeDuration(duration) => {
162 write!(f, "failed to convert negative duration: {:?}", duration)
163 }
164 DurationError::OutOfRange => {
165 write!(f, "failed to convert duration out of range")
166 }
167 }
168 }
169}
170
171#[cfg(feature = "std")]
172impl std::error::Error for DurationError {}
173
174impl FromStr for Duration {
175 type Err = DurationError;
176
177 fn from_str(s: &str) -> Result<Duration, DurationError> {
178 datetime::parse_duration(s).ok_or(DurationError::ParseFailure)
179 }
180}
181
182impl From<Duration> for chrono::Duration {
188 fn from(val: Duration) -> Self {
189 let mut value = val;
190 value.normalize();
195 let s = chrono::TimeDelta::try_seconds(value.seconds).expect("invalid or out-of-range seconds");
196 let ns = chrono::Duration::nanoseconds(value.nanos as i64);
197 s + ns
198 }
199}
200
201impl From<chrono::Duration> for Duration {
203 fn from(val: chrono::Duration) -> Self {
204 Duration {
205 seconds: val.num_seconds(),
206 nanos: (val.num_nanoseconds().unwrap() % 1_000_000_000) as i32,
207 }
208 }
209}
210
211impl Serialize for Duration {
212 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
213 where
214 S: Serializer,
215 {
216 let seconds: f64 = self.seconds as f64 + self.nanos as f64 / NANOS_PER_SECOND as f64;
217 serializer.serialize_str(&format!("{:.9}s", seconds))
222 }
223}
224
225impl<'de> Deserialize<'de> for Duration {
226 fn deserialize<D>(deserializer: D) -> Result<Duration, D::Error>
227 where
228 D: Deserializer<'de>,
229 {
230 struct DurationVisitor;
231
232 impl<'de> de::Visitor<'de> for DurationVisitor {
233 type Value = Duration;
234
235 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
236 formatter.write_str("A duration ending in 's'")
237 }
238
239 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
240 where
241 E: de::Error,
242 {
243 if !value.ends_with('s') {
244 return Err(de::Error::custom("Duration should end with 's'"));
245 }
246
247 let duration_str = &value[..value.len() - 1]; let mut parts = duration_str.split('.'); let seconds: i64 = parts
252 .next()
253 .ok_or_else(|| de::Error::custom("Missing seconds"))?
254 .parse()
255 .map_err(de::Error::custom)?;
256
257 let nanos: i32 = match parts.next() {
258 Some(fraction) => {
259 let fraction = format!("{:0<9}", fraction); let nanos = fraction.parse().map_err(de::Error::custom)?;
261 if nanos < 0 || nanos >= NANOS_PER_SECOND as i32 {
262 return Err(de::Error::custom(format!(
263 "Fractional nanoseconds out of range: {}",
264 nanos
265 )));
266 }
267 nanos
268 }
269 None => 0,
270 };
271
272 Ok(Duration { seconds, nanos })
273 }
274 }
275
276 deserializer.deserialize_str(DurationVisitor)
277 }
278}
279
280
281