Skip to main content

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