1use core::ops::{Add, Div, Mul, Neg, Sub};
3use std::time::Duration as StdDuration;
4
5use chrono::{Date, DateTime, Duration, NaiveDate, NaiveDateTime, TimeZone};
6
7use super::delta::shift_months;
8
9mod parse;
10
11#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
13pub struct RelativeDuration {
14 months: i32, duration: Duration,
16}
17
18impl From<Duration> for RelativeDuration {
19 #[inline]
21 fn from(item: Duration) -> Self {
22 RelativeDuration {
23 months: 0,
24 duration: item,
25 }
26 }
27}
28
29impl From<StdDuration> for RelativeDuration {
30 #[inline]
32 fn from(item: StdDuration) -> Self {
33 RelativeDuration::from(
34 Duration::from_std(item).expect("RelativeDuration::from_std OutOfRangeError"),
35 )
36 }
37}
38
39impl RelativeDuration {
40 #[inline]
45 pub fn years(years: i32) -> RelativeDuration {
46 let months = years
47 .checked_mul(12)
48 .expect("RelativeDuration::years out of bounds");
49 RelativeDuration::months(months)
50 }
51
52 #[inline]
55 pub fn months(months: i32) -> RelativeDuration {
56 RelativeDuration {
57 months,
58 duration: Duration::zero(),
59 }
60 }
61
62 #[inline]
65 pub fn weeks(weeks: i64) -> RelativeDuration {
66 RelativeDuration {
67 months: 0,
68 duration: Duration::weeks(weeks),
69 }
70 }
71
72 #[inline]
75 pub fn days(days: i64) -> RelativeDuration {
76 RelativeDuration {
77 months: 0,
78 duration: Duration::days(days),
79 }
80 }
81
82 #[inline]
85 pub fn hours(hours: i64) -> RelativeDuration {
86 RelativeDuration {
87 months: 0,
88 duration: Duration::hours(hours),
89 }
90 }
91
92 #[inline]
95 pub fn minutes(minutes: i64) -> RelativeDuration {
96 RelativeDuration {
97 months: 0,
98 duration: Duration::minutes(minutes),
99 }
100 }
101
102 #[inline]
105 pub fn seconds(seconds: i64) -> RelativeDuration {
106 RelativeDuration {
107 months: 0,
108 duration: Duration::seconds(seconds),
109 }
110 }
111
112 #[inline]
114 pub fn milliseconds(milliseconds: i64) -> RelativeDuration {
115 RelativeDuration {
116 months: 0,
117 duration: Duration::milliseconds(milliseconds),
118 }
119 }
120
121 #[inline]
123 pub fn microseconds(microseconds: i64) -> RelativeDuration {
124 RelativeDuration {
125 months: 0,
126 duration: Duration::microseconds(microseconds),
127 }
128 }
129
130 #[inline]
132 pub fn nanoseconds(nanos: i64) -> RelativeDuration {
133 RelativeDuration {
134 months: 0,
135 duration: Duration::nanoseconds(nanos),
136 }
137 }
138
139 #[inline]
141 pub fn with_duration(self, duration: Duration) -> RelativeDuration {
142 RelativeDuration {
143 months: self.months,
144 duration,
145 }
146 }
147
148 #[inline]
150 pub fn zero() -> RelativeDuration {
151 RelativeDuration {
152 months: 0,
153 duration: Duration::zero(),
154 }
155 }
156
157 #[inline]
159 pub fn is_zero(&self) -> bool {
160 self.months == 0 && self.duration.is_zero()
161 }
162}
163
164impl Neg for RelativeDuration {
165 type Output = RelativeDuration;
166
167 #[inline]
168 fn neg(self) -> RelativeDuration {
169 RelativeDuration {
170 months: -self.months,
171 duration: -self.duration,
172 }
173 }
174}
175
176impl Add<RelativeDuration> for RelativeDuration {
177 type Output = RelativeDuration;
178
179 #[inline]
180 fn add(self, rhs: RelativeDuration) -> RelativeDuration {
181 RelativeDuration {
182 months: self.months + rhs.months,
183 duration: self.duration + rhs.duration,
184 }
185 }
186}
187
188impl Add<Duration> for RelativeDuration {
189 type Output = RelativeDuration;
190
191 #[inline]
192 fn add(self, rhs: Duration) -> RelativeDuration {
193 self + RelativeDuration {
194 months: 0,
195 duration: rhs,
196 }
197 }
198}
199
200impl Add<RelativeDuration> for Duration {
201 type Output = RelativeDuration;
202
203 #[inline]
204 fn add(self, rhs: RelativeDuration) -> RelativeDuration {
205 rhs + self
206 }
207}
208
209impl Sub for RelativeDuration {
210 type Output = RelativeDuration;
211
212 #[inline]
213 fn sub(self, rhs: RelativeDuration) -> RelativeDuration {
214 self + (-rhs)
215 }
216}
217
218impl Sub<RelativeDuration> for Duration {
219 type Output = RelativeDuration;
220
221 #[inline]
222 fn sub(self, rhs: RelativeDuration) -> RelativeDuration {
223 -rhs + self
224 }
225}
226
227impl Sub<Duration> for RelativeDuration {
228 type Output = RelativeDuration;
229
230 #[inline]
231 fn sub(self, rhs: Duration) -> RelativeDuration {
232 self + (-rhs)
233 }
234}
235
236impl Mul<i32> for RelativeDuration {
237 type Output = RelativeDuration;
238
239 #[inline]
240 fn mul(self, rhs: i32) -> RelativeDuration {
241 RelativeDuration {
242 months: self.months * rhs,
243 duration: self.duration * rhs,
244 }
245 }
246}
247
248impl Div<i32> for RelativeDuration {
249 type Output = RelativeDuration;
250
251 #[inline]
252 fn div(self, rhs: i32) -> RelativeDuration {
253 RelativeDuration {
254 months: self.months / rhs,
255 duration: self.duration / rhs,
256 }
257 }
258}
259
260impl Add<RelativeDuration> for NaiveDate {
263 type Output = NaiveDate;
264
265 #[inline]
266 fn add(self, rhs: RelativeDuration) -> NaiveDate {
267 shift_months(self, rhs.months) + rhs.duration
268 }
269}
270
271impl Add<RelativeDuration> for NaiveDateTime {
272 type Output = NaiveDateTime;
273
274 #[inline]
275 fn add(self, rhs: RelativeDuration) -> NaiveDateTime {
276 shift_months(self, rhs.months) + rhs.duration
277 }
278}
279
280impl<Tz> Add<RelativeDuration> for Date<Tz>
281where
282 Tz: TimeZone,
283{
284 type Output = Date<Tz>;
285
286 #[inline]
287 fn add(self, rhs: RelativeDuration) -> Date<Tz> {
288 shift_months(self, rhs.months) + rhs.duration
289 }
290}
291
292impl<Tz> Add<RelativeDuration> for DateTime<Tz>
293where
294 Tz: TimeZone,
295{
296 type Output = DateTime<Tz>;
297
298 #[inline]
299 fn add(self, rhs: RelativeDuration) -> DateTime<Tz> {
300 shift_months(self, rhs.months) + rhs.duration
301 }
302}
303
304impl Sub<RelativeDuration> for NaiveDate {
305 type Output = NaiveDate;
306
307 #[inline]
308 fn sub(self, rhs: RelativeDuration) -> NaiveDate {
309 self + (-rhs)
310 }
311}
312
313impl Sub<RelativeDuration> for NaiveDateTime {
314 type Output = NaiveDateTime;
315
316 #[inline]
317 fn sub(self, rhs: RelativeDuration) -> NaiveDateTime {
318 self + (-rhs)
319 }
320}
321
322impl<Tz> Sub<RelativeDuration> for Date<Tz>
323where
324 Tz: TimeZone,
325{
326 type Output = Date<Tz>;
327
328 #[inline]
329 fn sub(self, rhs: RelativeDuration) -> Date<Tz> {
330 self + (-rhs)
331 }
332}
333
334impl<Tz> Sub<RelativeDuration> for DateTime<Tz>
335where
336 Tz: TimeZone,
337{
338 type Output = DateTime<Tz>;
339
340 #[inline]
341 fn sub(self, rhs: RelativeDuration) -> DateTime<Tz> {
342 self + (-rhs)
343 }
344}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349
350 #[test]
351 fn test_duration_arithmetic() {
352 let x = RelativeDuration {
353 months: 5 * 12 + 7,
354 duration: Duration::seconds(100),
355 };
356 let y = RelativeDuration {
357 months: 3 * 12 + 6,
358 duration: Duration::seconds(300),
359 };
360 let z = Duration::days(100);
361
362 assert_eq!(
363 x + y,
364 RelativeDuration {
365 months: 9 * 12 + 1,
366 duration: Duration::seconds(400)
367 }
368 );
369 assert_eq!(
370 x - y,
371 RelativeDuration {
372 months: 2 * 12 + 1,
373 duration: Duration::seconds(-200)
374 }
375 );
376 assert_eq!(
377 x + z,
378 RelativeDuration {
379 months: 5 * 12 + 7,
380 duration: Duration::days(100) + Duration::seconds(100)
381 }
382 );
383
384 assert_eq!(y + x, y + x, "Addition should be symmetric");
385 assert_eq!(x - y, -(y - x), "Subtraction should be anti-symmetric");
386 assert_eq!(y + z, z + y, "Addition should be symmetric");
387 assert_eq!(y - z, -(z - y), "Subtraction should be anti-symmetric");
388
389 assert_eq!(
390 x / 2,
391 RelativeDuration {
392 months: 5 * 6 + 3,
393 duration: Duration::seconds(50)
394 }
395 );
396 assert_eq!(
397 x * 2,
398 RelativeDuration {
399 months: 10 * 12 + 14,
400 duration: Duration::seconds(200)
401 }
402 );
403 }
404
405 #[test]
406 fn test_date_arithmetic() {
407 let base = NaiveDate::from_ymd_opt(2020, 2, 29).unwrap();
408
409 assert_eq!(
410 base + RelativeDuration {
411 months: 24,
412 duration: Duration::zero()
413 },
414 NaiveDate::from_ymd_opt(2022, 2, 28).unwrap()
415 );
416 assert_eq!(
417 base + RelativeDuration {
418 months: 48,
419 duration: Duration::zero()
420 },
421 NaiveDate::from_ymd_opt(2024, 2, 29).unwrap()
422 );
423
424 let not_leap = NaiveDate::from_ymd_opt(2020, 2, 28).unwrap();
425 let tricky_delta = RelativeDuration {
426 months: 24,
427 duration: Duration::days(1),
428 };
429 assert_eq!(
430 base + tricky_delta,
431 NaiveDate::from_ymd_opt(2022, 3, 1).unwrap()
432 );
433 assert_eq!(base + tricky_delta, not_leap + tricky_delta);
434 }
435
436 #[test]
437 fn test_date_negative_arithmetic() {
438 let base = NaiveDate::from_ymd_opt(2020, 2, 29).unwrap();
439
440 assert_eq!(
441 base - RelativeDuration {
442 months: 24,
443 duration: Duration::zero()
444 },
445 NaiveDate::from_ymd_opt(2018, 2, 28).unwrap()
446 );
447 assert_eq!(
448 base - RelativeDuration {
449 months: 48,
450 duration: Duration::zero()
451 },
452 NaiveDate::from_ymd_opt(2016, 2, 29).unwrap()
453 );
454
455 let not_leap = NaiveDate::from_ymd_opt(2020, 2, 28).unwrap();
456 let tricky_delta = RelativeDuration {
457 months: 24,
458 duration: Duration::days(-1),
459 };
460 assert_eq!(
461 base - tricky_delta,
462 NaiveDate::from_ymd_opt(2018, 3, 1).unwrap()
463 );
464 assert_eq!(base - tricky_delta, not_leap - tricky_delta);
465 }
466
467 #[test]
468 fn test_constructors() {
469 assert_eq!(RelativeDuration::years(5), RelativeDuration::months(60));
470 assert_eq!(RelativeDuration::weeks(5), RelativeDuration::days(35));
471 assert_eq!(RelativeDuration::days(5), RelativeDuration::hours(120));
472 assert_eq!(RelativeDuration::hours(5), RelativeDuration::minutes(300));
473 assert_eq!(RelativeDuration::minutes(5), RelativeDuration::seconds(300));
474 assert_eq!(
475 RelativeDuration::months(1).with_duration(Duration::weeks(3)),
476 RelativeDuration {
477 months: 1,
478 duration: Duration::weeks(3)
479 },
480 );
481 }
482}