Skip to main content

date_differencer/diff/
mod.rs

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