iso8601_duration/
chrono.rs

1use std::ops::Add;
2
3use chrono::{DateTime, Datelike, Duration as ChronoDuration, NaiveDate, TimeZone};
4
5use crate::Duration;
6
7fn seconds_to_chrono_duration(seconds: f32) -> ChronoDuration {
8    let nanoseconds = seconds.fract() * 1_000_000_000.;
9    let seconds = seconds.trunc();
10
11    ChronoDuration::seconds(seconds as i64) + ChronoDuration::nanoseconds(nanoseconds as i64)
12}
13
14impl Duration {
15    /// Convert `Duration` to `chrono::Duration`.
16    ///
17    /// This method will return `None` is `Duration` contains
18    /// `year` or `month`.
19    pub fn to_chrono(&self) -> Option<ChronoDuration> {
20        // we can't get the duration of year or month,
21        // without knowing the start date.
22        if self.year > 0.0 || self.month > 0.0 {
23            return None;
24        }
25
26        let seconds =
27            self.day * 60. * 60. * 24. + self.hour * 60. * 60. + self.minute * 60. + self.second;
28
29        Some(seconds_to_chrono_duration(seconds))
30    }
31
32    /// Convert `Duration` to `chrono::Duration` at given datetime.
33    pub fn to_chrono_at_datetime<Tz: TimeZone>(&self, at: DateTime<Tz>) -> ChronoDuration {
34        (at.clone() + *self) - at
35    }
36}
37
38impl<Tz: TimeZone> Add<Duration> for DateTime<Tz> {
39    type Output = DateTime<Tz>;
40
41    fn add(self, rhs: Duration) -> Self {
42        let mut d = ChronoDuration::zero();
43
44        if rhs.year > 0.0 {
45            let year = self.date_naive().year();
46
47            let seconds_in_this_year = NaiveDate::from_ymd_opt(year + 1, 1, 1)
48                .expect("Date out of range")
49                .signed_duration_since(
50                    NaiveDate::from_ymd_opt(year, 1, 1).expect("Date out of range"),
51                )
52                .num_seconds();
53
54            d = d + seconds_to_chrono_duration(rhs.year * seconds_in_this_year as f32)
55        }
56
57        if rhs.month > 0.0 {
58            let year = self.date_naive().year();
59            let month = self.date_naive().month();
60
61            let seconds_in_this_month = NaiveDate::from_ymd_opt(
62                if month == 12 { year + 1 } else { year },
63                if month == 12 { 1 } else { month + 1 },
64                1,
65            )
66            .expect("Date out of range")
67            .signed_duration_since(
68                NaiveDate::from_ymd_opt(year, month, 1).expect("Date out of range"),
69            )
70            .num_seconds();
71
72            d = d + seconds_to_chrono_duration(rhs.month * seconds_in_this_month as f32)
73        }
74
75        d = d + seconds_to_chrono_duration(
76            rhs.day * 60. * 60. * 24. + rhs.hour * 60. * 60. + rhs.minute * 60. + rhs.second,
77        );
78
79        self + d
80    }
81}
82
83#[test]
84fn test_chrono() {
85    use chrono::Utc;
86
87    fn ymd(y: i32, m: u32, d: u32) -> DateTime<Utc> {
88        DateTime::<Utc>::from_utc(
89            NaiveDate::from_ymd_opt(y, m, d)
90                .unwrap()
91                .and_hms_opt(0, 0, 0)
92                .unwrap(),
93            Utc,
94        )
95    }
96
97    let quarter: Duration = "P0.25Y".parse().unwrap();
98
99    assert_eq!(
100        (ymd(2000, 6, 1) + quarter).to_rfc3339(),
101        "2000-08-31T12:00:00+00:00" // 91.5days
102    );
103    assert_eq!(
104        (ymd(2001, 6, 1) + quarter).to_rfc3339(),
105        "2001-08-31T06:00:00+00:00" // 91.25days
106    );
107
108    let half_month: Duration = "P0.5M".parse().unwrap();
109
110    assert_eq!(
111        (ymd(2001, 2, 1) + half_month).to_rfc3339(),
112        "2001-02-15T00:00:00+00:00" // 14days
113    );
114    assert_eq!(
115        (ymd(2001, 4, 1) + half_month).to_rfc3339(),
116        "2001-04-16T00:00:00+00:00" // 15days
117    );
118
119    let week: Duration = "P1W".parse().unwrap();
120    assert_eq!(week.to_chrono(), Some(ChronoDuration::weeks(1)));
121}