| 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 | #[allow (deprecated)] |
| 22 | pub use self::datetime::{NaiveDateTime, MAX_DATETIME, MIN_DATETIME}; |
| 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 | |