cftime_rs/
duration.rs

1//! Module related to duration
2//! These CFDuration can be added to a CFDateTime by using the `+` or `-` operator
3//! Internally it uses the timestamp in seconds representation
4
5use crate::{calendars::Calendar, constants, utils::normalize_nanoseconds};
6
7/// A CF duration
8#[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    /// Returns the calendar
28    pub fn calendar(&self) -> Calendar {
29        self.calendar
30    }
31    /// Makes a new `Duration` with given number of years.
32    /// Depends on the Calendar definitions found in  the CF conventions
33    /// See also [Calendar]
34    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    /// Makes a new `Duration` with given number of months.
46    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    /// Makes a new `Duration` with given number of weeks
51    pub fn from_weeks(weeks: i64, calendar: Calendar) -> CFDuration {
52        Self::new(weeks * 7 * 24 * 60 * 60, 0, calendar)
53    }
54    /// Makes a new `Duration` with given number of days
55    pub fn from_days(days: i64, calendar: Calendar) -> CFDuration {
56        Self::new(days * 24 * 60 * 60, 0, calendar)
57    }
58    /// Makes a new `Duration` with given number of hours
59    pub fn from_hours(hours: i64, calendar: Calendar) -> CFDuration {
60        Self::new(hours * 60 * 60, 0, calendar)
61    }
62    /// Makes a new `Duration` with given number of minutes
63    pub fn from_minutes(minutes: i64, calendar: Calendar) -> CFDuration {
64        Self::new(minutes * 60, 0, calendar)
65    }
66    /// Makes a new `Duration` with given number of seconds
67    pub fn from_seconds(seconds: i64, calendar: Calendar) -> CFDuration {
68        Self::new(seconds, 0, calendar)
69    }
70    /// Makes a new `Duration` with given number of milliseconds
71    pub fn from_milliseconds(milliseconds: i64, calendar: Calendar) -> CFDuration {
72        Self::new(0, milliseconds * 1_000_000, calendar)
73    }
74    /// Makes a new `Duration` with given number of microseconds
75    pub fn from_microseconds(microseconds: i64, calendar: Calendar) -> CFDuration {
76        Self::new(0, 1_000 * microseconds, calendar)
77    }
78    /// Makes a new `Duration` with given number of nanoseconds
79    pub fn from_nanoseconds(nanoseconds: i64, calendar: Calendar) -> CFDuration {
80        Self::new(0, nanoseconds, calendar)
81    }
82    /// Return the total number of years in the duration.
83    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    /// Return the total number of motnhs in the duration.
95    pub fn num_months(&self) -> f64 {
96        self.num_years() * 12.
97    }
98    /// Return the total number of weeks in the duration.
99    pub fn num_weeks(&self) -> f64 {
100        self.num_days() / 7.
101    }
102    /// Return the total number of days in the duration.
103    pub fn num_days(&self) -> f64 {
104        self.num_hours() / 24.
105    }
106    /// Return the total number of hours in the duration.
107    pub fn num_hours(&self) -> f64 {
108        self.num_minutes() / 60.
109    }
110    /// Return the total number of minutes in the duration.
111    pub fn num_minutes(&self) -> f64 {
112        self.num_seconds() / 60.0
113    }
114    /// Return the total number of seconds in the duration.
115    pub fn num_seconds(&self) -> f64 {
116        self.seconds as f64 + self.nanoseconds as f64 / 1e9
117    }
118    /// Return the total number of milliseconds in the duration.
119    pub fn num_milliseconds(&self) -> f64 {
120        self.num_seconds() * 1e3
121    }
122    /// Return the total number of microseconds in the duration.
123    pub fn num_microseconds(&self) -> f64 {
124        self.num_seconds() * 1e6
125    }
126    /// Return the total number of nanoseconds in the duration.
127    pub fn num_nanoseconds(&self) -> f64 {
128        (self.seconds * 1_000_000_000 + self.nanoseconds as i64) as f64
129    }
130}
131
132/// Display a CFDuration with te ISO 8601 format of duration.
133///
134/// # Example
135/// ```
136/// CFDuration::from_days(1).__repr__()
137/// assert_eq!(CFDuration::from_days(1).__repr__(),  "P0Y0M1DT0H0M0S");
138/// ```
139///
140impl 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                // Classic (a+b)(d+c)
241                // f32 to i64 does not give the same result all the time
242                // i.e. 8276688000.0 gives 8276687872
243                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        // Years and month are not exact so we need to test by omparing with an epsilon
312        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}