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 #[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 => 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(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, 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}