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