chrono/naive/
mod.rs

1//! Date and time types unconcerned with timezones.
2//!
3//! They are primarily building blocks for other types
4//! (e.g. [`TimeZone`](../offset/trait.TimeZone.html)),
5//! but can be also used for the simpler date and time handling.
6
7use core::ops::RangeInclusive;
8
9use crate::expect;
10use crate::Weekday;
11
12pub(crate) mod date;
13pub(crate) mod datetime;
14mod internals;
15pub(crate) mod isoweek;
16pub(crate) mod time;
17
18pub use self::date::{NaiveDate, NaiveDateDaysIterator, NaiveDateWeeksIterator};
19#[allow(deprecated)]
20pub use self::date::{MAX_DATE, MIN_DATE};
21#[allow(deprecated)]
22pub use self::datetime::{NaiveDateTime, MAX_DATETIME, MIN_DATETIME};
23pub use self::isoweek::IsoWeek;
24pub use self::time::NaiveTime;
25
26#[cfg(feature = "__internal_bench")]
27#[doc(hidden)]
28pub use self::internals::YearFlags as __BenchYearFlags;
29
30/// A week represented by a [`NaiveDate`] and a [`Weekday`] which is the first
31/// day of the week.
32#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
33pub struct NaiveWeek {
34    date: NaiveDate,
35    start: Weekday,
36}
37
38impl NaiveWeek {
39    /// Create a new `NaiveWeek`
40    pub(crate) const fn new(date: NaiveDate, start: Weekday) -> Self {
41        Self { date, start }
42    }
43
44    /// Returns a date representing the first day of the week.
45    ///
46    /// # Panics
47    ///
48    /// Panics if the first day of the week happens to fall just out of range of `NaiveDate`
49    /// (more than ca. 262,000 years away from common era).
50    ///
51    /// # Examples
52    ///
53    /// ```
54    /// use chrono::{NaiveDate, Weekday};
55    ///
56    /// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap();
57    /// let week = date.week(Weekday::Mon);
58    /// assert!(week.first_day() <= date);
59    /// ```
60    #[inline]
61    #[must_use]
62    pub const fn first_day(&self) -> NaiveDate {
63        expect(self.checked_first_day(), "first weekday out of range for `NaiveDate`")
64    }
65
66    /// Returns a date representing the first day of the week or
67    /// `None` if the date is out of `NaiveDate`'s range
68    /// (more than ca. 262,000 years away from common era).
69    ///
70    /// # Examples
71    ///
72    /// ```
73    /// use chrono::{NaiveDate, Weekday};
74    ///
75    /// let date = NaiveDate::MIN;
76    /// let week = date.week(Weekday::Mon);
77    /// if let Some(first_day) = week.checked_first_day() {
78    ///     assert!(first_day == date);
79    /// } else {
80    ///     // error handling code
81    ///     return;
82    /// };
83    /// ```
84    #[inline]
85    #[must_use]
86    pub const fn checked_first_day(&self) -> Option<NaiveDate> {
87        let start = self.start.num_days_from_monday() as i32;
88        let ref_day = self.date.weekday().num_days_from_monday() as i32;
89        // Calculate the number of days to subtract from `self.date`.
90        // Do not construct an intermediate date beyond `self.date`, because that may be out of
91        // range if `date` is close to `NaiveDate::MAX`.
92        let days = start - ref_day - if start > ref_day { 7 } else { 0 };
93        self.date.add_days(days)
94    }
95
96    /// Returns a date representing the last day of the week.
97    ///
98    /// # Panics
99    ///
100    /// Panics if the last day of the week happens to fall just out of range of `NaiveDate`
101    /// (more than ca. 262,000 years away from common era).
102    ///
103    /// # Examples
104    ///
105    /// ```
106    /// use chrono::{NaiveDate, Weekday};
107    ///
108    /// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap();
109    /// let week = date.week(Weekday::Mon);
110    /// assert!(week.last_day() >= date);
111    /// ```
112    #[inline]
113    #[must_use]
114    pub const fn last_day(&self) -> NaiveDate {
115        expect(self.checked_last_day(), "last weekday out of range for `NaiveDate`")
116    }
117
118    /// Returns a date representing the last day of the week or
119    /// `None` if the date is out of `NaiveDate`'s range
120    /// (more than ca. 262,000 years away from common era).
121    ///
122    /// # Examples
123    ///
124    /// ```
125    /// use chrono::{NaiveDate, Weekday};
126    ///
127    /// let date = NaiveDate::MAX;
128    /// let week = date.week(Weekday::Mon);
129    /// if let Some(last_day) = week.checked_last_day() {
130    ///     assert!(last_day == date);
131    /// } else {
132    ///     // error handling code
133    ///     return;
134    /// };
135    /// ```
136    #[inline]
137    #[must_use]
138    pub const fn checked_last_day(&self) -> Option<NaiveDate> {
139        let end = self.start.pred().num_days_from_monday() as i32;
140        let ref_day = self.date.weekday().num_days_from_monday() as i32;
141        // Calculate the number of days to add to `self.date`.
142        // Do not construct an intermediate date before `self.date` (like with `first_day()`),
143        // because that may be out of range if `date` is close to `NaiveDate::MIN`.
144        let days = end - ref_day + if end < ref_day { 7 } else { 0 };
145        self.date.add_days(days)
146    }
147
148    /// Returns a [`RangeInclusive<T>`] representing the whole week bounded by
149    /// [first_day](NaiveWeek::first_day) and [last_day](NaiveWeek::last_day) functions.
150    ///
151    /// # Panics
152    ///
153    /// Panics if the either the first or last day of the week happens to fall just out of range of
154    /// `NaiveDate` (more than ca. 262,000 years away from common era).
155    ///
156    /// # Examples
157    ///
158    /// ```
159    /// use chrono::{NaiveDate, Weekday};
160    ///
161    /// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap();
162    /// let week = date.week(Weekday::Mon);
163    /// let days = week.days();
164    /// assert!(days.contains(&date));
165    /// ```
166    #[inline]
167    #[must_use]
168    pub const fn days(&self) -> RangeInclusive<NaiveDate> {
169        // `expect` doesn't work because `RangeInclusive` is not `Copy`
170        match self.checked_days() {
171            Some(val) => val,
172            None => panic!("{}", "first or last weekday is out of range for `NaiveDate`"),
173        }
174    }
175
176    /// Returns an [`Option<RangeInclusive<T>>`] representing the whole week bounded by
177    /// [checked_first_day](NaiveWeek::checked_first_day) and
178    /// [checked_last_day](NaiveWeek::checked_last_day) functions.
179    ///
180    /// Returns `None` if either of the boundaries are out of `NaiveDate`'s range
181    /// (more than ca. 262,000 years away from common era).
182    ///
183    ///
184    /// # Examples
185    ///
186    /// ```
187    /// use chrono::{NaiveDate, Weekday};
188    ///
189    /// let date = NaiveDate::MAX;
190    /// let week = date.week(Weekday::Mon);
191    /// let _days = match week.checked_days() {
192    ///     Some(d) => d,
193    ///     None => {
194    ///         // error handling code
195    ///         return;
196    ///     }
197    /// };
198    /// ```
199    #[inline]
200    #[must_use]
201    pub const fn checked_days(&self) -> Option<RangeInclusive<NaiveDate>> {
202        match (self.checked_first_day(), self.checked_last_day()) {
203            (Some(first), Some(last)) => Some(first..=last),
204            (_, _) => None,
205        }
206    }
207}
208
209/// A duration in calendar days.
210///
211/// This is useful because when using `TimeDelta` it is possible that adding `TimeDelta::days(1)`
212/// doesn't increment the day value as expected due to it being a fixed number of seconds. This
213/// difference applies only when dealing with `DateTime<TimeZone>` data types and in other cases
214/// `TimeDelta::days(n)` and `Days::new(n)` are equivalent.
215#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
216pub struct Days(pub(crate) u64);
217
218impl Days {
219    /// Construct a new `Days` from a number of days
220    pub const fn new(num: u64) -> Self {
221        Self(num)
222    }
223}
224
225/// Serialization/Deserialization of `NaiveDateTime` in alternate formats
226///
227/// The various modules in here are intended to be used with serde's [`with` annotation] to
228/// serialize as something other than the default ISO 8601 format.
229///
230/// [`with` annotation]: https://serde.rs/field-attrs.html#with
231#[cfg(feature = "serde")]
232pub mod serde {
233    pub use super::datetime::serde::*;
234}
235
236#[cfg(test)]
237mod test {
238    use crate::{NaiveDate, Weekday};
239    #[test]
240    fn test_naiveweek() {
241        let date = NaiveDate::from_ymd_opt(2022, 5, 18).unwrap();
242        let asserts = [
243            (Weekday::Mon, "Mon 2022-05-16", "Sun 2022-05-22"),
244            (Weekday::Tue, "Tue 2022-05-17", "Mon 2022-05-23"),
245            (Weekday::Wed, "Wed 2022-05-18", "Tue 2022-05-24"),
246            (Weekday::Thu, "Thu 2022-05-12", "Wed 2022-05-18"),
247            (Weekday::Fri, "Fri 2022-05-13", "Thu 2022-05-19"),
248            (Weekday::Sat, "Sat 2022-05-14", "Fri 2022-05-20"),
249            (Weekday::Sun, "Sun 2022-05-15", "Sat 2022-05-21"),
250        ];
251        for (start, first_day, last_day) in asserts {
252            let week = date.week(start);
253            let days = week.days();
254            assert_eq!(Ok(week.first_day()), NaiveDate::parse_from_str(first_day, "%a %Y-%m-%d"));
255            assert_eq!(Ok(week.last_day()), NaiveDate::parse_from_str(last_day, "%a %Y-%m-%d"));
256            assert!(days.contains(&date));
257        }
258    }
259
260    #[test]
261    fn test_naiveweek_min_max() {
262        let date_max = NaiveDate::MAX;
263        assert!(date_max.week(Weekday::Mon).first_day() <= date_max);
264        let date_min = NaiveDate::MIN;
265        assert!(date_min.week(Weekday::Mon).last_day() >= date_min);
266    }
267
268    #[test]
269    fn test_naiveweek_checked_no_panic() {
270        let date_max = NaiveDate::MAX;
271        if let Some(last) = date_max.week(Weekday::Mon).checked_last_day() {
272            assert!(last == date_max);
273        }
274        let date_min = NaiveDate::MIN;
275        if let Some(first) = date_min.week(Weekday::Mon).checked_first_day() {
276            assert!(first == date_min);
277        }
278        let _ = date_min.week(Weekday::Mon).checked_days();
279        let _ = date_max.week(Weekday::Mon).checked_days();
280    }
281}