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::expect; |
10 | use crate::Weekday; |
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 | pub use self::date::{NaiveDate, NaiveDateDaysIterator, NaiveDateWeeksIterator}; |
19 | #[allow (deprecated)] |
20 | pub use self::date::{MAX_DATE, MIN_DATE}; |
21 | #[cfg (feature = "rustc-serialize" )] |
22 | #[allow (deprecated)] |
23 | pub use self::datetime::rustc_serialize::TsSeconds; |
24 | #[allow (deprecated)] |
25 | pub use self::datetime::{NaiveDateTime, MAX_DATETIME, MIN_DATETIME}; |
26 | pub use self::isoweek::IsoWeek; |
27 | pub use self::time::NaiveTime; |
28 | |
29 | #[cfg (feature = "__internal_bench" )] |
30 | #[doc (hidden)] |
31 | pub use self::internals::YearFlags as __BenchYearFlags; |
32 | |
33 | /// A week represented by a [`NaiveDate`] and a [`Weekday`] which is the first |
34 | /// day of the week. |
35 | #[derive (Debug)] |
36 | pub struct NaiveWeek { |
37 | date: NaiveDate, |
38 | start: Weekday, |
39 | } |
40 | |
41 | impl NaiveWeek { |
42 | /// Create a new `NaiveWeek` |
43 | pub(crate) const fn new(date: NaiveDate, start: Weekday) -> Self { |
44 | Self { date, start } |
45 | } |
46 | |
47 | /// Returns a date representing the first day of the week. |
48 | /// |
49 | /// # Panics |
50 | /// |
51 | /// Panics if the first day of the week happens to fall just out of range of `NaiveDate` |
52 | /// (more than ca. 262,000 years away from common era). |
53 | /// |
54 | /// # Examples |
55 | /// |
56 | /// ``` |
57 | /// use chrono::{NaiveDate, Weekday}; |
58 | /// |
59 | /// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap(); |
60 | /// let week = date.week(Weekday::Mon); |
61 | /// assert!(week.first_day() <= date); |
62 | /// ``` |
63 | #[inline ] |
64 | #[must_use ] |
65 | pub const fn first_day(&self) -> NaiveDate { |
66 | let start = self.start.num_days_from_monday() as i32; |
67 | let ref_day = self.date.weekday().num_days_from_monday() as i32; |
68 | // Calculate the number of days to subtract from `self.date`. |
69 | // Do not construct an intermediate date beyond `self.date`, because that may be out of |
70 | // range if `date` is close to `NaiveDate::MAX`. |
71 | let days = start - ref_day - if start > ref_day { 7 } else { 0 }; |
72 | expect!(self.date.add_days(days), "first weekday out of range for `NaiveDate`" ) |
73 | } |
74 | |
75 | /// Returns a date representing the last day of the week. |
76 | /// |
77 | /// # Panics |
78 | /// |
79 | /// Panics if the last day of the week happens to fall just out of range of `NaiveDate` |
80 | /// (more than ca. 262,000 years away from common era). |
81 | /// |
82 | /// # Examples |
83 | /// |
84 | /// ``` |
85 | /// use chrono::{NaiveDate, Weekday}; |
86 | /// |
87 | /// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap(); |
88 | /// let week = date.week(Weekday::Mon); |
89 | /// assert!(week.last_day() >= date); |
90 | /// ``` |
91 | #[inline ] |
92 | #[must_use ] |
93 | pub const fn last_day(&self) -> NaiveDate { |
94 | let end = self.start.pred().num_days_from_monday() as i32; |
95 | let ref_day = self.date.weekday().num_days_from_monday() as i32; |
96 | // Calculate the number of days to add to `self.date`. |
97 | // Do not construct an intermediate date before `self.date` (like with `first_day()`), |
98 | // because that may be out of range if `date` is close to `NaiveDate::MIN`. |
99 | let days = end - ref_day + if end < ref_day { 7 } else { 0 }; |
100 | expect!(self.date.add_days(days), "last weekday out of range for `NaiveDate`" ) |
101 | } |
102 | |
103 | /// Returns a [`RangeInclusive<T>`] representing the whole week bounded by |
104 | /// [first_day](NaiveWeek::first_day) and [last_day](NaiveWeek::last_day) functions. |
105 | /// |
106 | /// # Panics |
107 | /// |
108 | /// Panics if the either the first or last day of the week happens to fall just out of range of |
109 | /// `NaiveDate` (more than ca. 262,000 years away from common era). |
110 | /// |
111 | /// # Examples |
112 | /// |
113 | /// ``` |
114 | /// use chrono::{NaiveDate, Weekday}; |
115 | /// |
116 | /// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap(); |
117 | /// let week = date.week(Weekday::Mon); |
118 | /// let days = week.days(); |
119 | /// assert!(days.contains(&date)); |
120 | /// ``` |
121 | #[inline ] |
122 | #[must_use ] |
123 | pub const fn days(&self) -> RangeInclusive<NaiveDate> { |
124 | self.first_day()..=self.last_day() |
125 | } |
126 | } |
127 | |
128 | /// A duration in calendar days. |
129 | /// |
130 | /// This is useful because when using `TimeDelta` it is possible that adding `TimeDelta::days(1)` |
131 | /// doesn't increment the day value as expected due to it being a fixed number of seconds. This |
132 | /// difference applies only when dealing with `DateTime<TimeZone>` data types and in other cases |
133 | /// `TimeDelta::days(n)` and `Days::new(n)` are equivalent. |
134 | #[derive (Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] |
135 | pub struct Days(pub(crate) u64); |
136 | |
137 | impl Days { |
138 | /// Construct a new `Days` from a number of days |
139 | pub const fn new(num: u64) -> Self { |
140 | Self(num) |
141 | } |
142 | } |
143 | |
144 | /// Serialization/Deserialization of naive types in alternate formats |
145 | /// |
146 | /// The various modules in here are intended to be used with serde's [`with` |
147 | /// annotation][1] to serialize as something other than the default [RFC |
148 | /// 3339][2] format. |
149 | /// |
150 | /// [1]: https://serde.rs/attributes.html#field-attributes |
151 | /// [2]: https://tools.ietf.org/html/rfc3339 |
152 | #[cfg (feature = "serde" )] |
153 | pub mod serde { |
154 | pub use super::datetime::serde::*; |
155 | } |
156 | |
157 | #[cfg (test)] |
158 | mod test { |
159 | use crate::{NaiveDate, Weekday}; |
160 | #[test ] |
161 | fn test_naiveweek() { |
162 | let date = NaiveDate::from_ymd_opt(2022, 5, 18).unwrap(); |
163 | let asserts = [ |
164 | (Weekday::Mon, "Mon 2022-05-16" , "Sun 2022-05-22" ), |
165 | (Weekday::Tue, "Tue 2022-05-17" , "Mon 2022-05-23" ), |
166 | (Weekday::Wed, "Wed 2022-05-18" , "Tue 2022-05-24" ), |
167 | (Weekday::Thu, "Thu 2022-05-12" , "Wed 2022-05-18" ), |
168 | (Weekday::Fri, "Fri 2022-05-13" , "Thu 2022-05-19" ), |
169 | (Weekday::Sat, "Sat 2022-05-14" , "Fri 2022-05-20" ), |
170 | (Weekday::Sun, "Sun 2022-05-15" , "Sat 2022-05-21" ), |
171 | ]; |
172 | for (start, first_day, last_day) in asserts { |
173 | let week = date.week(start); |
174 | let days = week.days(); |
175 | assert_eq!(Ok(week.first_day()), NaiveDate::parse_from_str(first_day, "%a %Y-%m-%d" )); |
176 | assert_eq!(Ok(week.last_day()), NaiveDate::parse_from_str(last_day, "%a %Y-%m-%d" )); |
177 | assert!(days.contains(&date)); |
178 | } |
179 | } |
180 | |
181 | #[test ] |
182 | fn test_naiveweek_min_max() { |
183 | let date_max = NaiveDate::MAX; |
184 | assert!(date_max.week(Weekday::Mon).first_day() <= date_max); |
185 | let date_min = NaiveDate::MIN; |
186 | assert!(date_min.week(Weekday::Mon).last_day() >= date_min); |
187 | } |
188 | } |
189 | |