iso8601_duration/
chrono.rs1use 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 pub fn to_chrono(&self) -> Option<ChronoDuration> {
20 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 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" );
103 assert_eq!(
104 (ymd(2001, 6, 1) + quarter).to_rfc3339(),
105 "2001-08-31T06:00:00+00:00" );
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" );
114 assert_eq!(
115 (ymd(2001, 4, 1) + half_month).to_rfc3339(),
116 "2001-04-16T00:00:00+00:00" );
118
119 let week: Duration = "P1W".parse().unwrap();
120 assert_eq!(week.to_chrono(), Some(ChronoDuration::weeks(1)));
121}