Skip to main content

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    let total_nanoseconds = nanosecond.checked_add(n)?;
155    let seconds = total_nanoseconds.div_euclid(SECOND_NANOSECONDS_I32);
156    let normalized_nanoseconds = total_nanoseconds.rem_euclid(SECOND_NANOSECONDS_I32);
157
158    if seconds != 0 {
159        second_add(year, month, date, hour, minute, second, seconds)?;
160    }
161
162    *nanosecond = normalized_nanoseconds;
163
164    Some(())
165}
166
167/// Calculate `from` + `date_time_diff`.
168///
169/// # Example
170///
171/// ```rust
172/// use chrono::prelude::*;
173/// use date_differencer::{add_date_time_diff, DateDiffResult};
174///
175/// let date = Local.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
176///
177/// let date_after_1_year_1_day = add_date_time_diff(date, &DateDiffResult {
178///     years: 1,
179///     days: 1,
180///     ..DateDiffResult::default()
181/// })
182/// .unwrap();
183///
184/// assert_eq!(
185///     Local.with_ymd_and_hms(2001, 1, 2, 0, 0, 0).unwrap(),
186///     date_after_1_year_1_day
187/// )
188/// ```
189pub fn add_date_time_diff<Tz>(
190    from: DateTime<Tz>,
191    date_time_diff: &impl DateTimeDiff,
192) -> LocalResult<DateTime<Tz>>
193where
194    Tz: TimeZone, {
195    let mut year = match from.year().checked_add(date_time_diff.years()) {
196        Some(v) => v,
197        None => return LocalResult::None,
198    };
199
200    let mut month = from.month0() as i32;
201
202    if month_add(&mut year, &mut month, date_time_diff.months()).is_none() {
203        return LocalResult::None;
204    }
205
206    let mut date = from.day() as i32;
207
208    let days_in_month = year_helper::get_days_in_month(year, (month + 1) as u8).unwrap() as i32;
209
210    if date > days_in_month {
211        date = days_in_month;
212    }
213
214    if date_add(&mut year, &mut month, &mut date, date_time_diff.days()).is_none() {
215        return LocalResult::None;
216    }
217
218    let mut hour = from.hour() as i32;
219
220    if hour_add(&mut year, &mut month, &mut date, &mut hour, date_time_diff.hours()).is_none() {
221        return LocalResult::None;
222    }
223
224    let mut minute = from.minute() as i32;
225
226    if minute_add(
227        &mut year,
228        &mut month,
229        &mut date,
230        &mut hour,
231        &mut minute,
232        date_time_diff.minutes(),
233    )
234    .is_none()
235    {
236        return LocalResult::None;
237    }
238
239    let mut second = from.second() as i32;
240
241    if second_add(
242        &mut year,
243        &mut month,
244        &mut date,
245        &mut hour,
246        &mut minute,
247        &mut second,
248        date_time_diff.seconds(),
249    )
250    .is_none()
251    {
252        return LocalResult::None;
253    }
254
255    let mut nanosecond = from.nanosecond() as i32;
256
257    if nanosecond_add(
258        &mut year,
259        &mut month,
260        &mut date,
261        &mut hour,
262        &mut minute,
263        &mut second,
264        &mut nanosecond,
265        date_time_diff.nanoseconds(),
266    )
267    .is_none()
268    {
269        return LocalResult::None;
270    }
271
272    match from.timezone().with_ymd_and_hms(
273        year,
274        month as u32 + 1,
275        date as u32,
276        hour as u32,
277        minute as u32,
278        second as u32,
279    ) {
280        LocalResult::Single(v) => {
281            match v.checked_add_signed(Duration::nanoseconds(nanosecond as i64)) {
282                Some(v) => LocalResult::Single(v),
283                None => LocalResult::None,
284            }
285        },
286        LocalResult::Ambiguous(a, b) => {
287            let delta = Duration::nanoseconds(nanosecond as i64);
288            LocalResult::Ambiguous(
289                match a.checked_add_signed(delta) {
290                    Some(v) => v,
291                    None => return LocalResult::None,
292                },
293                match b.checked_add_signed(delta) {
294                    Some(v) => v,
295                    None => return LocalResult::None,
296                },
297            )
298        },
299        LocalResult::None => LocalResult::None,
300    }
301}