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