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