1#![deny(missing_docs)]
2
3use chrono::prelude::*;
25
26pub fn beginning_of_week(date: &NaiveDate) -> Option<NaiveDate> {
32 if date.weekday() == Weekday::Sun {
33 Some(date.clone())
34 } else {
35 NaiveDate::from_isoywd_opt(date.iso_week().year(), date.iso_week().week(), Weekday::Sun)
36 .map(|d| d - chrono::Duration::weeks(1))
37 }
38}
39
40pub fn end_of_week(date: &NaiveDate) -> Option<NaiveDate> {
44 beginning_of_week(date).map(|d| d + chrono::Duration::days(6))
45}
46
47pub fn next_week(date: &NaiveDate) -> Option<NaiveDate> {
51 beginning_of_week(date).map(|d| d + chrono::Duration::weeks(1))
52}
53
54pub fn previous_week(date: &NaiveDate) -> Option<NaiveDate> {
58 beginning_of_week(date).map(|d| d - chrono::Duration::weeks(1))
59}
60
61pub fn beginning_of_month(date: &NaiveDate) -> Option<NaiveDate> {
63 date.with_day(1)
64}
65
66pub fn end_of_month(date: &NaiveDate) -> Option<NaiveDate> {
68 next_month(date).map(|d| d - chrono::Duration::days(1))
69}
70
71pub fn next_month(date: &NaiveDate) -> Option<NaiveDate> {
75 if date.month() == 12 {
76 next_year(date)
77 } else {
78 beginning_of_month(date)?.with_month(date.month() + 1)
79 }
80}
81
82pub fn previous_month(date: &NaiveDate) -> Option<NaiveDate> {
86 if date.month() == 1 {
87 beginning_of_month(date)?
88 .with_month(12)?
89 .with_year(date.year() - 1)
90 } else {
91 beginning_of_month(date)?.with_month(date.month() - 1)
92 }
93}
94
95pub fn beginning_of_quarter(date: &NaiveDate) -> Option<NaiveDate> {
99 beginning_of_month(date)?.with_month(quarter_month(date))
100}
101
102pub fn end_of_quarter(date: &NaiveDate) -> Option<NaiveDate> {
106 next_quarter(date).map(|d| d - chrono::Duration::days(1))
107}
108
109pub fn next_quarter(date: &NaiveDate) -> Option<NaiveDate> {
114 if date.month() >= 10 {
115 beginning_of_year(date)?.with_year(date.year() + 1)
116 } else {
117 beginning_of_month(date)?.with_month(quarter_month(date) + 3)
118 }
119}
120
121pub fn previous_quarter(date: &NaiveDate) -> Option<NaiveDate> {
126 if date.month() < 4 {
127 beginning_of_month(date)?
128 .with_year(date.year() - 1)?
129 .with_month(10)
130 } else {
131 beginning_of_month(date)?.with_month(quarter_month(date) - 3)
132 }
133}
134
135fn quarter_month(date: &NaiveDate) -> u32 {
136 1 + 3 * ((date.month() - 1) / 3)
137}
138
139pub fn beginning_of_year(date: &NaiveDate) -> Option<NaiveDate> {
141 beginning_of_month(date)?.with_month(1)
142}
143
144pub fn end_of_year(date: &NaiveDate) -> Option<NaiveDate> {
146 date.with_month(12)?.with_day(31)
147}
148
149pub fn next_year(date: &NaiveDate) -> Option<NaiveDate> {
151 beginning_of_year(date)?.with_year(date.year() + 1)
152}
153
154pub fn previous_year(date: &NaiveDate) -> Option<NaiveDate> {
156 beginning_of_year(date)?.with_year(date.year() - 1)
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162 use num::clamp;
163 use quickcheck::{Arbitrary, Gen};
164 use quickcheck_macros::quickcheck;
165
166 #[derive(Clone, Debug)]
167 struct NaiveDateWrapper(NaiveDate);
168
169 #[quickcheck]
170 fn beginning_of_week_works(d: NaiveDateWrapper) -> bool {
171 let since = d.0.signed_duration_since(beginning_of_week(&d.0).unwrap());
172
173 beginning_of_week(&d.0).unwrap().weekday() == Weekday::Sun
174 && since.num_days() >= 0
175 && since.num_days() < 7
176 }
177
178 #[quickcheck]
179 fn end_of_week_works(d: NaiveDateWrapper) -> bool {
180 end_of_week(&d.0).unwrap().weekday() == Weekday::Sat
181 }
182
183 #[quickcheck]
184 fn next_week_works(d: NaiveDateWrapper) -> bool {
185 let since = next_week(&d.0).unwrap().signed_duration_since(d.0);
186 next_week(&d.0).unwrap().weekday() == Weekday::Sun
187 && since.num_days() > 0
188 && since.num_days() <= 7
189 }
190
191 #[quickcheck]
192 fn previous_week_works(d: NaiveDateWrapper) -> bool {
193 let since = previous_week(&d.0).unwrap().signed_duration_since(d.0);
194 previous_week(&d.0).unwrap().weekday() == Weekday::Sun
195 && since.num_days() <= -7
196 && since.num_days() > -14
197 }
198
199 #[quickcheck]
200 fn beginning_of_month_works(d: NaiveDateWrapper) -> bool {
201 beginning_of_month(&d.0).unwrap().day() == 1
202 && beginning_of_month(&d.0).unwrap().month() == d.0.month()
203 && beginning_of_month(&d.0).unwrap().year() == d.0.year()
204 }
205
206 #[quickcheck]
207 fn end_of_month_works(d: NaiveDateWrapper) -> bool {
208 end_of_month(&d.0).unwrap().month() == d.0.month()
209 && end_of_month(&d.0).unwrap().year() == d.0.year()
210 && (end_of_month(&d.0).unwrap() + chrono::Duration::days(1))
211 == next_month(&d.0).unwrap()
212 }
213
214 #[quickcheck]
215 fn beginning_of_year_works(d: NaiveDateWrapper) -> bool {
216 beginning_of_year(&d.0).unwrap().month() == 1
217 && beginning_of_year(&d.0).unwrap().day() == 1
218 && beginning_of_year(&d.0).unwrap().year() == d.0.year()
219 }
220
221 #[quickcheck]
222 fn end_of_year_works(d: NaiveDateWrapper) -> bool {
223 end_of_year(&d.0).unwrap().month() == 12
224 && end_of_year(&d.0).unwrap().day() == 31
225 && end_of_year(&d.0).unwrap().year() == d.0.year()
226 }
227
228 #[quickcheck]
229 fn next_year_works(d: NaiveDateWrapper) -> bool {
230 next_year(&d.0).unwrap().month() == 1
231 && next_year(&d.0).unwrap().day() == 1
232 && next_year(&d.0).unwrap().year() == d.0.year() + 1
233 }
234
235 #[quickcheck]
236 fn previous_year_works(d: NaiveDateWrapper) -> bool {
237 previous_year(&d.0).unwrap().month() == 1
238 && previous_year(&d.0).unwrap().day() == 1
239 && previous_year(&d.0).unwrap().year() == d.0.year() - 1
240 }
241
242 #[quickcheck]
243 fn beginning_of_quarter_works(d: NaiveDateWrapper) -> bool {
244 [1, 4, 7, 10].contains(&beginning_of_quarter(&d.0).unwrap().month())
245 && beginning_of_quarter(&d.0).unwrap().day() == 1
246 && beginning_of_quarter(&d.0).unwrap().year() == d.0.year()
247 }
248
249 #[quickcheck]
250 fn end_of_quarter_works(d: NaiveDateWrapper) -> bool {
251 [3, 6, 9, 12].contains(&end_of_quarter(&d.0).unwrap().month())
252 && end_of_quarter(&d.0)
253 .map(|x| x + chrono::Duration::days(1))
254 .unwrap()
255 == next_quarter(&d.0).unwrap()
256 && end_of_quarter(&d.0).unwrap().year() == d.0.year()
257 }
258
259 #[quickcheck]
260 fn next_quarter_works(d: NaiveDateWrapper) -> bool {
261 let current_month = d.0.month();
262 let year = if current_month >= 10 {
263 d.0.year() + 1
264 } else {
265 d.0.year()
266 };
267
268 [1, 4, 7, 10].contains(&next_quarter(&d.0).unwrap().month())
269 && next_quarter(&d.0).unwrap().day() == 1
270 && next_quarter(&d.0).unwrap().year() == year
271 }
272
273 #[quickcheck]
274 fn previous_quarter_works(d: NaiveDateWrapper) -> bool {
275 let current_month = d.0.month();
276 let year = if current_month <= 3 {
277 d.0.year() - 1
278 } else {
279 d.0.year()
280 };
281
282 [1, 4, 7, 10].contains(&previous_quarter(&d.0).unwrap().month())
283 && previous_quarter(&d.0).unwrap().day() == 1
284 && previous_quarter(&d.0).unwrap().year() == year
285 }
286
287 impl Arbitrary for NaiveDateWrapper {
288 fn arbitrary<G: Gen>(g: &mut G) -> NaiveDateWrapper {
289 let year = clamp(i32::arbitrary(g), 1584, 2800);
290 let month = 1 + u32::arbitrary(g) % 12;
291 let day = 1 + u32::arbitrary(g) % 31;
292
293 let first_date = NaiveDate::from_ymd_opt(year, month, day);
294 if day > 27 {
295 let result = vec![
296 first_date,
297 NaiveDate::from_ymd_opt(year, month, day - 1),
298 NaiveDate::from_ymd_opt(year, month, day - 2),
299 ]
300 .into_iter()
301 .filter_map(|v| v)
302 .nth(0)
303 .unwrap();
304
305 NaiveDateWrapper(result)
306 } else {
307 NaiveDateWrapper(first_date.unwrap())
308 }
309 }
310 }
311}