Skip to main content

date_differencer/add_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
8#[cfg(any(feature = "chrono", feature = "jiff", feature = "time"))]
9use super::constants::*;
10use super::{DateTimeDiff, DateTimeParts};
11
12#[cfg(any(feature = "chrono", feature = "jiff", feature = "time"))]
13#[derive(Debug, Clone, Copy)]
14struct AddedDateTimeParts {
15    year:       i32,
16    month:      u8,
17    day:        u8,
18    hour:       u8,
19    minute:     u8,
20    second:     u8,
21    nanosecond: u32,
22}
23
24#[cfg(any(feature = "chrono", feature = "jiff", feature = "time"))]
25#[inline]
26fn month_add(year: &mut i32, month: &mut i32, n: i32) -> Option<()> {
27    *month = month.checked_add(n)?;
28
29    if *month >= 12 {
30        *year = year.checked_add(*month / 12)?;
31        *month %= 12;
32    } else if *month < 0 {
33        *year = year.checked_add(*month / 12 - 1)?;
34
35        *month = 12 - (-*month % 12);
36
37        if *month == 12 {
38            *month = 0;
39        }
40    }
41
42    Some(())
43}
44
45#[cfg(any(feature = "chrono", feature = "jiff", feature = "time"))]
46#[inline]
47fn date_add(year: &mut i32, month: &mut i32, date: &mut i32, n: i32) -> Option<()> {
48    *date = date.checked_add(n)?;
49
50    if *date == 0 {
51        month_add(year, month, -1)?;
52
53        *date = year_helper::get_days_in_month(*year, (*month + 1) as u8).unwrap() as i32;
54    } else if *date > 28 {
55        loop {
56            let days_in_month =
57                year_helper::get_days_in_month(*year, (*month + 1) as u8).unwrap() as i32;
58
59            if *date <= days_in_month {
60                break;
61            }
62
63            month_add(year, month, 1)?;
64
65            *date -= days_in_month;
66        }
67    } else if *date < 0 {
68        loop {
69            month_add(year, month, -1)?;
70
71            let days_in_month =
72                year_helper::get_days_in_month(*year, (*month + 1) as u8).unwrap() as i32;
73
74            if -*date < days_in_month {
75                *date += days_in_month;
76                break;
77            }
78
79            *date += days_in_month;
80        }
81    }
82
83    Some(())
84}
85
86#[cfg(any(feature = "chrono", feature = "jiff", feature = "time"))]
87#[inline]
88fn hour_add(year: &mut i32, month: &mut i32, date: &mut i32, hour: &mut i32, n: i32) -> Option<()> {
89    *hour = hour.checked_add(n)?;
90
91    if *hour >= 24 {
92        date_add(year, month, date, *hour / 24)?;
93        *hour %= 24;
94    } else if *hour < 0 {
95        date_add(year, month, date, *hour / 24 - 1)?;
96
97        *hour = 24 - (-*hour % 24);
98
99        if *hour == 24 {
100            *hour = 0;
101        }
102    }
103
104    Some(())
105}
106
107#[cfg(any(feature = "chrono", feature = "jiff", feature = "time"))]
108#[inline]
109fn minute_add(
110    year: &mut i32,
111    month: &mut i32,
112    date: &mut i32,
113    hour: &mut i32,
114    minute: &mut i32,
115    n: i32,
116) -> Option<()> {
117    *minute = minute.checked_add(n)?;
118
119    if *minute >= 60 {
120        hour_add(year, month, date, hour, *minute / 60)?;
121        *minute %= 60;
122    } else if *minute < 0 {
123        hour_add(year, month, date, hour, *minute / 60 - 1)?;
124
125        *minute = 60 - (-*minute % 60);
126
127        if *minute == 60 {
128            *minute = 0;
129        }
130    }
131
132    Some(())
133}
134
135#[cfg(any(feature = "chrono", feature = "jiff", feature = "time"))]
136#[inline]
137fn second_add(
138    year: &mut i32,
139    month: &mut i32,
140    date: &mut i32,
141    hour: &mut i32,
142    minute: &mut i32,
143    second: &mut i32,
144    n: i32,
145) -> Option<()> {
146    *second = second.checked_add(n)?;
147
148    if *second >= 60 {
149        minute_add(year, month, date, hour, minute, *second / 60)?;
150        *second %= 60;
151    } else if *second < 0 {
152        minute_add(year, month, date, hour, minute, *second / 60 - 1)?;
153
154        *second = 60 - (-*second % 60);
155
156        if *second == 60 {
157            *second = 0;
158        }
159    }
160
161    Some(())
162}
163
164#[cfg(any(feature = "chrono", feature = "jiff", feature = "time"))]
165#[allow(clippy::too_many_arguments)]
166#[inline]
167fn nanosecond_add(
168    year: &mut i32,
169    month: &mut i32,
170    date: &mut i32,
171    hour: &mut i32,
172    minute: &mut i32,
173    second: &mut i32,
174    nanosecond: &mut i32,
175    n: i32,
176) -> Option<()> {
177    const SECOND_NANOSECONDS_I32: i32 = SECOND_NANOSECONDS as i32;
178
179    let total_nanoseconds = nanosecond.checked_add(n)?;
180    let seconds = total_nanoseconds.div_euclid(SECOND_NANOSECONDS_I32);
181    let normalized_nanoseconds = total_nanoseconds.rem_euclid(SECOND_NANOSECONDS_I32);
182
183    if seconds != 0 {
184        second_add(year, month, date, hour, minute, second, seconds)?;
185    }
186
187    *nanosecond = normalized_nanoseconds;
188
189    Some(())
190}
191
192#[cfg(any(feature = "chrono", feature = "jiff", feature = "time"))]
193fn add_date_time_parts(
194    from: &impl DateTimeParts,
195    date_time_diff: &impl DateTimeDiff,
196) -> Option<AddedDateTimeParts> {
197    let mut year = from.year().checked_add(date_time_diff.years())?;
198    let mut month = from.month() as i32 - 1;
199
200    month_add(&mut year, &mut month, date_time_diff.months())?;
201
202    let mut date = from.day() as i32;
203
204    let days_in_month = year_helper::get_days_in_month(year, (month + 1) as u8).unwrap() as i32;
205
206    if date > days_in_month {
207        date = days_in_month;
208    }
209
210    date_add(&mut year, &mut month, &mut date, date_time_diff.days())?;
211
212    let mut hour = from.hour() as i32;
213
214    hour_add(&mut year, &mut month, &mut date, &mut hour, date_time_diff.hours())?;
215
216    let mut minute = from.minute() as i32;
217
218    minute_add(&mut year, &mut month, &mut date, &mut hour, &mut minute, date_time_diff.minutes())?;
219
220    let mut second = from.second() as i32;
221
222    second_add(
223        &mut year,
224        &mut month,
225        &mut date,
226        &mut hour,
227        &mut minute,
228        &mut second,
229        date_time_diff.seconds(),
230    )?;
231
232    let mut nanosecond = from.nanosecond() as i32;
233
234    nanosecond_add(
235        &mut year,
236        &mut month,
237        &mut date,
238        &mut hour,
239        &mut minute,
240        &mut second,
241        &mut nanosecond,
242        date_time_diff.nanoseconds(),
243    )?;
244
245    Some(AddedDateTimeParts {
246        year,
247        month: (month + 1) as u8,
248        day: date as u8,
249        hour: hour as u8,
250        minute: minute as u8,
251        second: second as u8,
252        nanosecond: nanosecond as u32,
253    })
254}
255
256/// A trait for date-time types that can apply a `DateTimeDiff`.
257pub trait AddDateTimeDiff: DateTimeParts {
258    type Output;
259
260    fn add_date_time_diff(self, date_time_diff: &impl DateTimeDiff) -> Self::Output;
261}
262
263/// Calculate `from` + `date_time_diff`.
264///
265/// # Example
266///
267/// ```rust
268/// # #[cfg(feature = "chrono")]
269/// # {
270/// use chrono::prelude::*;
271/// use date_differencer::{DateDiffResult, add_date_time_diff};
272///
273/// let date = Local.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
274///
275/// let date_after_1_year_1_day = add_date_time_diff(date, &DateDiffResult {
276///     years: 1,
277///     days: 1,
278///     ..DateDiffResult::default()
279/// })
280/// .unwrap();
281///
282/// assert_eq!(
283///     Local.with_ymd_and_hms(2001, 1, 2, 0, 0, 0).unwrap(),
284///     date_after_1_year_1_day
285/// )
286/// # }
287/// ```
288#[inline]
289pub fn add_date_time_diff<DT: AddDateTimeDiff>(
290    from: DT,
291    date_time_diff: &impl DateTimeDiff,
292) -> DT::Output {
293    from.add_date_time_diff(date_time_diff)
294}