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
167pub 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}