/src/chrono/src/naive/mod.rs
Line | Count | Source (jump to first uncovered line) |
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 | | |
7 | | use core::hash::{Hash, Hasher}; |
8 | | use core::ops::RangeInclusive; |
9 | | |
10 | | use crate::Weekday; |
11 | | use crate::expect; |
12 | | |
13 | | pub(crate) mod date; |
14 | | pub(crate) mod datetime; |
15 | | mod internals; |
16 | | pub(crate) mod isoweek; |
17 | | pub(crate) mod time; |
18 | | |
19 | | #[allow(deprecated)] |
20 | | pub use self::date::{MAX_DATE, MIN_DATE}; |
21 | | pub use self::date::{NaiveDate, NaiveDateDaysIterator, NaiveDateWeeksIterator}; |
22 | | #[allow(deprecated)] |
23 | | pub use self::datetime::{MAX_DATETIME, MIN_DATETIME, NaiveDateTime}; |
24 | | pub use self::isoweek::IsoWeek; |
25 | | pub use self::time::NaiveTime; |
26 | | |
27 | | #[cfg(feature = "__internal_bench")] |
28 | | #[doc(hidden)] |
29 | | pub 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 | | pub struct NaiveWeek { |
35 | | date: NaiveDate, |
36 | | start: Weekday, |
37 | | } |
38 | | |
39 | | impl NaiveWeek { |
40 | | /// Create a new `NaiveWeek` |
41 | 0 | pub(crate) const fn new(date: NaiveDate, start: Weekday) -> Self { |
42 | 0 | Self { date, start } |
43 | 0 | } |
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 | 0 | pub const fn first_day(&self) -> NaiveDate { |
64 | 0 | expect(self.checked_first_day(), "first weekday out of range for `NaiveDate`") |
65 | 0 | } |
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 | 0 | pub const fn checked_first_day(&self) -> Option<NaiveDate> { |
88 | 0 | let start = self.start.num_days_from_monday() as i32; |
89 | 0 | 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 | 0 | let days = start - ref_day - if start > ref_day { 7 } else { 0 }; |
94 | 0 | self.date.add_days(days) |
95 | 0 | } |
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 | 0 | pub const fn last_day(&self) -> NaiveDate { |
116 | 0 | expect(self.checked_last_day(), "last weekday out of range for `NaiveDate`") |
117 | 0 | } |
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 | 0 | pub const fn checked_last_day(&self) -> Option<NaiveDate> { |
140 | 0 | let end = self.start.pred().num_days_from_monday() as i32; |
141 | 0 | 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 | 0 | let days = end - ref_day + if end < ref_day { 7 } else { 0 }; |
146 | 0 | self.date.add_days(days) |
147 | 0 | } |
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 | 0 | pub const fn days(&self) -> RangeInclusive<NaiveDate> { |
170 | 0 | // `expect` doesn't work because `RangeInclusive` is not `Copy` |
171 | 0 | match self.checked_days() { |
172 | 0 | Some(val) => val, |
173 | 0 | None => panic!("{}", "first or last weekday is out of range for `NaiveDate`"), |
174 | | } |
175 | 0 | } |
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 | 0 | pub const fn checked_days(&self) -> Option<RangeInclusive<NaiveDate>> { |
203 | 0 | match (self.checked_first_day(), self.checked_last_day()) { |
204 | 0 | (Some(first), Some(last)) => Some(first..=last), |
205 | 0 | (_, _) => None, |
206 | | } |
207 | 0 | } |
208 | | } |
209 | | |
210 | | impl PartialEq for NaiveWeek { |
211 | 0 | fn eq(&self, other: &Self) -> bool { |
212 | 0 | self.first_day() == other.first_day() |
213 | 0 | } |
214 | | } |
215 | | |
216 | | impl Hash for NaiveWeek { |
217 | 0 | fn hash<H: Hasher>(&self, state: &mut H) { |
218 | 0 | self.first_day().hash(state); |
219 | 0 | } |
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)] |
229 | | pub struct Days(pub(crate) u64); |
230 | | |
231 | | impl Days { |
232 | | /// Construct a new `Days` from a number of days |
233 | 0 | pub const fn new(num: u64) -> Self { |
234 | 0 | Self(num) |
235 | 0 | } |
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")] |
245 | | pub mod serde { |
246 | | pub use super::datetime::serde::*; |
247 | | } |
248 | | |
249 | | #[cfg(test)] |
250 | | mod 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 | | } |