Skip to main content

date_differencer/
diff.rs

1use core::cmp::Ordering;
2
3use chrono::prelude::*;
4
5use super::constants::*;
6
7#[derive(Debug)]
8struct TimeDiffResult {
9    pub(crate) hours:       i32,
10    pub(crate) minutes:     i32,
11    pub(crate) seconds:     i32,
12    pub(crate) nanoseconds: i32,
13}
14
15/// The result of the `date_diff` function.
16#[derive(Debug, Default, Clone, Eq, PartialEq)]
17pub struct DateDiffResult {
18    pub years:  i32,
19    pub months: i32,
20    pub days:   i32,
21}
22
23impl DateDiffResult {
24    #[doc(hidden)]
25    #[inline]
26    pub fn into_neg(mut self) -> Self {
27        self.years *= -1;
28        self.months *= -1;
29        self.days *= -1;
30
31        self
32    }
33}
34
35#[derive(Debug)]
36struct _DateDiffResult {
37    pub(crate) earlier_nanoseconds_of_day: u64,
38    pub(crate) later_nanoseconds_of_day:   u64,
39    pub(crate) result:                     DateDiffResult,
40}
41
42/// The result of the `date_time_diff` function.
43#[derive(Debug, Default, Clone, Eq, PartialEq)]
44pub struct DateTimeDiffResult {
45    pub years:       i32,
46    pub months:      i32,
47    pub days:        i32,
48    pub hours:       i32,
49    pub minutes:     i32,
50    pub seconds:     i32,
51    pub nanoseconds: i32,
52}
53
54impl DateTimeDiffResult {
55    #[doc(hidden)]
56    #[inline]
57    pub fn into_neg(mut self) -> Self {
58        self.years *= -1;
59        self.months *= -1;
60        self.days *= -1;
61        self.hours *= -1;
62        self.minutes *= -1;
63        self.seconds *= -1;
64        self.nanoseconds *= -1;
65
66        self
67    }
68}
69
70impl From<DateDiffResult> for DateTimeDiffResult {
71    #[inline]
72    fn from(value: DateDiffResult) -> Self {
73        DateTimeDiffResult {
74            years: value.years,
75            months: value.months,
76            days: value.days,
77            ..DateTimeDiffResult::default()
78        }
79    }
80}
81
82impl From<DateTimeDiffResult> for DateDiffResult {
83    #[inline]
84    fn from(value: DateTimeDiffResult) -> Self {
85        DateDiffResult {
86            years: value.years, months: value.months, days: value.days
87        }
88    }
89}
90
91/// A trait to represent a date-time difference with multiple units.
92pub trait DateTimeDiff {
93    #[inline]
94    fn years(&self) -> i32 {
95        0
96    }
97
98    #[inline]
99    fn months(&self) -> i32 {
100        0
101    }
102
103    #[inline]
104    fn days(&self) -> i32 {
105        0
106    }
107
108    #[inline]
109    fn hours(&self) -> i32 {
110        0
111    }
112
113    #[inline]
114    fn minutes(&self) -> i32 {
115        0
116    }
117
118    #[inline]
119    fn seconds(&self) -> i32 {
120        0
121    }
122
123    #[inline]
124    fn nanoseconds(&self) -> i32 {
125        0
126    }
127}
128
129impl DateTimeDiff for DateDiffResult {
130    #[inline]
131    fn years(&self) -> i32 {
132        self.years
133    }
134
135    #[inline]
136    fn months(&self) -> i32 {
137        self.months
138    }
139
140    #[inline]
141    fn days(&self) -> i32 {
142        self.days
143    }
144}
145
146impl DateTimeDiff for DateTimeDiffResult {
147    #[inline]
148    fn years(&self) -> i32 {
149        self.years
150    }
151
152    #[inline]
153    fn months(&self) -> i32 {
154        self.months
155    }
156
157    #[inline]
158    fn days(&self) -> i32 {
159        self.days
160    }
161
162    #[inline]
163    fn hours(&self) -> i32 {
164        self.hours
165    }
166
167    #[inline]
168    fn minutes(&self) -> i32 {
169        self.minutes
170    }
171
172    #[inline]
173    fn seconds(&self) -> i32 {
174        self.seconds
175    }
176
177    #[inline]
178    fn nanoseconds(&self) -> i32 {
179        self.nanoseconds
180    }
181}
182
183#[inline]
184const fn _nanoseconds_to_units(mut nanoseconds: u64) -> TimeDiffResult {
185    let h = nanoseconds / HOUR_NANOSECONDS;
186    nanoseconds -= h * HOUR_NANOSECONDS;
187
188    let m = nanoseconds / MINUTE_NANOSECONDS;
189    nanoseconds -= m * MINUTE_NANOSECONDS;
190
191    let s = nanoseconds / SECOND_NANOSECONDS;
192    nanoseconds -= s * SECOND_NANOSECONDS;
193
194    TimeDiffResult {
195        hours:       h as i32,
196        minutes:     m as i32,
197        seconds:     s as i32,
198        nanoseconds: nanoseconds as i32,
199    }
200}
201
202#[inline]
203const fn _time_diff(
204    earlier_nanoseconds_of_day: u64,
205    later_nanoseconds_of_day: u64,
206) -> TimeDiffResult {
207    let nanoseconds = if later_nanoseconds_of_day >= earlier_nanoseconds_of_day {
208        later_nanoseconds_of_day - earlier_nanoseconds_of_day
209    } else {
210        DAY_NANOSECONDS + later_nanoseconds_of_day - earlier_nanoseconds_of_day
211    };
212
213    _nanoseconds_to_units(nanoseconds)
214}
215
216#[inline]
217fn _date_time_nanoseconds_of_day(date_time: impl Timelike) -> u64 {
218    (date_time.hour() as u64 * HOUR_NANOSECONDS)
219        + (date_time.minute() as u64 * MINUTE_NANOSECONDS)
220        + (date_time.second() as u64 * SECOND_NANOSECONDS)
221        + date_time.nanosecond() as u64
222}
223
224fn _date_diff(
225    earlier: impl Datelike + Timelike,
226    later: impl Datelike + Timelike,
227    start_from_later: bool,
228) -> _DateDiffResult {
229    let mut earlier_year = earlier.year();
230    let mut earlier_month = earlier.month() as u8;
231    let mut earlier_date = earlier.day() as u8;
232
233    let mut later_year = later.year();
234    let mut later_month = later.month() as u8;
235    let mut later_date = later.day() as u8;
236
237    let later_nanoseconds_of_day = _date_time_nanoseconds_of_day(later);
238    let earlier_nanoseconds_of_day = _date_time_nanoseconds_of_day(earlier);
239
240    let years: i32;
241    let months: i32;
242    let days: i32;
243
244    if later_nanoseconds_of_day < earlier_nanoseconds_of_day {
245        // e.g. 12:00 to 11:59
246
247        if start_from_later {
248            // increase a day from the earlier date
249
250            if earlier_date < year_helper::get_days_in_month(earlier_year, earlier_month).unwrap() {
251                // e.g. 2020-01-12 12:00 to 2022-02-15 11:59
252
253                earlier_date += 1;
254            } else if earlier_month < 12 {
255                // e.g. 2020-01-31 12:00 to 2022-02-15 11:59
256
257                earlier_month += 1;
258                earlier_date = 1;
259            } else {
260                // e.g. 2020-12-31 12:00 to 2022-02-15 11:59
261
262                earlier_year += 1;
263                earlier_month = 1;
264                earlier_date = 1;
265            }
266        } else {
267            // decrease a day from the later date
268
269            if later_date > 1 {
270                // e.g. 2020-01-12 12:00 to 2022-02-15 11:59
271
272                later_date -= 1;
273            } else if later_month > 1 {
274                // e.g. 2020-01-12 12:00 to 2022-02-01 11:59
275
276                later_month -= 1;
277                later_date = year_helper::get_days_in_month(later_year, later_month).unwrap();
278            } else {
279                // e.g. 2020-01-12 12:00 to 2022-01-01 11:59
280
281                later_year -= 1;
282                later_month = 12;
283                later_date = 31;
284            }
285        }
286    }
287
288    let year_diff = later_year - earlier_year;
289    let month_diff = later_month as i32 - earlier_month as i32;
290
291    match month_diff.cmp(&0) {
292        Ordering::Greater => {
293            // e.g. 2010-01 to 2010-03
294
295            years = year_diff;
296
297            if later_date >= earlier_date {
298                // e.g. 2010-01-02 to 2010-03-04
299
300                months = month_diff;
301            } else {
302                // e.g. 2010-01-02 to 2010-03-01
303
304                months = month_diff - 1;
305            }
306        },
307        Ordering::Less => {
308            // e.g. 2009-11 to 2010-03
309
310            years = year_diff - 1;
311
312            if later_date >= earlier_date {
313                // e.g. 2009-11-02 to 2010-03-04
314
315                months = month_diff + 12;
316            } else {
317                // e.g. 2009-11-02 to 2010-03-04
318
319                months = month_diff + 11;
320            }
321        },
322        Ordering::Equal => {
323            // month_diff == 0, e.g. 2009-12 to 2010-12
324
325            if later_date >= earlier_date {
326                // e.g. 2009-12-02 to 2010-12-04
327
328                years = year_diff;
329                months = 0;
330            } else {
331                // e.g. 2009-12-04 to 2010-12-02
332
333                years = year_diff - 1;
334                months = 11;
335            }
336        },
337    }
338
339    if later_date >= earlier_date {
340        // e.g. 2010-01-02 to 2010-03-04, 2009-11-02 to 2010-03-04, 2009-12-02 to 2010-12-04
341
342        if start_from_later {
343            days = later_date
344                .min(year_helper::get_days_in_month(earlier_year, earlier_month).unwrap())
345                as i32
346                - earlier_date as i32;
347        } else {
348            days = later_date as i32 - earlier_date as i32;
349        }
350    } else {
351        // e.g. 2010-01-02 to 2010-03-01, 2009-11-02 to 2010-03-04, 2009-12-04 to 2010-12-02
352
353        if start_from_later {
354            if earlier_month < 12 {
355                later_date = later_date
356                    .min(year_helper::get_days_in_month(earlier_year, earlier_month + 1).unwrap())
357            } else {
358                // we don't need to handle this because the laterDate cannot be bigger than 31 (January has 31 days)
359            }
360
361            days = (later_date
362                + (year_helper::get_days_in_month(earlier_year, earlier_month).unwrap()
363                    - earlier_date)) as i32;
364        } else {
365            let days_in_month = if later_month > 1 {
366                year_helper::get_days_in_month(later_year, later_month - 1).unwrap()
367            } else {
368                31 // year_helper::get_days_in_month(later_year - 1, 12).unwrap()
369            };
370
371            if days_in_month > earlier_date {
372                days = (later_date + (days_in_month - earlier_date)) as i32;
373            } else {
374                days = later_date as i32;
375            }
376        }
377    }
378
379    _DateDiffResult {
380        earlier_nanoseconds_of_day,
381        later_nanoseconds_of_day,
382        result: DateDiffResult {
383            years,
384            months,
385            days,
386        },
387    }
388}
389
390/// Calculate the difference between two `DateTime` instances.
391///
392/// # Example
393///
394/// ```rust
395/// use chrono::prelude::*;
396/// use date_differencer::{date_diff, DateDiffResult};
397///
398/// let date = Local.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
399/// let date_after_1_year_1_day =
400///     Local.with_ymd_and_hms(2001, 1, 2, 0, 0, 0).unwrap();
401///
402/// assert_eq!(
403///     DateDiffResult {
404///         years: 1,
405///         days: 1,
406///         ..DateDiffResult::default()
407///     },
408///     date_diff(date, date_after_1_year_1_day)
409/// );
410/// ```
411#[inline]
412pub fn date_diff<DT: Datelike + Timelike + Ord>(from: DT, to: DT) -> DateDiffResult {
413    match to.cmp(&from) {
414        Ordering::Greater => _date_diff(from, to, false).result,
415        Ordering::Less => _date_diff(to, from, true).result.into_neg(),
416        Ordering::Equal => DateDiffResult::default(),
417    }
418}
419
420/// Calculate the difference between two `DateTime` instances.
421///
422/// # Example
423///
424/// ```rust
425/// use chrono::prelude::*;
426/// use date_differencer::{date_time_diff, DateTimeDiffResult};
427///
428/// let date = Local.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
429/// let date_after_1_year_1_day_5_minutes =
430///     Local.with_ymd_and_hms(2001, 1, 2, 0, 5, 0).unwrap();
431///
432/// assert_eq!(
433///     DateTimeDiffResult {
434///         years: 1,
435///         days: 1,
436///         minutes: 5,
437///         ..DateTimeDiffResult::default()
438///     },
439///     date_time_diff(date, date_after_1_year_1_day_5_minutes)
440/// );
441/// ```
442#[inline]
443pub fn date_time_diff<DT: Datelike + Timelike + Ord>(from: DT, to: DT) -> DateTimeDiffResult {
444    match to.cmp(&from) {
445        Ordering::Greater => {
446            let date_diff = _date_diff(from, to, false);
447
448            let time_diff = _time_diff(
449                date_diff.earlier_nanoseconds_of_day,
450                date_diff.later_nanoseconds_of_day,
451            );
452
453            let date_diff = date_diff.result;
454
455            DateTimeDiffResult {
456                years:       date_diff.years,
457                months:      date_diff.months,
458                days:        date_diff.days,
459                hours:       time_diff.hours,
460                minutes:     time_diff.minutes,
461                seconds:     time_diff.seconds,
462                nanoseconds: time_diff.nanoseconds,
463            }
464        },
465        Ordering::Less => {
466            let date_diff = _date_diff(to, from, true);
467
468            let time_diff = _time_diff(
469                date_diff.earlier_nanoseconds_of_day,
470                date_diff.later_nanoseconds_of_day,
471            );
472
473            let date_diff = date_diff.result;
474
475            DateTimeDiffResult {
476                years:       -date_diff.years,
477                months:      -date_diff.months,
478                days:        -date_diff.days,
479                hours:       -time_diff.hours,
480                minutes:     -time_diff.minutes,
481                seconds:     -time_diff.seconds,
482                nanoseconds: -time_diff.nanoseconds,
483            }
484        },
485        Ordering::Equal => DateTimeDiffResult::default(),
486    }
487}