chrono_utilities/naive/mod.rs
1//! Utility structs and traits related to chrono's [NaiveDate](https://docs.rs/chrono/0.4.11/chrono/naive/struct.NaiveDate.html)
2use crate::oldtime::Duration as OldDuration;
3use chrono::{Datelike, NaiveDate};
4
5/// Value at index `i` is the minimum number of days in the month `i+1`
6static MONTH_MIN_DAYS: [u8; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
7
8/// Value at index `i` is the maximum number of days in the month `i+1`
9static MONTH_MAX_DAYS: [u8; 12] = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
10
11/// Common set of methods for transitioning dates into newer ones
12pub trait DateTransitions: Sized {
13 /// Returns true if leap year
14 fn is_leap_year(&self) -> bool;
15
16 /// Returns the last day of the month
17 fn last_day_of_month(&self) -> u32;
18
19 /// Returns the date as on the start of the current year
20 fn start_of_year(&self) -> Option<Self>;
21
22 /// Returns the date as on the end of the current year
23 fn end_of_year(&self) -> Option<Self>;
24
25 /// Returns the date as on the start of the current month
26 fn start_of_month(&self) -> Option<Self>;
27
28 /// Returns the date as on the end of the current month
29 fn end_of_month(&self) -> Option<Self>;
30
31 /// Returns the date as on the start of the current week
32 fn start_of_iso8601_week(&self) -> Option<Self>;
33
34 /// Returns the date as on the end of the current week
35 fn end_of_iso8601_week(&self) -> Option<Self>;
36
37 /// Returns the date as on the start of the previous year
38 fn start_of_pred_year(&self) -> Option<Self>;
39
40 /// Returns the date as on the end of the previous year
41 fn end_of_pred_year(&self) -> Option<Self>;
42
43 /// Returns the date as on the start of the previous month
44 fn start_of_pred_month(&self) -> Option<Self>;
45
46 /// Returns the date as on the end of the previous month
47 fn end_of_pred_month(&self) -> Option<Self>;
48
49 /// Returns the date as on the start of the previous week
50 fn start_of_pred_iso8601_week(&self) -> Option<Self>;
51
52 /// Returns the date as on the end of the previous week
53 fn end_of_pred_iso8601_week(&self) -> Option<Self>;
54
55 /// Returns the date as on the start of the succeeding year
56 fn start_of_succ_year(&self) -> Option<Self>;
57
58 /// Returns the date as on the end of the succeeding year
59 fn end_of_succ_year(&self) -> Option<Self>;
60
61 /// Returns the date as on the start of the succeeding month
62 fn start_of_succ_month(&self) -> Option<Self>;
63
64 /// Returns the date as on the end of the succeeding month
65 fn end_of_succ_month(&self) -> Option<Self>;
66
67 /// Returns the date as on the start of the succeeding week
68 fn start_of_succ_iso8601_week(&self) -> Option<Self>;
69
70 /// Returns the date as on the end of the succeeding week
71 fn end_of_succ_iso8601_week(&self) -> Option<Self>;
72}
73
74impl DateTransitions for NaiveDate {
75 /// Returns true if the date belongs to a year which is leap.
76 ///
77 /// # Example
78 ///
79 /// ~~~~
80 /// use chrono::NaiveDate;
81 /// use chrono_utilities::naive::DateTransitions;
82 ///
83 /// let d1 = NaiveDate::from_ymd(1996, 8, 14);
84 /// assert_eq!(d1.is_leap_year(), true);
85 /// let d2 = NaiveDate::from_ymd(1900, 2, 28);
86 /// assert_eq!(d2.is_leap_year(), false);
87 /// let d3 = NaiveDate::from_ymd(2000, 2, 29);
88 /// assert_eq!(d3.is_leap_year(), true);
89 /// let d4 = NaiveDate::from_ymd(1997, 11, 3);
90 /// assert_eq!(d4.is_leap_year(), false);
91 #[inline]
92 fn is_leap_year(&self) -> bool {
93 // TODO: Original chrono PR using private APIs
94 // self.of().flags().ndays() == 366
95
96 // See: https://github.com/chronotope/chrono/issues/29#issuecomment-84492746
97 NaiveDate::from_ymd_opt(self.year(), 2, 29).is_some()
98 }
99
100 /// Returns the last day of the month
101 ///
102 /// # Example
103 ///
104 /// ~~~~
105 /// use chrono::NaiveDate;
106 /// use chrono_utilities::naive::DateTransitions;
107 ///
108 /// let d1 = NaiveDate::from_ymd(1996, 2, 23);
109 /// assert_eq!(d1.last_day_of_month(), 29);
110 /// let d2 = NaiveDate::from_ymd(1993, 2, 1);
111 /// assert_eq!(d2.last_day_of_month(), 28);
112 /// let d3 = NaiveDate::from_ymd(2000, 1, 1);
113 /// assert_eq!(d3.last_day_of_month(), 31);
114 #[inline]
115 fn last_day_of_month(&self) -> u32 {
116 let index = (self.month() - 1) as usize;
117 if self.is_leap_year() {
118 MONTH_MAX_DAYS[index] as u32
119 } else {
120 MONTH_MIN_DAYS[index] as u32
121 }
122 }
123
124 /// Returns the year start date for the current date
125 ///
126 /// # Example
127 ///
128 /// ~~~~
129 /// use chrono::NaiveDate;
130 /// use chrono_utilities::naive::DateTransitions;
131 ///
132 /// let d = NaiveDate::from_ymd(2019, 3, 31);
133 /// assert_eq!(d.start_of_year().unwrap(), NaiveDate::from_ymd(2019, 1, 1));
134 #[inline]
135 fn start_of_year(&self) -> Option<Self> {
136 // TODO: Original chrono PR using private APIs
137 // self.with_mdf(Mdf::new(1, 1, self.of().flags()))
138 NaiveDate::from_ymd_opt(self.year(), 1, 1)
139 }
140
141 /// Returns the year end date for the current date
142 ///
143 /// # Example
144 ///
145 /// ~~~~
146 /// use chrono::NaiveDate;
147 /// use chrono_utilities::naive::DateTransitions;
148 ///
149 /// let d = NaiveDate::from_ymd(2019, 3, 31);
150 /// assert_eq!(d.end_of_year().unwrap(), NaiveDate::from_ymd(2019, 12, 31));
151 #[inline]
152 fn end_of_year(&self) -> Option<Self> {
153 // TODO: Original chrono PR using private APIs
154 // self.with_mdf(Mdf::new(12, 31, self.of().flags()))
155 NaiveDate::from_ymd_opt(self.year(), 12, 31)
156 }
157
158 /// Returns the start date of the current month
159 ///
160 /// # Example
161 ///
162 /// ~~~~
163 /// use chrono::NaiveDate;
164 /// use chrono_utilities::naive::DateTransitions;
165 ///
166 /// let d = NaiveDate::from_ymd(2019, 9, 13);
167 /// assert_eq!(d.start_of_month().unwrap(), NaiveDate::from_ymd(2019, 9, 1));
168 #[inline]
169 fn start_of_month(&self) -> Option<Self> {
170 // TODO: Original chrono PR using private APIs
171 // self.with_mdf(Mdf::new(self.month(), 1, self.of().flags()))
172 self.with_day(1)
173 }
174
175 /// Returns the date as on the end of the current month
176 ///
177 /// # Example
178 ///
179 /// ~~~~
180 /// use chrono::NaiveDate;
181 /// use chrono_utilities::naive::DateTransitions;
182 ///
183 /// let d1 = NaiveDate::from_ymd(1996, 2, 23);
184 /// assert_eq!(d1.end_of_month().unwrap(), NaiveDate::from_ymd(1996, 2, 29));
185 /// let d2 = NaiveDate::from_ymd(1993, 2, 1);
186 /// assert_eq!(d2.end_of_month().unwrap(), NaiveDate::from_ymd(1993, 2, 28));
187 /// let d3 = NaiveDate::from_ymd(2000, 1, 1);
188 /// assert_eq!(d3.end_of_month().unwrap(), NaiveDate::from_ymd(2000, 1, 31));
189 #[inline]
190 fn end_of_month(&self) -> Option<Self> {
191 self.with_day(self.last_day_of_month())
192 }
193
194 /// Returns the start of the week for the current date. Uses the ISO 8601 standard for
195 /// calculating the week. See [Wikipedia](https://en.wikipedia.org/wiki/ISO_week_date).
196 ///
197 /// # Example
198 ///
199 /// ~~~~
200 /// use chrono::NaiveDate;
201 /// use chrono_utilities::naive::DateTransitions;
202 ///
203 /// let d1 = NaiveDate::from_ymd(2020, 1, 2);
204 /// assert_eq!(d1.start_of_iso8601_week().unwrap(), NaiveDate::from_ymd(2019, 12, 30));
205 /// let d2 = NaiveDate::from_ymd(2019, 12, 29);
206 /// assert_eq!(d2.start_of_iso8601_week().unwrap(), NaiveDate::from_ymd(2019, 12, 23));
207 /// let d3 = NaiveDate::from_ymd(1992, 2, 29);
208 /// assert_eq!(d3.start_of_iso8601_week().unwrap(), NaiveDate::from_ymd(1992, 2, 24));
209 fn start_of_iso8601_week(&self) -> Option<Self> {
210 // TODO: Original chrono PR using private APIs
211 // let days = self.of().weekday().num_days_from_monday() as i64;
212 let days = self.weekday().num_days_from_monday() as i64;
213 self.checked_sub_signed(OldDuration::days(days))
214 }
215
216 /// Returns the end of the week for the current date. Uses the ISO 8601 standard for calculating
217 /// the week. See [Wikipedia](https://en.wikipedia.org/wiki/ISO_week_date).
218 ///
219 /// # Example
220 ///
221 /// ~~~~
222 /// use chrono::NaiveDate;
223 /// use chrono_utilities::naive::DateTransitions;
224 ///
225 /// let d1 = NaiveDate::from_ymd(2020, 1, 2);
226 /// assert_eq!(d1.end_of_iso8601_week().unwrap(), NaiveDate::from_ymd(2020, 1, 5));
227 /// let d2 = NaiveDate::from_ymd(2019, 12, 29);
228 /// assert_eq!(d2.end_of_iso8601_week().unwrap(), NaiveDate::from_ymd(2019, 12, 29));
229 /// let d3 = NaiveDate::from_ymd(1992, 2, 29);
230 /// assert_eq!(d3.end_of_iso8601_week().unwrap(), NaiveDate::from_ymd(1992, 3, 1));
231 fn end_of_iso8601_week(&self) -> Option<Self> {
232 // TODO: Original chrono PR using private APIs
233 // let days = 6 - self.of().weekday().num_days_from_monday() as i64;
234 let max_days = 6;
235 let days = max_days - self.weekday().num_days_from_monday() as i64;
236 self.checked_add_signed(OldDuration::days(days))
237 }
238
239 /// Returns the start of preceding year relative to the current date
240 ///
241 /// # Example
242 ///
243 /// ~~~~
244 /// use chrono::NaiveDate;
245 /// use chrono_utilities::naive::DateTransitions;
246 ///
247 /// let d = NaiveDate::from_ymd(2019, 3, 31);
248 /// assert_eq!(d.start_of_pred_year().unwrap(), NaiveDate::from_ymd(2018, 1, 1));
249 #[inline]
250 fn start_of_pred_year(&self) -> Option<Self> {
251 let prev_year = self.year() - 1;
252 // TODO: Original chrono PR using private APIs
253 // let flags = YearFlags::from_year(prev_year);
254 // NaiveDate::from_mdf(prev_year, Mdf::new(1, 1, flags))
255
256 NaiveDate::from_ymd_opt(prev_year, 1, 1)
257 }
258
259 /// Returns the end of preceding year relative to the current date
260 ///
261 /// # Example
262 ///
263 /// ~~~~
264 /// use chrono::NaiveDate;
265 /// use chrono_utilities::naive::DateTransitions;
266 ///
267 /// let d = NaiveDate::from_ymd(2019, 3, 31);
268 /// assert_eq!(d.end_of_pred_year().unwrap(), NaiveDate::from_ymd(2018, 12, 31));
269 #[inline]
270 fn end_of_pred_year(&self) -> Option<Self> {
271 let prev_year = self.year() - 1;
272 // TODO: Original chrono PR using private APIs
273 // let flags = YearFlags::from_year(prev_year);
274 // NaiveDate::from_mdf(prev_year, Mdf::new(12, 31, flags))
275
276 NaiveDate::from_ymd_opt(prev_year, 12, 31)
277 }
278
279 /// Returns the start of preceding month for the current date.
280 ///
281 /// # Example
282 ///
283 /// ~~~~
284 /// use chrono::NaiveDate;
285 /// use chrono_utilities::naive::DateTransitions;
286 ///
287 /// let d1 = NaiveDate::from_ymd(2019, 1, 4);
288 /// assert_eq!(d1.start_of_pred_month().unwrap(), NaiveDate::from_ymd(2018, 12, 1));
289 /// let d2 = NaiveDate::from_ymd(1999, 11, 17);
290 /// assert_eq!(d2.start_of_pred_month().unwrap(), NaiveDate::from_ymd(1999, 10, 1));
291 fn start_of_pred_month(&self) -> Option<Self> {
292 // TODO: Original chrono PR using private APIs
293 // let mut month = self.month() - 1;
294 // let mut flags = self.mdf().flags();
295 // let mut year = self.year();
296 // if month == 0 {
297 // month = 12;
298 // year -= 1;
299 // flags = YearFlags::from_year(year);
300 // };
301 // NaiveDate::from_mdf(year, Mdf::new(month, 1, flags))
302
303 let mut month = self.month() - 1;
304 let mut year = self.year();
305 if month == 0 {
306 month = 12;
307 year -= 1;
308 };
309 NaiveDate::from_ymd_opt(year, month, 1)
310 }
311
312 /// Returns the end of preceding month for the current date.
313 ///
314 /// # Example
315 ///
316 /// ~~~~
317 /// use chrono::NaiveDate;
318 /// use chrono_utilities::naive::DateTransitions;
319 ///
320 /// let d1 = NaiveDate::from_ymd(2019, 1, 4);
321 /// assert_eq!(d1.end_of_pred_month().unwrap(), NaiveDate::from_ymd(2018, 12, 31));
322 /// let d2 = NaiveDate::from_ymd(1999, 10, 17);
323 /// assert_eq!(d2.end_of_pred_month().unwrap(), NaiveDate::from_ymd(1999, 9, 30));
324 /// let d3 = NaiveDate::from_ymd(1996, 3, 1);
325 /// assert_eq!(d3.end_of_pred_month().unwrap(), NaiveDate::from_ymd(1996, 2, 29));
326 fn end_of_pred_month(&self) -> Option<Self> {
327 match self.start_of_pred_month() {
328 Some(pred_start) => pred_start.with_day(pred_start.last_day_of_month()),
329 None => None,
330 }
331 }
332
333 /// Returns the start of preceding week for the current date. Uses the ISO 8601 standard for
334 /// calculating the week. See [Wikipedia](https://en.wikipedia.org/wiki/ISO_week_date).
335 ///
336 /// # Example
337 ///
338 /// ~~~~
339 /// use chrono::NaiveDate;
340 /// use chrono_utilities::naive::DateTransitions;
341 ///
342 /// let d1 = NaiveDate::from_ymd(2019, 1, 4);
343 /// assert_eq!(d1.start_of_pred_iso8601_week().unwrap(), NaiveDate::from_ymd(2018, 12, 24));
344 /// let d2 = NaiveDate::from_ymd(1999, 10, 17);
345 /// assert_eq!(d2.start_of_pred_iso8601_week().unwrap(), NaiveDate::from_ymd(1999, 10, 4));
346 /// let d3 = NaiveDate::from_ymd(1996, 3, 1);
347 /// assert_eq!(d3.start_of_pred_iso8601_week().unwrap(), NaiveDate::from_ymd(1996, 2, 19));
348 fn start_of_pred_iso8601_week(&self) -> Option<Self> {
349 match self.start_of_iso8601_week() {
350 Some(week_start) => Some(week_start - OldDuration::days(7)),
351 None => None,
352 }
353 }
354
355 /// Returns the end of preceding week for the current date. Uses the ISO 8601 standard for
356 /// calculating the week. See [Wikipedia](https://en.wikipedia.org/wiki/ISO_week_date).
357 ///
358 /// # Example
359 ///
360 /// ~~~~
361 /// use chrono::NaiveDate;
362 /// use chrono_utilities::naive::DateTransitions;
363 ///
364 /// let d1 = NaiveDate::from_ymd(2019, 1, 4);
365 /// assert_eq!(d1.end_of_pred_iso8601_week().unwrap(), NaiveDate::from_ymd(2018, 12, 30));
366 /// let d2 = NaiveDate::from_ymd(1999, 10, 17);
367 /// assert_eq!(d2.end_of_pred_iso8601_week().unwrap(), NaiveDate::from_ymd(1999, 10, 10));
368 /// let d3 = NaiveDate::from_ymd(1996, 3, 1);
369 /// assert_eq!(d3.end_of_pred_iso8601_week().unwrap(), NaiveDate::from_ymd(1996, 2, 25));
370 fn end_of_pred_iso8601_week(&self) -> Option<Self> {
371 match self.start_of_iso8601_week() {
372 Some(week_start) => Some(week_start - OldDuration::days(1)),
373 None => None,
374 }
375 }
376
377 /// Returns the start of succeeding year relative to the current date
378 ///
379 /// # Example
380 ///
381 /// ~~~~
382 /// use chrono::NaiveDate;
383 /// use chrono_utilities::naive::DateTransitions;
384 ///
385 /// let d = NaiveDate::from_ymd(2019, 3, 31);
386 /// assert_eq!(d.start_of_succ_year().unwrap(), NaiveDate::from_ymd(2020, 1, 1));
387 #[inline]
388 fn start_of_succ_year(&self) -> Option<Self> {
389 let nxt_year = self.year() + 1;
390 // TODO: Original chrono PR using private APIs
391 // let flags = YearFlags::from_year(nxt_year);
392 // NaiveDate::from_mdf(nxt_year, Mdf::new(1, 1, flags))
393 NaiveDate::from_ymd_opt(nxt_year, 1, 1)
394 }
395
396 /// Returns the end of succeeding year relative to the current date
397 ///
398 /// # Example
399 ///
400 /// ~~~~
401 /// use chrono::NaiveDate;
402 /// use chrono_utilities::naive::DateTransitions;
403 ///
404 /// let d = NaiveDate::from_ymd(2019, 3, 31);
405 /// assert_eq!(d.end_of_succ_year().unwrap(), NaiveDate::from_ymd(2020, 12, 31));
406 #[inline]
407 fn end_of_succ_year(&self) -> Option<Self> {
408 let nxt_year = self.year() + 1;
409 // TODO: Original chrono PR using private APIs
410 // let flags = YearFlags::from_year(nxt_year);
411 // NaiveDate::from_mdf(prev_year, Mdf::new(12, 31, flags))
412 NaiveDate::from_ymd_opt(nxt_year, 12, 31)
413 }
414
415 /// Returns the start of succeeding month for the current date.
416 ///
417 /// # Example
418 ///
419 /// ~~~~
420 /// use chrono::NaiveDate;
421 /// use chrono_utilities::naive::DateTransitions;
422 ///
423 /// let d1 = NaiveDate::from_ymd(2019, 12, 12);
424 /// assert_eq!(d1.start_of_succ_month().unwrap(), NaiveDate::from_ymd(2020, 1, 1));
425 /// let d2 = NaiveDate::from_ymd(1999, 2, 28);
426 /// assert_eq!(d2.start_of_succ_month().unwrap(), NaiveDate::from_ymd(1999, 3, 1));
427 fn start_of_succ_month(&self) -> Option<Self> {
428 // TODO: Original chrono PR using private APIs
429 // let mut month = self.month() + 1;
430 // let mut flags = self.mdf().flags();
431 // let mut year = self.year();
432 // if month == 13 {
433 // month = 1;
434 // year += 1;
435 // flags = YearFlags::from_year(year);
436 // };
437 // NaiveDate::from_mdf(year, Mdf::new(month, 1, flags))
438
439 let mut month = self.month() + 1;
440 let mut year = self.year();
441 if month == 13 {
442 month = 1;
443 year += 1;
444 };
445 NaiveDate::from_ymd_opt(year, month, 1)
446 }
447
448 /// Returns the end of succeeding month for the current date.
449 ///
450 /// # Example
451 ///
452 /// ~~~~
453 /// use chrono::NaiveDate;
454 /// use chrono_utilities::naive::DateTransitions;
455 ///
456 /// let d1 = NaiveDate::from_ymd(2019, 1, 4);
457 /// assert_eq!(d1.end_of_pred_month().unwrap(), NaiveDate::from_ymd(2018, 12, 31));
458 /// let d2 = NaiveDate::from_ymd(1999, 10, 17);
459 /// assert_eq!(d2.end_of_pred_month().unwrap(), NaiveDate::from_ymd(1999, 9, 30));
460 /// let d3 = NaiveDate::from_ymd(1996, 3, 1);
461 /// assert_eq!(d3.end_of_pred_month().unwrap(), NaiveDate::from_ymd(1996, 2, 29));
462 fn end_of_succ_month(&self) -> Option<Self> {
463 match self.start_of_succ_month() {
464 Some(succ_start) => succ_start.with_day(succ_start.last_day_of_month()),
465 None => None,
466 }
467 }
468
469 /// Returns the start of succeeding week for the current date. Uses the ISO 8601 standard for
470 /// calculating the week. See [Wikipedia](https://en.wikipedia.org/wiki/ISO_week_date).
471 ///
472 /// # Example
473 ///
474 /// ~~~~
475 /// use chrono::NaiveDate;
476 /// use chrono_utilities::naive::DateTransitions;
477 ///
478 /// let d1 = NaiveDate::from_ymd(2020, 1, 4);
479 /// assert_eq!(d1.start_of_succ_iso8601_week().unwrap(), NaiveDate::from_ymd(2020, 1, 6));
480 /// let d2 = NaiveDate::from_ymd(2017, 12, 28);
481 /// assert_eq!(d2.start_of_succ_iso8601_week().unwrap(), NaiveDate::from_ymd(2018, 1, 1));
482 /// let d3 = NaiveDate::from_ymd(1996, 2, 26);
483 /// assert_eq!(d3.start_of_succ_iso8601_week().unwrap(), NaiveDate::from_ymd(1996, 3, 4));
484 fn start_of_succ_iso8601_week(&self) -> Option<Self> {
485 match self.start_of_iso8601_week() {
486 Some(week_start) => Some(week_start + OldDuration::days(7)),
487 None => None,
488 }
489 }
490
491 /// Returns the end of succeeding week for the current date. Uses the ISO 8601 standard for
492 /// calculating the week. See [Wikipedia](https://en.wikipedia.org/wiki/ISO_week_date).
493 ///
494 /// # Example
495 ///
496 /// ~~~~
497 /// use chrono::NaiveDate;
498 /// use chrono_utilities::naive::DateTransitions;
499 ///
500 /// let d1 = NaiveDate::from_ymd(2019, 1, 4);
501 /// assert_eq!(d1.end_of_succ_iso8601_week().unwrap(), NaiveDate::from_ymd(2019, 1, 13));
502 /// let d2 = NaiveDate::from_ymd(2004, 2, 20);
503 /// assert_eq!(d2.end_of_succ_iso8601_week().unwrap(), NaiveDate::from_ymd(2004, 2, 29));
504 /// let d3 = NaiveDate::from_ymd(2005, 12, 20);
505 /// assert_eq!(d3.end_of_succ_iso8601_week().unwrap(), NaiveDate::from_ymd(2006, 1, 1));
506 fn end_of_succ_iso8601_week(&self) -> Option<Self> {
507 match self.start_of_succ_iso8601_week() {
508 Some(week_start) => Some(week_start + OldDuration::days(6)),
509 None => None,
510 }
511 }
512}