date_differencer/
add_diff.rs

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