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