1use chrono::{DateTime, Days, Months, NaiveDate, TimeZone};
27use std::cmp::Ordering;
28
29pub(crate) fn add_months_date(date: NaiveDate, months: i32) -> Option<NaiveDate> {
33 match months.cmp(&0) {
34 Ordering::Equal => Some(date),
35 Ordering::Greater => date.checked_add_months(Months::new(months as u32)),
36 Ordering::Less => date.checked_sub_months(Months::new(months.unsigned_abs())),
37 }
38}
39
40pub(crate) fn add_months_datetime<Tz: TimeZone>(
44 dt: DateTime<Tz>,
45 months: i32,
46) -> Option<DateTime<Tz>> {
47 match months.cmp(&0) {
48 Ordering::Equal => Some(dt),
49 Ordering::Greater => dt.checked_add_months(Months::new(months as u32)),
50 Ordering::Less => dt.checked_sub_months(Months::new(months.unsigned_abs())),
51 }
52}
53
54pub(crate) fn add_days_datetime<Tz: TimeZone>(dt: DateTime<Tz>, days: i32) -> Option<DateTime<Tz>> {
58 match days.cmp(&0) {
59 Ordering::Equal => Some(dt),
60 Ordering::Greater => dt.checked_add_days(Days::new(days as u64)),
61 Ordering::Less => dt.checked_sub_days(Days::new(days.unsigned_abs() as u64)),
62 }
63}
64
65pub(crate) fn sub_months_datetime<Tz: TimeZone>(
69 dt: DateTime<Tz>,
70 months: i32,
71) -> Option<DateTime<Tz>> {
72 match months.cmp(&0) {
73 Ordering::Equal => Some(dt),
74 Ordering::Greater => dt.checked_sub_months(Months::new(months as u32)),
75 Ordering::Less => dt.checked_add_months(Months::new(months.unsigned_abs())),
76 }
77}
78
79pub(crate) fn sub_days_datetime<Tz: TimeZone>(dt: DateTime<Tz>, days: i32) -> Option<DateTime<Tz>> {
83 match days.cmp(&0) {
84 Ordering::Equal => Some(dt),
85 Ordering::Greater => dt.checked_sub_days(Days::new(days as u64)),
86 Ordering::Less => dt.checked_add_days(Days::new(days.unsigned_abs() as u64)),
87 }
88}
89
90#[cfg(test)]
91mod tests {
92
93 use chrono::naive::NaiveDate;
94
95 use super::*;
96
97 #[test]
98 fn test_add_monts_months() {
99 let base = NaiveDate::from_ymd_opt(2020, 1, 31).unwrap();
100
101 assert_eq!(
102 add_months_date(base, 0),
103 NaiveDate::from_ymd_opt(2020, 1, 31)
104 );
105 assert_eq!(
106 add_months_date(base, 1),
107 NaiveDate::from_ymd_opt(2020, 2, 29)
108 );
109 assert_eq!(
110 add_months_date(base, 2),
111 NaiveDate::from_ymd_opt(2020, 3, 31)
112 );
113 assert_eq!(
114 add_months_date(base, 3),
115 NaiveDate::from_ymd_opt(2020, 4, 30)
116 );
117 assert_eq!(
118 add_months_date(base, 4),
119 NaiveDate::from_ymd_opt(2020, 5, 31)
120 );
121 assert_eq!(
122 add_months_date(base, 5),
123 NaiveDate::from_ymd_opt(2020, 6, 30)
124 );
125 assert_eq!(
126 add_months_date(base, 6),
127 NaiveDate::from_ymd_opt(2020, 7, 31)
128 );
129 assert_eq!(
130 add_months_date(base, 7),
131 NaiveDate::from_ymd_opt(2020, 8, 31)
132 );
133 assert_eq!(
134 add_months_date(base, 8),
135 NaiveDate::from_ymd_opt(2020, 9, 30)
136 );
137 assert_eq!(
138 add_months_date(base, 9),
139 NaiveDate::from_ymd_opt(2020, 10, 31)
140 );
141 assert_eq!(
142 add_months_date(base, 10),
143 NaiveDate::from_ymd_opt(2020, 11, 30)
144 );
145 assert_eq!(
146 add_months_date(base, 11),
147 NaiveDate::from_ymd_opt(2020, 12, 31)
148 );
149 assert_eq!(
150 add_months_date(base, 12),
151 NaiveDate::from_ymd_opt(2021, 1, 31)
152 );
153 assert_eq!(
154 add_months_date(base, 13),
155 NaiveDate::from_ymd_opt(2021, 2, 28)
156 );
157
158 assert_eq!(
159 add_months_date(base, -1),
160 NaiveDate::from_ymd_opt(2019, 12, 31)
161 );
162 assert_eq!(
163 add_months_date(base, -2),
164 NaiveDate::from_ymd_opt(2019, 11, 30)
165 );
166 assert_eq!(
167 add_months_date(base, -3),
168 NaiveDate::from_ymd_opt(2019, 10, 31)
169 );
170 assert_eq!(
171 add_months_date(base, -4),
172 NaiveDate::from_ymd_opt(2019, 9, 30)
173 );
174 assert_eq!(
175 add_months_date(base, -5),
176 NaiveDate::from_ymd_opt(2019, 8, 31)
177 );
178 assert_eq!(
179 add_months_date(base, -6),
180 NaiveDate::from_ymd_opt(2019, 7, 31)
181 );
182 assert_eq!(
183 add_months_date(base, -7),
184 NaiveDate::from_ymd_opt(2019, 6, 30)
185 );
186 assert_eq!(
187 add_months_date(base, -8),
188 NaiveDate::from_ymd_opt(2019, 5, 31)
189 );
190 assert_eq!(
191 add_months_date(base, -9),
192 NaiveDate::from_ymd_opt(2019, 4, 30)
193 );
194 assert_eq!(
195 add_months_date(base, -10),
196 NaiveDate::from_ymd_opt(2019, 3, 31)
197 );
198 assert_eq!(
199 add_months_date(base, -11),
200 NaiveDate::from_ymd_opt(2019, 2, 28)
201 );
202 assert_eq!(
203 add_months_date(base, -12),
204 NaiveDate::from_ymd_opt(2019, 1, 31)
205 );
206 assert_eq!(
207 add_months_date(base, -13),
208 NaiveDate::from_ymd_opt(2018, 12, 31)
209 );
210
211 assert_eq!(
212 add_months_date(base, 1265),
213 NaiveDate::from_ymd_opt(2125, 6, 30)
214 );
215
216 assert_eq!(add_months_date(base, i32::MAX), None);
218 assert_eq!(add_months_date(base, i32::MIN), None);
219 }
220
221 #[test]
222 fn test_add_months_date_with_overflow() {
223 let base = NaiveDate::from_ymd_opt(2020, 12, 31).unwrap();
224
225 assert_eq!(add_months_date(base, 0), Some(base));
226 assert_eq!(
227 add_months_date(base, 1),
228 NaiveDate::from_ymd_opt(2021, 1, 31)
229 );
230 assert_eq!(
231 add_months_date(base, 2),
232 NaiveDate::from_ymd_opt(2021, 2, 28)
233 );
234 assert_eq!(
235 add_months_date(base, 12),
236 NaiveDate::from_ymd_opt(2021, 12, 31)
237 );
238 assert_eq!(
239 add_months_date(base, 18),
240 NaiveDate::from_ymd_opt(2022, 6, 30)
241 );
242
243 assert_eq!(
244 add_months_date(base, -1),
245 NaiveDate::from_ymd_opt(2020, 11, 30)
246 );
247 assert_eq!(
248 add_months_date(base, -2),
249 NaiveDate::from_ymd_opt(2020, 10, 31)
250 );
251 assert_eq!(
252 add_months_date(base, -10),
253 NaiveDate::from_ymd_opt(2020, 2, 29)
254 );
255 assert_eq!(
256 add_months_date(base, -12),
257 NaiveDate::from_ymd_opt(2019, 12, 31)
258 );
259 assert_eq!(
260 add_months_date(base, -18),
261 NaiveDate::from_ymd_opt(2019, 6, 30)
262 );
263 }
264}