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, Copy, Eq, PartialEq, Hash)]
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/// The date and time fields of a date-time value.
41#[derive(Debug, Clone, Copy)]
42pub(crate) struct DateTimeFields {
43    pub(crate) year:       i32,
44    pub(crate) month:      u8,
45    pub(crate) day:        u8,
46    pub(crate) hour:       u8,
47    pub(crate) minute:     u8,
48    pub(crate) second:     u8,
49    pub(crate) nanosecond: u32,
50}
51
52impl DateTimeFields {
53    #[inline]
54    pub(crate) fn from_parts(date_time: &impl DateTimeParts) -> Self {
55        let (year, month, day, hour, minute, second, nanosecond) = date_time.all_parts();
56
57        Self {
58            year,
59            month,
60            day,
61            hour,
62            minute,
63            second,
64            nanosecond,
65        }
66    }
67}
68
69/// The wall-clock date and time of a date-time value.
70// The field order matters because `Ord` is derived.
71#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
72struct WallClock {
73    year:               i32,
74    month:              u8,
75    day:                u8,
76    nanoseconds_of_day: u64,
77}
78
79impl WallClock {
80    #[inline]
81    fn from_parts(date_time: &impl DateTimeParts) -> Self {
82        let fields = DateTimeFields::from_parts(date_time);
83
84        Self {
85            year:               fields.year,
86            month:              fields.month,
87            day:                fields.day,
88            nanoseconds_of_day: (fields.hour as u64 * HOUR_NANOSECONDS)
89                + (fields.minute as u64 * MINUTE_NANOSECONDS)
90                + (fields.second as u64 * SECOND_NANOSECONDS)
91                + fields.nanosecond as u64,
92        }
93    }
94}
95
96#[derive(Debug)]
97struct _DateDiffResult {
98    pub(crate) earlier_nanoseconds_of_day: u64,
99    pub(crate) later_nanoseconds_of_day:   u64,
100    pub(crate) result:                     DateDiffResult,
101}
102
103/// The result of the `date_time_diff` function.
104#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
105pub struct DateTimeDiffResult {
106    pub years:       i32,
107    pub months:      i32,
108    pub days:        i32,
109    pub hours:       i32,
110    pub minutes:     i32,
111    pub seconds:     i32,
112    pub nanoseconds: i32,
113}
114
115impl DateTimeDiffResult {
116    #[doc(hidden)]
117    #[inline]
118    pub fn into_neg(mut self) -> Self {
119        self.years *= -1;
120        self.months *= -1;
121        self.days *= -1;
122        self.hours *= -1;
123        self.minutes *= -1;
124        self.seconds *= -1;
125        self.nanoseconds *= -1;
126
127        self
128    }
129}
130
131impl From<DateDiffResult> for DateTimeDiffResult {
132    #[inline]
133    fn from(value: DateDiffResult) -> Self {
134        DateTimeDiffResult {
135            years: value.years,
136            months: value.months,
137            days: value.days,
138            ..DateTimeDiffResult::default()
139        }
140    }
141}
142
143impl From<DateTimeDiffResult> for DateDiffResult {
144    #[inline]
145    fn from(value: DateTimeDiffResult) -> Self {
146        DateDiffResult {
147            years: value.years, months: value.months, days: value.days
148        }
149    }
150}
151
152/// A trait to represent a date-time difference with multiple units.
153pub trait DateTimeDiff {
154    #[inline]
155    fn years(&self) -> i32 {
156        0
157    }
158
159    #[inline]
160    fn months(&self) -> i32 {
161        0
162    }
163
164    #[inline]
165    fn days(&self) -> i32 {
166        0
167    }
168
169    #[inline]
170    fn hours(&self) -> i32 {
171        0
172    }
173
174    #[inline]
175    fn minutes(&self) -> i32 {
176        0
177    }
178
179    #[inline]
180    fn seconds(&self) -> i32 {
181        0
182    }
183
184    #[inline]
185    fn nanoseconds(&self) -> i32 {
186        0
187    }
188}
189
190/// A trait to expose the date and time fields used by this crate.
191///
192/// The fields describe the wall-clock date and time of the value.
193/// Each method must return a value in the range written in its documentation, otherwise the functions of this crate may panic.
194///
195/// This trait is also implemented for `chrono` types.
196/// If `chrono::Datelike` or `chrono::Timelike` is in scope at the same time (for example, by `use chrono::prelude::*;` and `use date_differencer::*;`), calling a method such as `year()` on a `chrono` value is ambiguous and does not compile.
197/// To avoid this, import only the items you need from this crate (for example, `use date_differencer::{date_diff, date_time_diff};`), or call the method with its full path (for example, `Datelike::year(&date_time)`).
198pub trait DateTimeParts: Ord {
199    /// Returns the year.
200    fn year(&self) -> i32;
201
202    /// Returns the month, from `1` to `12`.
203    fn month(&self) -> u8;
204
205    /// Returns the day of the month, from `1` to the number of days in the month.
206    fn day(&self) -> u8;
207
208    /// Returns the hour, from `0` to `23`.
209    fn hour(&self) -> u8;
210
211    /// Returns the minute, from `0` to `59`.
212    fn minute(&self) -> u8;
213
214    /// Returns the second, from `0` to `59`.
215    fn second(&self) -> u8;
216
217    /// Returns the nanosecond, from `0` to `999_999_999`.
218    fn nanosecond(&self) -> u32;
219
220    /// Returns all fields at once, in the order of year, month, day, hour, minute, second and nanosecond.
221    #[doc(hidden)]
222    #[inline]
223    fn all_parts(&self) -> (i32, u8, u8, u8, u8, u8, u32) {
224        (
225            self.year(),
226            self.month(),
227            self.day(),
228            self.hour(),
229            self.minute(),
230            self.second(),
231            self.nanosecond(),
232        )
233    }
234
235    /// Converts `other` to the time zone of `self` without changing the instant it represents.
236    #[doc(hidden)]
237    #[inline]
238    fn to_same_time_zone(&self, other: Self) -> Self
239    where
240        Self: Sized, {
241        other
242    }
243}
244
245impl DateTimeDiff for DateDiffResult {
246    #[inline]
247    fn years(&self) -> i32 {
248        self.years
249    }
250
251    #[inline]
252    fn months(&self) -> i32 {
253        self.months
254    }
255
256    #[inline]
257    fn days(&self) -> i32 {
258        self.days
259    }
260}
261
262impl DateTimeDiff for DateTimeDiffResult {
263    #[inline]
264    fn years(&self) -> i32 {
265        self.years
266    }
267
268    #[inline]
269    fn months(&self) -> i32 {
270        self.months
271    }
272
273    #[inline]
274    fn days(&self) -> i32 {
275        self.days
276    }
277
278    #[inline]
279    fn hours(&self) -> i32 {
280        self.hours
281    }
282
283    #[inline]
284    fn minutes(&self) -> i32 {
285        self.minutes
286    }
287
288    #[inline]
289    fn seconds(&self) -> i32 {
290        self.seconds
291    }
292
293    #[inline]
294    fn nanoseconds(&self) -> i32 {
295        self.nanoseconds
296    }
297}
298
299#[inline]
300const fn _nanoseconds_to_units(mut nanoseconds: u64) -> TimeDiffResult {
301    let h = nanoseconds / HOUR_NANOSECONDS;
302    nanoseconds -= h * HOUR_NANOSECONDS;
303
304    let m = nanoseconds / MINUTE_NANOSECONDS;
305    nanoseconds -= m * MINUTE_NANOSECONDS;
306
307    let s = nanoseconds / SECOND_NANOSECONDS;
308    nanoseconds -= s * SECOND_NANOSECONDS;
309
310    TimeDiffResult {
311        hours:       h as i32,
312        minutes:     m as i32,
313        seconds:     s as i32,
314        nanoseconds: nanoseconds as i32,
315    }
316}
317
318#[inline]
319const fn _time_diff(
320    earlier_nanoseconds_of_day: u64,
321    later_nanoseconds_of_day: u64,
322) -> TimeDiffResult {
323    let nanoseconds = if later_nanoseconds_of_day >= earlier_nanoseconds_of_day {
324        later_nanoseconds_of_day - earlier_nanoseconds_of_day
325    } else {
326        DAY_NANOSECONDS + later_nanoseconds_of_day - earlier_nanoseconds_of_day
327    };
328
329    _nanoseconds_to_units(nanoseconds)
330}
331
332fn _date_diff(earlier: &WallClock, later: &WallClock, start_from_later: bool) -> _DateDiffResult {
333    let mut earlier_year = earlier.year;
334    let mut earlier_month = earlier.month;
335    let mut earlier_date = earlier.day;
336
337    let mut later_year = later.year;
338    let mut later_month = later.month;
339    let mut later_date = later.day;
340
341    let later_nanoseconds_of_day = later.nanoseconds_of_day;
342    let earlier_nanoseconds_of_day = earlier.nanoseconds_of_day;
343
344    let years: i32;
345    let months: i32;
346    let days: i32;
347
348    if later_nanoseconds_of_day < earlier_nanoseconds_of_day {
349        // e.g. 12:00 to 11:59
350
351        if start_from_later {
352            // increase a day from the earlier date
353
354            if earlier_date < year_helper::get_days_in_month(earlier_year, earlier_month).unwrap() {
355                // e.g. 2020-01-12 12:00 to 2022-02-15 11:59
356
357                earlier_date += 1;
358            } else if earlier_month < 12 {
359                // e.g. 2020-01-31 12:00 to 2022-02-15 11:59
360
361                earlier_month += 1;
362                earlier_date = 1;
363            } else {
364                // e.g. 2020-12-31 12:00 to 2022-02-15 11:59
365
366                earlier_year += 1;
367                earlier_month = 1;
368                earlier_date = 1;
369            }
370        } else {
371            // decrease a day from the later date
372
373            if later_date > 1 {
374                // e.g. 2020-01-12 12:00 to 2022-02-15 11:59
375
376                later_date -= 1;
377            } else if later_month > 1 {
378                // e.g. 2020-01-12 12:00 to 2022-02-01 11:59
379
380                later_month -= 1;
381                later_date = year_helper::get_days_in_month(later_year, later_month).unwrap();
382            } else {
383                // e.g. 2020-01-12 12:00 to 2022-01-01 11:59
384
385                later_year -= 1;
386                later_month = 12;
387                later_date = 31;
388            }
389        }
390    }
391
392    let year_diff = later_year - earlier_year;
393    let month_diff = later_month as i32 - earlier_month as i32;
394
395    match month_diff.cmp(&0) {
396        Ordering::Greater => {
397            // e.g. 2010-01 to 2010-03
398
399            years = year_diff;
400
401            if later_date >= earlier_date {
402                // e.g. 2010-01-02 to 2010-03-04
403
404                months = month_diff;
405            } else {
406                // e.g. 2010-01-02 to 2010-03-01
407
408                months = month_diff - 1;
409            }
410        },
411        Ordering::Less => {
412            // e.g. 2009-11 to 2010-03
413
414            years = year_diff - 1;
415
416            if later_date >= earlier_date {
417                // e.g. 2009-11-02 to 2010-03-04
418
419                months = month_diff + 12;
420            } else {
421                // e.g. 2009-11-04 to 2010-03-02
422
423                months = month_diff + 11;
424            }
425        },
426        Ordering::Equal => {
427            // month_diff == 0, e.g. 2009-12 to 2010-12
428
429            if later_date >= earlier_date {
430                // e.g. 2009-12-02 to 2010-12-04
431
432                years = year_diff;
433                months = 0;
434            } else {
435                // e.g. 2009-12-04 to 2010-12-02
436
437                years = year_diff - 1;
438                months = 11;
439            }
440        },
441    }
442
443    if later_date >= earlier_date {
444        // e.g. 2010-01-02 to 2010-03-04, 2009-11-02 to 2010-03-04, 2009-12-02 to 2010-12-04
445
446        if start_from_later {
447            days = later_date
448                .min(year_helper::get_days_in_month(earlier_year, earlier_month).unwrap())
449                as i32
450                - earlier_date as i32;
451        } else {
452            days = later_date as i32 - earlier_date as i32;
453        }
454    } else {
455        // e.g. 2010-01-02 to 2010-03-01, 2009-11-04 to 2010-03-02, 2009-12-04 to 2010-12-02
456
457        if start_from_later {
458            if earlier_month < 12 {
459                later_date = later_date
460                    .min(year_helper::get_days_in_month(earlier_year, earlier_month + 1).unwrap())
461            } else {
462                // we don't need to handle this because the laterDate cannot be bigger than 31 (January has 31 days)
463            }
464
465            days = (later_date
466                + (year_helper::get_days_in_month(earlier_year, earlier_month).unwrap()
467                    - earlier_date)) as i32;
468        } else {
469            let days_in_month = if later_month > 1 {
470                year_helper::get_days_in_month(later_year, later_month - 1).unwrap()
471            } else {
472                31 // year_helper::get_days_in_month(later_year - 1, 12).unwrap()
473            };
474
475            if days_in_month > earlier_date {
476                days = (later_date + (days_in_month - earlier_date)) as i32;
477            } else {
478                days = later_date as i32;
479            }
480        }
481    }
482
483    _DateDiffResult {
484        earlier_nanoseconds_of_day,
485        later_nanoseconds_of_day,
486        result: DateDiffResult {
487            years,
488            months,
489            days,
490        },
491    }
492}
493
494/// Calculate the difference between two date-time values in years, months and days.
495///
496/// The result is positive when `to` is later than `from`, and negative when `to` is earlier than `from`.
497/// If the two values are in different time zones, `to` is converted to the time zone of `from` first.
498/// Then the result is calculated with the wall-clock date and time in that time zone.
499/// Only complete days are counted, so a remaining time shorter than a day is dropped.
500///
501/// # Example
502///
503/// ```rust
504/// # #[cfg(feature = "chrono")]
505/// # {
506/// use chrono::prelude::*;
507/// use date_differencer::{DateDiffResult, date_diff};
508///
509/// let date = Local.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
510/// let date_after_1_year_1_day =
511///     Local.with_ymd_and_hms(2001, 1, 2, 0, 0, 0).unwrap();
512///
513/// assert_eq!(
514///     DateDiffResult {
515///         years: 1,
516///         days: 1,
517///         ..DateDiffResult::default()
518///     },
519///     date_diff(date, date_after_1_year_1_day)
520/// );
521/// # }
522/// ```
523#[inline]
524pub fn date_diff<DT: DateTimeParts>(from: DT, to: DT) -> DateDiffResult {
525    let to = WallClock::from_parts(&from.to_same_time_zone(to));
526    let from = WallClock::from_parts(&from);
527
528    match to.cmp(&from) {
529        Ordering::Greater => _date_diff(&from, &to, false).result,
530        Ordering::Less => _date_diff(&to, &from, true).result.into_neg(),
531        Ordering::Equal => DateDiffResult::default(),
532    }
533}
534
535/// Calculate the difference between two date-time values in years, months, days, hours, minutes, seconds and nanoseconds.
536///
537/// The result is positive when `to` is later than `from`, and negative when `to` is earlier than `from`.
538/// If the two values are in different time zones, `to` is converted to the time zone of `from` first.
539/// Then the result is calculated with the wall-clock date and time in that time zone.
540///
541/// # Example
542///
543/// ```rust
544/// # #[cfg(feature = "chrono")]
545/// # {
546/// use chrono::prelude::*;
547/// use date_differencer::{DateTimeDiffResult, date_time_diff};
548///
549/// let date = Local.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
550/// let date_after_1_year_1_day_5_minutes =
551///     Local.with_ymd_and_hms(2001, 1, 2, 0, 5, 0).unwrap();
552///
553/// assert_eq!(
554///     DateTimeDiffResult {
555///         years: 1,
556///         days: 1,
557///         minutes: 5,
558///         ..DateTimeDiffResult::default()
559///     },
560///     date_time_diff(date, date_after_1_year_1_day_5_minutes)
561/// );
562/// # }
563/// ```
564#[inline]
565pub fn date_time_diff<DT: DateTimeParts>(from: DT, to: DT) -> DateTimeDiffResult {
566    let to = WallClock::from_parts(&from.to_same_time_zone(to));
567    let from = WallClock::from_parts(&from);
568
569    match to.cmp(&from) {
570        Ordering::Greater => {
571            let date_diff = _date_diff(&from, &to, false);
572
573            let time_diff = _time_diff(
574                date_diff.earlier_nanoseconds_of_day,
575                date_diff.later_nanoseconds_of_day,
576            );
577
578            let date_diff = date_diff.result;
579
580            DateTimeDiffResult {
581                years:       date_diff.years,
582                months:      date_diff.months,
583                days:        date_diff.days,
584                hours:       time_diff.hours,
585                minutes:     time_diff.minutes,
586                seconds:     time_diff.seconds,
587                nanoseconds: time_diff.nanoseconds,
588            }
589        },
590        Ordering::Less => {
591            let date_diff = _date_diff(&to, &from, true);
592
593            let time_diff = _time_diff(
594                date_diff.earlier_nanoseconds_of_day,
595                date_diff.later_nanoseconds_of_day,
596            );
597
598            let date_diff = date_diff.result;
599
600            DateTimeDiffResult {
601                years:       -date_diff.years,
602                months:      -date_diff.months,
603                days:        -date_diff.days,
604                hours:       -time_diff.hours,
605                minutes:     -time_diff.minutes,
606                seconds:     -time_diff.seconds,
607                nanoseconds: -time_diff.nanoseconds,
608            }
609        },
610        Ordering::Equal => DateTimeDiffResult::default(),
611    }
612}