1use crate::Duration;
2use core::cmp::Ordering;
3use core::ops::{Add, Div, Mul, Sub};
4use core::time::Duration as StdDuration;
5
6impl PartialEq<StdDuration> for Duration {
7 #[inline]
8 fn eq(&self, other: &StdDuration) -> bool {
9 self.total_nanos() == other.as_nanos().cast_signed()
10 }
11}
12
13impl PartialEq<Duration> for StdDuration {
14 #[inline]
15 fn eq(&self, other: &Duration) -> bool {
16 other == self
17 }
18}
19
20impl PartialOrd<StdDuration> for Duration {
21 #[inline]
22 fn partial_cmp(&self, other: &StdDuration) -> Option<Ordering> {
23 Some(
24 self.total_nanos()
25 .cmp(&other.as_nanos().cast_signed()),
26 )
27 }
28}
29
30impl PartialOrd<Duration> for StdDuration {
31 #[inline]
32 fn partial_cmp(&self, other: &Duration) -> Option<Ordering> {
33 other.partial_cmp(self).map(Ordering::reverse)
34 }
35}
36
37#[cfg(feature = "chrono")]
38impl PartialEq<chrono::TimeDelta> for Duration {
39 #[inline]
40 fn eq(&self, other: &chrono::TimeDelta) -> bool {
41 let other_total = (i128::from(other.num_seconds()) * Self::NANOS_PER_SEC_I128)
42 + i128::from(other.subsec_nanos());
43
44 self.total_nanos() == other_total
45 }
46}
47
48#[cfg(feature = "chrono")]
49impl PartialEq<Duration> for chrono::TimeDelta {
50 #[inline]
51 fn eq(&self, other: &Duration) -> bool {
52 other == self
53 }
54}
55
56#[cfg(feature = "chrono")]
57impl PartialOrd<chrono::TimeDelta> for Duration {
58 #[inline]
59 fn partial_cmp(&self, other: &chrono::TimeDelta) -> Option<Ordering> {
60 let other_total = (i128::from(other.num_seconds()) * Self::NANOS_PER_SEC_I128)
61 + i128::from(other.subsec_nanos());
62
63 Some(self.total_nanos().cmp(&other_total))
64 }
65}
66
67#[cfg(feature = "chrono")]
68impl PartialOrd<Duration> for chrono::TimeDelta {
69 #[inline]
70 fn partial_cmp(&self, other: &Duration) -> Option<Ordering> {
71 other.partial_cmp(self).map(Ordering::reverse)
72 }
73}
74
75impl Add<StdDuration> for Duration {
76 type Output = Self;
77
78 #[inline]
79 fn add(self, rhs: StdDuration) -> Self::Output {
80 let rhs_s = i64::try_from(rhs.as_secs()).expect("overflow in duration addition");
81 let rhs_n = i64::from(rhs.subsec_nanos());
82
83 self.checked_add_raw(rhs_s, rhs_n)
84 .expect("overflow in duration addition")
85 }
86}
87
88impl Add for Duration {
89 type Output = Self;
90 #[inline]
91 fn add(self, rhs: Self) -> Self::Output {
92 self.checked_add(&rhs)
93 .expect("overflow in duration addition")
94 }
95}
96
97impl Sub for Duration {
98 type Output = Self;
99
100 #[inline]
101 fn sub(self, other: Self) -> Self {
102 self.checked_sub(&other)
103 .expect("overflow in duration subtraction")
104 }
105}
106
107impl Sub<StdDuration> for Duration {
108 type Output = Self;
109
110 #[inline]
111 fn sub(self, rhs: StdDuration) -> Self::Output {
112 let rhs_s = i64::try_from(rhs.as_secs()).expect("overflow in duration subtraction");
113 let rhs_n = i64::from(rhs.subsec_nanos());
114
115 self.checked_sub_raw(rhs_s, rhs_n)
116 .expect("overflow in duration subtraction")
117 }
118}
119
120#[cfg(feature = "chrono")]
121impl Add<chrono::TimeDelta> for Duration {
122 type Output = Self;
123
124 #[inline]
125 fn add(self, rhs: chrono::TimeDelta) -> Self::Output {
126 self.checked_add_raw(rhs.num_seconds(), i64::from(rhs.subsec_nanos()))
127 .expect("overflow in duration addition")
128 }
129}
130
131#[cfg(feature = "chrono")]
132impl Sub<chrono::TimeDelta> for Duration {
133 type Output = Self;
134
135 #[inline]
136 fn sub(self, rhs: chrono::TimeDelta) -> Self::Output {
137 self.checked_sub_raw(rhs.num_seconds(), i64::from(rhs.subsec_nanos()))
138 .expect("overflow in duration subtraction")
139 }
140}
141
142impl Mul<i64> for Duration {
143 type Output = Self;
144
145 #[inline]
146 fn mul(self, rhs: i64) -> Self {
147 self.checked_mul(rhs)
148 .expect("Duration multiplication by i64 overflowed")
149 }
150}
151
152impl Mul<i32> for Duration {
153 type Output = Self;
154
155 #[inline]
156 fn mul(self, rhs: i32) -> Self {
157 self.checked_mul(i64::from(rhs)) .expect("Duration multiplication by i32 overflowed")
159 }
160}
161
162impl Div<i64> for Duration {
163 type Output = Self;
164
165 #[inline]
166 fn div(self, rhs: i64) -> Self {
167 self.checked_div(rhs)
168 .expect("Duration division by i64 overflowed or divided by zero")
169 }
170}
171
172impl Div<i32> for Duration {
173 type Output = Self;
174
175 #[inline]
176 fn div(self, rhs: i32) -> Self {
177 self.checked_div(i64::from(rhs))
178 .expect("Duration division by i32 overflowed or divided by zero")
179 }
180}
181
182impl Duration {
183 const NANOS_PER_SEC: i64 = 1_000_000_000;
184 const NANOS_PER_SEC_I128: i128 = 1_000_000_000;
185
186 fn align_signs(mut s: i64, mut n: i32) -> Option<Self> {
187 if s > 0 && n < 0 {
188 s = s.checked_sub(1)?;
189 n += 1_000_000_000;
190 } else if s < 0 && n > 0 {
191 s = s.checked_add(1)?;
192 n -= 1_000_000_000;
193 }
194 Some(Self {
195 seconds: s,
196 nanos: n,
197 })
198 }
199
200 fn checked_add_raw(&self, rhs_s: i64, rhs_n: i64) -> Option<Self> {
201 let mut s = self.seconds.checked_add(rhs_s)?;
202 let mut n_total = i64::from(self.nanos) + rhs_n;
203
204 if n_total >= 1_000_000_000 {
205 s = s.checked_add(1)?;
206 n_total -= 1_000_000_000;
207 } else if n_total <= -1_000_000_000 {
208 s = s.checked_sub(1)?;
209 n_total += 1_000_000_000;
210 }
211
212 if s > 0 && n_total < 0 {
213 s = s.checked_sub(1)?;
214 n_total += 1_000_000_000;
215 } else if s < 0 && n_total > 0 {
216 s = s.checked_add(1)?;
217 n_total -= 1_000_000_000;
218 }
219
220 Some(Self {
221 seconds: s,
222 #[allow(clippy::cast_possible_truncation)]
223 nanos: n_total as i32,
224 })
225 }
226
227 fn checked_sub_raw(&self, rhs_s: i64, rhs_n: i64) -> Option<Self> {
228 let mut s = self.seconds.checked_sub(rhs_s)?;
229 let mut n_total = i64::from(self.nanos) - rhs_n;
230
231 if n_total >= Self::NANOS_PER_SEC {
232 s = s.checked_add(1)?;
233 n_total -= Self::NANOS_PER_SEC;
234 } else if n_total <= -Self::NANOS_PER_SEC {
235 s = s.checked_sub(1)?;
236 n_total += Self::NANOS_PER_SEC;
237 }
238
239 #[allow(clippy::cast_possible_truncation)]
240 Self::align_signs(s, n_total as i32)
241 }
242
243 #[inline]
245 #[must_use]
246 pub const fn total_nanos(&self) -> i128 {
247 (self.seconds as i128) * (Self::NANOS_PER_SEC_I128) + (self.nanos as i128)
248 }
249
250 #[must_use]
252 #[inline]
253 pub fn from_total_nanos(total: i128) -> Option<Self> {
254 let factor = Self::NANOS_PER_SEC_I128;
255
256 let seconds_val = total / factor;
258 let seconds = i64::try_from(seconds_val).ok()?;
259
260 let nanos_val = total % factor;
262 #[allow(clippy::cast_possible_truncation)]
264 let nanos = nanos_val as i32;
265
266 Some(Self { seconds, nanos })
267 }
268
269 #[must_use]
271 #[inline]
272 pub fn checked_mul(&self, rhs: i64) -> Option<Self> {
273 let total = self.total_nanos().checked_mul(i128::from(rhs))?;
274 Self::from_total_nanos(total)
275 }
276
277 #[must_use]
279 #[inline]
280 pub fn checked_add(&self, other: &Self) -> Option<Self> {
281 self.checked_add_raw(other.seconds, other.nanos.into())
282 }
283
284 #[must_use]
286 #[inline]
287 pub fn checked_sub(&self, other: &Self) -> Option<Self> {
288 self.checked_sub_raw(other.seconds, other.nanos.into())
289 }
290
291 #[must_use]
293 #[inline]
294 pub fn checked_div(&self, rhs: i64) -> Option<Self> {
295 if rhs == 0 {
296 return None;
297 }
298 let total = self.total_nanos().checked_div(i128::from(rhs))?;
299 Self::from_total_nanos(total)
300 }
301}
302
303#[cfg(test)]
304mod tests {
305 use super::*;
306 use core::cmp::Ordering;
307
308 macro_rules! get_duration {
309 (duration, $secs:literal, $nanos:literal) => {
310 Duration::new($secs, $nanos)
311 };
312
313 (std, $secs:literal, $nanos:literal) => {
314 StdDuration::new($secs, $nanos)
315 };
316
317 (chrono, $secs:literal, $nanos:literal) => {
318 TimeDelta::new($secs, $nanos).unwrap()
319 };
320 }
321
322 macro_rules! test_ops {
323 ($duration:ident) => {
324 #[test]
325 fn test_add_sub() {
326 let d1 = dur(1, 900_000_000);
328 let d2 = get_duration!($duration, 0, 200_000_000);
329 let sum = d1 + d2;
330 assert_eq!(sum.seconds, 2);
331 assert_eq!(sum.nanos, 100_000_000);
332
333 let d1 = dur(2, 100_000_000);
335 let d2 = get_duration!($duration, 0, 200_000_000);
336 let diff = d1 - d2;
337 assert_eq!(diff.seconds, 1);
338 assert_eq!(diff.nanos, 900_000_000);
339
340 let d1 = dur(1, 0);
342 let d2 = get_duration!($duration, 2, 0);
343 let diff = d1 - d2;
344 assert_eq!(diff.seconds, -1);
346 assert_eq!(diff.nanos, 0);
347 }
348
349 #[test]
350 fn test_eq_ord() {
351 let d1 = dur(1, 500);
352 let d2 = get_duration!($duration, 1, 600);
353 let d3 = get_duration!($duration, 2, 0);
354
355 assert!(d1 < d2);
356 assert!(d2 < d3);
357
358 let unnormalized = dur(0, 1_500_000_000);
361 let normalized = get_duration!($duration, 1, 500_000_000);
362 assert_eq!(unnormalized.partial_cmp(&normalized), Some(Ordering::Equal));
363 }
364 };
365 }
366
367 test_ops!(duration);
368
369 mod std_test {
370 use super::*;
371
372 test_ops!(std);
373 }
374
375 #[cfg(feature = "chrono")]
376 mod chrono_test {
377 use super::*;
378 use chrono::TimeDelta;
379
380 test_ops!(chrono);
381 }
382
383 fn dur(s: i64, n: i32) -> Duration {
384 Duration {
385 seconds: s,
386 nanos: n,
387 }
388 }
389
390 #[test]
391 fn test_mul_overflow_checks() {
392 let d = dur(10, 0);
394 assert_eq!(d * 2, dur(20, 0));
395
396 let huge = dur(i64::MAX / 2 + 100, 0);
398 assert!(huge.checked_mul(2).is_none());
399
400 let edge = dur(i64::MAX, 0);
402 assert!(edge.checked_mul(1).is_some());
403
404 let edge_nanos = dur(i64::MAX - 1, 600_000_000);
406 assert!(edge_nanos.checked_mul(2).is_none());
408 }
409
410 #[test]
411 fn test_div_bug_fix() {
412 let numerator = dur(10_000_000_000, 0); let divisor = 11_000_000_000; let res = numerator.checked_div(divisor).unwrap();
418 assert_eq!(res.seconds, 0);
419 assert_eq!(res.nanos, 909_090_909);
421 }
422
423 #[test]
424 fn test_div_edge_cases() {
425 let d = dur(1, 0);
427 assert!(d.checked_div(0).is_none());
428
429 let d = dur(1, 0);
431 let res: Duration = d / 2;
432 assert_eq!(res.seconds, 0);
433 assert_eq!(res.nanos, 500_000_000);
434
435 let d = dur(-1, 0);
437 let res: Duration = d / 2;
438 assert_eq!(res.seconds, 0);
439 assert_eq!(res.nanos, -500_000_000);
440 }
441
442 #[test]
443 fn test_get_data_smoke_test() {
444 let d = dur(31_557_600 + 1, 0); let data = d.get_data();
448 assert_eq!(data.years.value, 1);
449 }
450}