1use crate::{calendars::Calendar, constants, utils::normalize_nanoseconds};
6
7#[derive(Debug)]
9pub struct CFDuration {
10 pub seconds: i64,
11 pub nanoseconds: u32,
12 pub calendar: Calendar,
13}
14
15impl CFDuration {
16 pub fn new(seconds: i64, nanoseconds: i64, calendar: Calendar) -> Self {
17 let (remaining_seconds, remaining_nanoseconds) = normalize_nanoseconds(nanoseconds);
18 Self {
19 seconds: seconds + remaining_seconds,
20 nanoseconds: (remaining_nanoseconds),
21 calendar,
22 }
23 }
24}
25
26impl CFDuration {
27 pub fn calendar(&self) -> Calendar {
29 self.calendar
30 }
31 pub fn from_years(years: i64, calendar: Calendar) -> CFDuration {
35 let secs_per_year = match calendar {
36 Calendar::ProlepticGregorian | Calendar::Standard => 3.15569259747e7,
37 Calendar::NoLeap => 365.0 * constants::SECS_PER_DAY as f64,
38 Calendar::AllLeap => 366.0 * constants::SECS_PER_DAY as f64,
39 Calendar::Julian => 365.25 * constants::SECS_PER_DAY as f64,
40 Calendar::Day360 => 360.0 * constants::SECS_PER_DAY as f64,
41 };
42 let secs = secs_per_year as i64 * years;
43 Self::new(secs, 0, calendar)
44 }
45 pub fn from_months(months: i64, calendar: Calendar) -> CFDuration {
47 let seconds_for_one_year = CFDuration::from_years(1, calendar).seconds;
48 Self::new(seconds_for_one_year / 12 * months, 0, calendar)
49 }
50 pub fn from_weeks(weeks: i64, calendar: Calendar) -> CFDuration {
52 Self::new(weeks * 7 * 24 * 60 * 60, 0, calendar)
53 }
54 pub fn from_days(days: i64, calendar: Calendar) -> CFDuration {
56 Self::new(days * 24 * 60 * 60, 0, calendar)
57 }
58 pub fn from_hours(hours: i64, calendar: Calendar) -> CFDuration {
60 Self::new(hours * 60 * 60, 0, calendar)
61 }
62 pub fn from_minutes(minutes: i64, calendar: Calendar) -> CFDuration {
64 Self::new(minutes * 60, 0, calendar)
65 }
66 pub fn from_seconds(seconds: i64, calendar: Calendar) -> CFDuration {
68 Self::new(seconds, 0, calendar)
69 }
70 pub fn from_milliseconds(milliseconds: i64, calendar: Calendar) -> CFDuration {
72 Self::new(0, milliseconds * 1_000_000, calendar)
73 }
74 pub fn from_microseconds(microseconds: i64, calendar: Calendar) -> CFDuration {
76 Self::new(0, 1_000 * microseconds, calendar)
77 }
78 pub fn from_nanoseconds(nanoseconds: i64, calendar: Calendar) -> CFDuration {
80 Self::new(0, nanoseconds, calendar)
81 }
82 pub fn num_years(&self) -> f64 {
84 match self.calendar {
85 Calendar::ProlepticGregorian | Calendar::Standard => {
86 self.num_seconds() / 3.15569259747e7
87 }
88 Calendar::NoLeap => self.num_days() / 365.0,
89 Calendar::AllLeap => self.num_days() / 366.0,
90 Calendar::Julian => self.num_days() / 365.25,
91 Calendar::Day360 => self.num_days() / 360.0,
92 }
93 }
94 pub fn num_months(&self) -> f64 {
96 self.num_years() * 12.
97 }
98 pub fn num_weeks(&self) -> f64 {
100 self.num_days() / 7.
101 }
102 pub fn num_days(&self) -> f64 {
104 self.num_hours() / 24.
105 }
106 pub fn num_hours(&self) -> f64 {
108 self.num_minutes() / 60.
109 }
110 pub fn num_minutes(&self) -> f64 {
112 self.num_seconds() / 60.0
113 }
114 pub fn num_seconds(&self) -> f64 {
116 self.seconds as f64 + self.nanoseconds as f64 / 1e9
117 }
118 pub fn num_milliseconds(&self) -> f64 {
120 self.num_seconds() * 1e3
121 }
122 pub fn num_microseconds(&self) -> f64 {
124 self.num_seconds() * 1e6
125 }
126 pub fn num_nanoseconds(&self) -> f64 {
128 (self.seconds * 1_000_000_000 + self.nanoseconds as i64) as f64
129 }
130}
131
132impl std::fmt::Display for CFDuration {
141 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
142 write!(
143 f,
144 "P{}Y{}M{}DT{}H{}M{}S",
145 self.num_years() as i64,
146 self.num_months() as i64 % 12,
147 self.num_days() as i64 % 31,
148 self.num_hours() as i64 % 24,
149 self.num_minutes() as i64 % 60,
150 self.num_seconds() as i64 % 60
151 )
152 }
153}
154
155macro_rules! impl_add_for_cf_duration {
156 ($self_dur:ty, $rhs_dur:ty) => {
157 impl std::ops::Add for $self_dur {
158 type Output = Result<CFDuration, crate::errors::Error>;
159 fn add(self, rhs: $rhs_dur) -> Self::Output {
160 if self.calendar() != rhs.calendar() {
161 return Err(crate::errors::Error::DifferentCalendars(
162 self.calendar().to_string(),
163 rhs.calendar().to_string(),
164 ));
165 }
166 Ok(CFDuration::new(
167 self.seconds + rhs.seconds,
168 self.nanoseconds as i64 + rhs.nanoseconds as i64,
169 self.calendar,
170 ))
171 }
172 }
173 };
174}
175impl_add_for_cf_duration!(CFDuration, CFDuration);
176impl_add_for_cf_duration!(&CFDuration, &CFDuration);
177
178macro_rules! impl_sub_for_cf_duration {
179 ($self_dur:ty, $rhs_dur:ty) => {
180 impl std::ops::Sub for $self_dur {
181 type Output = Result<CFDuration, crate::errors::Error>;
182 fn sub(self, rhs: $rhs_dur) -> Self::Output {
183 if self.calendar() != rhs.calendar() {
184 return Err(crate::errors::Error::DifferentCalendars(
185 self.calendar().to_string(),
186 rhs.calendar().to_string(),
187 ));
188 }
189 Ok(CFDuration::new(
190 self.seconds - rhs.seconds,
191 self.nanoseconds as i64 - rhs.nanoseconds as i64,
192 self.calendar,
193 ))
194 }
195 }
196 };
197}
198
199impl_sub_for_cf_duration!(CFDuration, CFDuration);
200impl_sub_for_cf_duration!(&CFDuration, &CFDuration);
201
202impl std::ops::Neg for CFDuration {
203 type Output = CFDuration;
204 fn neg(self) -> Self::Output {
205 Self::new(-self.seconds, -(self.nanoseconds as i64), self.calendar)
206 }
207}
208impl std::ops::Neg for &CFDuration {
209 type Output = CFDuration;
210 fn neg(self) -> Self::Output {
211 CFDuration::new(-self.seconds, -(self.nanoseconds as i64), self.calendar)
212 }
213}
214
215macro_rules! impl_mul_for_cf_duration_int {
216 ($which_dur:ty, $rhs_type:ty) => {
217 impl std::ops::Mul<$rhs_type> for $which_dur {
218 type Output = CFDuration;
219 fn mul(self, rhs: $rhs_type) -> Self::Output {
220 CFDuration::new(
221 self.seconds * rhs as i64,
222 self.nanoseconds as i64 * rhs as i64,
223 self.calendar,
224 )
225 }
226 }
227 };
228}
229
230impl_mul_for_cf_duration_int!(CFDuration, i64);
231impl_mul_for_cf_duration_int!(CFDuration, i32);
232impl_mul_for_cf_duration_int!(&CFDuration, i64);
233impl_mul_for_cf_duration_int!(&CFDuration, i32);
234
235macro_rules! impl_mul_for_cf_duration_float {
236 ($which_dur:ty, $rhs_type:ty) => {
237 impl std::ops::Mul<$rhs_type> for $which_dur {
238 type Output = CFDuration;
239 fn mul(self, rhs: $rhs_type) -> Self::Output {
240 let _rhs: f64 = rhs.into();
244 let mut new_seconds = (self.seconds as f64 * _rhs) as i64;
245 let mut new_ns = (self.nanoseconds as f64 * _rhs) as i64;
246 let (remaining_seconds, remaining_nanoseconds) = normalize_nanoseconds(new_ns);
247 new_seconds += remaining_seconds;
248 new_ns += remaining_nanoseconds as i64;
249 CFDuration::new(new_seconds, new_ns, self.calendar)
250 }
251 }
252 };
253}
254
255impl_mul_for_cf_duration_float!(CFDuration, f64);
256impl_mul_for_cf_duration_float!(CFDuration, f32);
257impl_mul_for_cf_duration_float!(&CFDuration, f64);
258impl_mul_for_cf_duration_float!(&CFDuration, f32);
259
260#[cfg(test)]
261mod tests {
262 use crate::calendars;
263
264 use super::*;
265
266 #[test]
267 fn test_idempotence_duration_all_calendars() {
268 let cals = vec![
269 calendars::Calendar::Day360,
270 calendars::Calendar::Standard,
271 calendars::Calendar::ProlepticGregorian,
272 calendars::Calendar::Julian,
273 calendars::Calendar::NoLeap,
274 calendars::Calendar::AllLeap,
275 ];
276 for cal in cals.clone() {
277 println!("{}", cal);
278 println!("Week");
279 let duration = CFDuration::from_weeks(1, cal);
280 let duration_result = duration.num_weeks();
281 assert_eq!(duration_result, 1.0);
282 println!("Day");
283 let duration = CFDuration::from_days(1, cal);
284 let duration_result = duration.num_days();
285 assert_eq!(duration_result, 1.0);
286 println!("Hours");
287 let duration = CFDuration::from_hours(1, cal);
288 let duration_result = duration.num_hours();
289 assert_eq!(duration_result, 1.0);
290 println!("Minutes");
291 let duration = CFDuration::from_minutes(1, cal);
292 let duration_result = duration.num_minutes();
293 assert_eq!(duration_result, 1.0);
294 println!("Seconds");
295 let duration = CFDuration::from_seconds(1, cal);
296 let duration_result = duration.num_seconds();
297 assert_eq!(duration_result, 1.0);
298 println!("Milliseconds");
299 let duration = CFDuration::from_milliseconds(1, cal);
300 let duration_result = duration.num_milliseconds();
301 assert_eq!(duration_result, 1.0);
302 println!("Microseconds");
303 let duration = CFDuration::from_microseconds(1, cal);
304 let duration_result = duration.num_microseconds();
305 assert_eq!(duration_result, 1.0);
306 println!("Nanoseconds");
307 let duration = CFDuration::from_nanoseconds(1, cal);
308 let duration_result = duration.num_nanoseconds();
309 assert_eq!(duration_result, 1.0);
310 }
311 let epsilon = 1e-6;
313 for cal in cals {
314 println!("Year");
315 let duration = CFDuration::from_years(1, cal);
316 let duration_result = duration.num_years();
317 assert!((duration_result - 1.0).abs() < epsilon);
318 println!("Month");
319 let duration = CFDuration::from_months(1, cal);
320 let duration_result = duration.num_months();
321 assert!((duration_result - 1.0).abs() < epsilon);
322 }
323 }
324}