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}