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