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
224#[inline]
225const fn _time_nanoseconds_of_day(timestamp: i64) -> u64 {
226    if timestamp >= 0 {
227        (timestamp as u64) % DAY_NANOSECONDS
228    } else {
229        let mut t = DAY_NANOSECONDS + ((-timestamp) as u64 % DAY_NANOSECONDS);
230
231        if t == DAY_NANOSECONDS {
232            t = 0;
233        }
234
235        t
236    }
237}
238
239fn _date_diff(
240    earlier: impl Datelike + Timelike,
241    later: impl Datelike + Timelike,
242    start_from_later: bool,
243) -> _DateDiffResult {
244    let mut earlier_year = earlier.year();
245    let mut earlier_month = earlier.month() as u8;
246    let mut earlier_date = earlier.day() as u8;
247
248    let mut later_year = later.year();
249    let mut later_month = later.month() as u8;
250    let mut later_date = later.day() as u8;
251
252    let later_nanoseconds_of_day = _date_time_nanoseconds_of_day(later);
253    let earlier_nanoseconds_of_day = _date_time_nanoseconds_of_day(earlier);
254
255    let years: i32;
256    let months: i32;
257    let days: i32;
258
259    if later_nanoseconds_of_day < earlier_nanoseconds_of_day {
260        // e.g. 12:00 to 11:59
261
262        if start_from_later {
263            // increase a day from the earlier date
264
265            if earlier_date < year_helper::get_days_in_month(earlier_year, earlier_month).unwrap() {
266                // e.g. 2020-01-12 12:00 to 2022-02-15 11:59
267
268                earlier_date += 1;
269            } else if earlier_month < 12 {
270                // e.g. 2020-01-31 12:00 to 2022-02-15 11:59
271
272                earlier_month += 1;
273                earlier_date = 1;
274            } else {
275                // e.g. 2020-12-31 12:00 to 2022-02-15 11:59
276
277                earlier_year += 1;
278                earlier_month = 1;
279                earlier_date = 1;
280            }
281        } else {
282            // decrease a day from the later date
283
284            if later_date > 1 {
285                // e.g. 2020-01-12 12:00 to 2022-02-15 11:59
286
287                later_date -= 1;
288            } else if later_month > 1 {
289                // e.g. 2020-01-12 12:00 to 2022-02-01 11:59
290
291                later_month -= 1;
292                later_date = year_helper::get_days_in_month(later_year, later_month).unwrap();
293            } else {
294                // e.g. 2020-01-12 12:00 to 2022-01-01 11:59
295
296                later_year -= 1;
297                later_month = 12;
298                later_date = 31;
299            }
300        }
301    }
302
303    let year_diff = later_year - earlier_year;
304    let month_diff = later_month as i32 - earlier_month as i32;
305
306    match month_diff.cmp(&0) {
307        Ordering::Greater => {
308            // e.g. 2010-01 to 2010-03
309
310            years = year_diff;
311
312            if later_date >= earlier_date {
313                // e.g. 2010-01-02 to 2010-03-04
314
315                months = month_diff;
316            } else {
317                // e.g. 2010-01-02 to 2010-03-01
318
319                months = month_diff - 1;
320            }
321        },
322        Ordering::Less => {
323            // e.g. 2009-11 to 2010-03
324
325            years = year_diff - 1;
326
327            if later_date >= earlier_date {
328                // e.g. 2009-11-02 to 2010-03-04
329
330                months = month_diff + 12;
331            } else {
332                // e.g. 2009-11-02 to 2010-03-04
333
334                months = month_diff + 11;
335            }
336        },
337        Ordering::Equal => {
338            // month_diff == 0, e.g. 2009-12 to 2010-12
339
340            if later_date >= earlier_date {
341                // e.g. 2009-12-02 to 2010-12-04
342
343                years = year_diff;
344                months = 0;
345            } else {
346                // e.g. 2009-12-04 to 2010-12-02
347
348                years = year_diff - 1;
349                months = 11;
350            }
351        },
352    }
353
354    if later_date >= earlier_date {
355        // e.g. 2010-01-02 to 2010-03-04, 2009-11-02 to 2010-03-04, 2009-12-02 to 2010-12-04
356
357        if start_from_later {
358            days = later_date
359                .min(year_helper::get_days_in_month(earlier_year, earlier_month).unwrap())
360                as i32
361                - earlier_date as i32;
362        } else {
363            days = later_date as i32 - earlier_date as i32;
364        }
365    } else {
366        // e.g. 2010-01-02 to 2010-03-01, 2009-11-02 to 2010-03-04, 2009-12-04 to 2010-12-02
367
368        if start_from_later {
369            if earlier_month < 12 {
370                later_date = later_date
371                    .min(year_helper::get_days_in_month(earlier_year, earlier_month + 1).unwrap())
372            } else {
373                // we don't need to handle this because the laterDate cannot be bigger than 31 (January has 31 days)
374            }
375
376            days = (later_date
377                + (year_helper::get_days_in_month(earlier_year, earlier_month).unwrap()
378                    - earlier_date)) as i32;
379        } else {
380            let days_in_month = if later_month > 1 {
381                year_helper::get_days_in_month(later_year, later_month - 1).unwrap()
382            } else {
383                31 // year_helper::get_days_in_month(later_year - 1, 12).unwrap()
384            };
385
386            if days_in_month > earlier_date {
387                days = (later_date + (days_in_month - earlier_date)) as i32;
388            } else {
389                days = later_date as i32;
390            }
391        }
392    }
393
394    _DateDiffResult {
395        earlier_nanoseconds_of_day,
396        later_nanoseconds_of_day,
397        result: DateDiffResult {
398            years,
399            months,
400            days,
401        },
402    }
403}
404
405/// Calculate the difference between two `DateTime` instances.
406///
407/// # Example
408///
409/// ```rust
410/// use chrono::prelude::*;
411/// use date_differencer::{date_diff, DateDiffResult};
412///
413/// let date = Local.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
414/// let date_after_1_year_1_day =
415///     Local.with_ymd_and_hms(2001, 1, 2, 0, 0, 0).unwrap();
416///
417/// assert_eq!(
418///     DateDiffResult {
419///         years: 1,
420///         days: 1,
421///         ..DateDiffResult::default()
422///     },
423///     date_diff(date, date_after_1_year_1_day)
424/// );
425/// ```
426#[inline]
427pub fn date_diff<DT: Datelike + Timelike + Ord>(from: DT, to: DT) -> DateDiffResult {
428    match to.cmp(&from) {
429        Ordering::Greater => _date_diff(from, to, false).result,
430        Ordering::Less => _date_diff(to, from, true).result.into_neg(),
431        Ordering::Equal => DateDiffResult::default(),
432    }
433}
434
435/// Calculate the difference between two `DateTime` instances.
436///
437/// # Example
438///
439/// ```rust
440/// use chrono::prelude::*;
441/// use date_differencer::{date_time_diff, DateTimeDiffResult};
442///
443/// let date = Local.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
444/// let date_after_1_year_1_day_5_minutes =
445///     Local.with_ymd_and_hms(2001, 1, 2, 0, 5, 0).unwrap();
446///
447/// assert_eq!(
448///     DateTimeDiffResult {
449///         years: 1,
450///         days: 1,
451///         minutes: 5,
452///         ..DateTimeDiffResult::default()
453///     },
454///     date_time_diff(date, date_after_1_year_1_day_5_minutes)
455/// );
456/// ```
457#[inline]
458pub fn date_time_diff<DT: Datelike + Timelike + Ord>(from: DT, to: DT) -> DateTimeDiffResult {
459    match to.cmp(&from) {
460        Ordering::Greater => {
461            let date_diff = _date_diff(from, to, false);
462
463            let time_diff = _time_diff(
464                date_diff.earlier_nanoseconds_of_day,
465                date_diff.later_nanoseconds_of_day,
466            );
467
468            let date_diff = date_diff.result;
469
470            DateTimeDiffResult {
471                years:       date_diff.years,
472                months:      date_diff.months,
473                days:        date_diff.days,
474                hours:       time_diff.hours,
475                minutes:     time_diff.minutes,
476                seconds:     time_diff.seconds,
477                nanoseconds: time_diff.nanoseconds,
478            }
479        },
480        Ordering::Less => {
481            let date_diff = _date_diff(to, from, true);
482
483            let time_diff = _time_diff(
484                date_diff.earlier_nanoseconds_of_day,
485                date_diff.later_nanoseconds_of_day,
486            );
487
488            let date_diff = date_diff.result;
489
490            DateTimeDiffResult {
491                years:       -date_diff.years,
492                months:      -date_diff.months,
493                days:        -date_diff.days,
494                hours:       -time_diff.hours,
495                minutes:     -time_diff.minutes,
496                seconds:     -time_diff.seconds,
497                nanoseconds: -time_diff.nanoseconds,
498            }
499        },
500        Ordering::Equal => DateTimeDiffResult::default(),
501    }
502}