1 | //! The [`Date`] struct and its associated `impl`s. |
2 | |
3 | use core::fmt; |
4 | use core::ops::{Add, Sub}; |
5 | use core::time::Duration as StdDuration; |
6 | #[cfg (feature = "formatting" )] |
7 | use std::io; |
8 | |
9 | use crate::convert::*; |
10 | #[cfg (feature = "formatting" )] |
11 | use crate::formatting::Formattable; |
12 | #[cfg (feature = "parsing" )] |
13 | use crate::parsing::Parsable; |
14 | use crate::util::{days_in_year, days_in_year_month, is_leap_year, weeks_in_year}; |
15 | use crate::{error, Duration, Month, PrimitiveDateTime, Time, Weekday}; |
16 | |
17 | /// The minimum valid year. |
18 | pub(crate) const MIN_YEAR: i32 = if cfg!(feature = "large-dates" ) { |
19 | -999_999 |
20 | } else { |
21 | -9999 |
22 | }; |
23 | /// The maximum valid year. |
24 | pub(crate) const MAX_YEAR: i32 = if cfg!(feature = "large-dates" ) { |
25 | 999_999 |
26 | } else { |
27 | 9999 |
28 | }; |
29 | |
30 | /// Date in the proleptic Gregorian calendar. |
31 | /// |
32 | /// By default, years between ±9999 inclusive are representable. This can be expanded to ±999,999 |
33 | /// inclusive by enabling the `large-dates` crate feature. Doing so has performance implications |
34 | /// and introduces some ambiguities when parsing. |
35 | #[derive (Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] |
36 | pub struct Date { |
37 | /// Bitpacked field containing both the year and ordinal. |
38 | // | xx | xxxxxxxxxxxxxxxxxxxxx | xxxxxxxxx | |
39 | // | 2 bits | 21 bits | 9 bits | |
40 | // | unassigned | year | ordinal | |
41 | // The year is 15 bits when `large-dates` is not enabled. |
42 | value: i32, |
43 | } |
44 | |
45 | impl Date { |
46 | /// The minimum valid `Date`. |
47 | /// |
48 | /// The value of this may vary depending on the feature flags enabled. |
49 | pub const MIN: Self = Self::__from_ordinal_date_unchecked(MIN_YEAR, 1); |
50 | |
51 | /// The maximum valid `Date`. |
52 | /// |
53 | /// The value of this may vary depending on the feature flags enabled. |
54 | pub const MAX: Self = Self::__from_ordinal_date_unchecked(MAX_YEAR, days_in_year(MAX_YEAR)); |
55 | |
56 | // region: constructors |
57 | /// Construct a `Date` from the year and ordinal values, the validity of which must be |
58 | /// guaranteed by the caller. |
59 | #[doc (hidden)] |
60 | pub const fn __from_ordinal_date_unchecked(year: i32, ordinal: u16) -> Self { |
61 | debug_assert!(year >= MIN_YEAR); |
62 | debug_assert!(year <= MAX_YEAR); |
63 | debug_assert!(ordinal != 0); |
64 | debug_assert!(ordinal <= days_in_year(year)); |
65 | |
66 | Self { |
67 | value: (year << 9) | ordinal as i32, |
68 | } |
69 | } |
70 | |
71 | /// Attempt to create a `Date` from the year, month, and day. |
72 | /// |
73 | /// ```rust |
74 | /// # use time::{Date, Month}; |
75 | /// assert!(Date::from_calendar_date(2019, Month::January, 1).is_ok()); |
76 | /// assert!(Date::from_calendar_date(2019, Month::December, 31).is_ok()); |
77 | /// ``` |
78 | /// |
79 | /// ```rust |
80 | /// # use time::{Date, Month}; |
81 | /// assert!(Date::from_calendar_date(2019, Month::February, 29).is_err()); // 2019 isn't a leap year. |
82 | /// ``` |
83 | pub const fn from_calendar_date( |
84 | year: i32, |
85 | month: Month, |
86 | day: u8, |
87 | ) -> Result<Self, error::ComponentRange> { |
88 | /// Cumulative days through the beginning of a month in both common and leap years. |
89 | const DAYS_CUMULATIVE_COMMON_LEAP: [[u16; 12]; 2] = [ |
90 | [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334], |
91 | [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335], |
92 | ]; |
93 | |
94 | ensure_value_in_range!(year in MIN_YEAR => MAX_YEAR); |
95 | ensure_value_in_range!(day conditionally in 1 => days_in_year_month(year, month)); |
96 | |
97 | Ok(Self::__from_ordinal_date_unchecked( |
98 | year, |
99 | DAYS_CUMULATIVE_COMMON_LEAP[is_leap_year(year) as usize][month as usize - 1] |
100 | + day as u16, |
101 | )) |
102 | } |
103 | |
104 | /// Attempt to create a `Date` from the year and ordinal day number. |
105 | /// |
106 | /// ```rust |
107 | /// # use time::Date; |
108 | /// assert!(Date::from_ordinal_date(2019, 1).is_ok()); |
109 | /// assert!(Date::from_ordinal_date(2019, 365).is_ok()); |
110 | /// ``` |
111 | /// |
112 | /// ```rust |
113 | /// # use time::Date; |
114 | /// assert!(Date::from_ordinal_date(2019, 366).is_err()); // 2019 isn't a leap year. |
115 | /// ``` |
116 | pub const fn from_ordinal_date(year: i32, ordinal: u16) -> Result<Self, error::ComponentRange> { |
117 | ensure_value_in_range!(year in MIN_YEAR => MAX_YEAR); |
118 | ensure_value_in_range!(ordinal conditionally in 1 => days_in_year(year)); |
119 | Ok(Self::__from_ordinal_date_unchecked(year, ordinal)) |
120 | } |
121 | |
122 | /// Attempt to create a `Date` from the ISO year, week, and weekday. |
123 | /// |
124 | /// ```rust |
125 | /// # use time::{Date, Weekday::*}; |
126 | /// assert!(Date::from_iso_week_date(2019, 1, Monday).is_ok()); |
127 | /// assert!(Date::from_iso_week_date(2019, 1, Tuesday).is_ok()); |
128 | /// assert!(Date::from_iso_week_date(2020, 53, Friday).is_ok()); |
129 | /// ``` |
130 | /// |
131 | /// ```rust |
132 | /// # use time::{Date, Weekday::*}; |
133 | /// assert!(Date::from_iso_week_date(2019, 53, Monday).is_err()); // 2019 doesn't have 53 weeks. |
134 | /// ``` |
135 | pub const fn from_iso_week_date( |
136 | year: i32, |
137 | week: u8, |
138 | weekday: Weekday, |
139 | ) -> Result<Self, error::ComponentRange> { |
140 | ensure_value_in_range!(year in MIN_YEAR => MAX_YEAR); |
141 | ensure_value_in_range!(week conditionally in 1 => weeks_in_year(year)); |
142 | |
143 | let adj_year = year - 1; |
144 | let raw = 365 * adj_year + div_floor!(adj_year, 4) - div_floor!(adj_year, 100) |
145 | + div_floor!(adj_year, 400); |
146 | let jan_4 = match (raw % 7) as i8 { |
147 | -6 | 1 => 8, |
148 | -5 | 2 => 9, |
149 | -4 | 3 => 10, |
150 | -3 | 4 => 4, |
151 | -2 | 5 => 5, |
152 | -1 | 6 => 6, |
153 | _ => 7, |
154 | }; |
155 | let ordinal = week as i16 * 7 + weekday.number_from_monday() as i16 - jan_4; |
156 | |
157 | Ok(if ordinal <= 0 { |
158 | Self::__from_ordinal_date_unchecked( |
159 | year - 1, |
160 | (ordinal as u16).wrapping_add(days_in_year(year - 1)), |
161 | ) |
162 | } else if ordinal > days_in_year(year) as i16 { |
163 | Self::__from_ordinal_date_unchecked(year + 1, ordinal as u16 - days_in_year(year)) |
164 | } else { |
165 | Self::__from_ordinal_date_unchecked(year, ordinal as _) |
166 | }) |
167 | } |
168 | |
169 | /// Create a `Date` from the Julian day. |
170 | /// |
171 | /// The algorithm to perform this conversion is derived from one provided by Peter Baum; it is |
172 | /// freely available [here](https://www.researchgate.net/publication/316558298_Date_Algorithms). |
173 | /// |
174 | /// ```rust |
175 | /// # use time::Date; |
176 | /// # use time_macros::date; |
177 | /// assert_eq!(Date::from_julian_day(0), Ok(date!(-4713 - 11 - 24))); |
178 | /// assert_eq!(Date::from_julian_day(2_451_545), Ok(date!(2000 - 01 - 01))); |
179 | /// assert_eq!(Date::from_julian_day(2_458_485), Ok(date!(2019 - 01 - 01))); |
180 | /// assert_eq!(Date::from_julian_day(2_458_849), Ok(date!(2019 - 12 - 31))); |
181 | /// ``` |
182 | #[doc (alias = "from_julian_date" )] |
183 | pub const fn from_julian_day(julian_day: i32) -> Result<Self, error::ComponentRange> { |
184 | ensure_value_in_range!( |
185 | julian_day in Self::MIN.to_julian_day() => Self::MAX.to_julian_day() |
186 | ); |
187 | Ok(Self::from_julian_day_unchecked(julian_day)) |
188 | } |
189 | |
190 | /// Create a `Date` from the Julian day. |
191 | /// |
192 | /// This does not check the validity of the provided Julian day, and as such may result in an |
193 | /// internally invalid value. |
194 | #[doc (alias = "from_julian_date_unchecked" )] |
195 | pub(crate) const fn from_julian_day_unchecked(julian_day: i32) -> Self { |
196 | debug_assert!(julian_day >= Self::MIN.to_julian_day()); |
197 | debug_assert!(julian_day <= Self::MAX.to_julian_day()); |
198 | |
199 | // To avoid a potential overflow, the value may need to be widened for some arithmetic. |
200 | |
201 | let z = julian_day - 1_721_119; |
202 | let (mut year, mut ordinal) = if julian_day < -19_752_948 || julian_day > 23_195_514 { |
203 | let g = 100 * z as i64 - 25; |
204 | let a = (g / 3_652_425) as i32; |
205 | let b = a - a / 4; |
206 | let year = div_floor!(100 * b as i64 + g, 36525) as i32; |
207 | let ordinal = (b + z - div_floor!(36525 * year as i64, 100) as i32) as _; |
208 | (year, ordinal) |
209 | } else { |
210 | let g = 100 * z - 25; |
211 | let a = g / 3_652_425; |
212 | let b = a - a / 4; |
213 | let year = div_floor!(100 * b + g, 36525); |
214 | let ordinal = (b + z - div_floor!(36525 * year, 100)) as _; |
215 | (year, ordinal) |
216 | }; |
217 | |
218 | if is_leap_year(year) { |
219 | ordinal += 60; |
220 | cascade!(ordinal in 1..367 => year); |
221 | } else { |
222 | ordinal += 59; |
223 | cascade!(ordinal in 1..366 => year); |
224 | } |
225 | |
226 | Self::__from_ordinal_date_unchecked(year, ordinal) |
227 | } |
228 | // endregion constructors |
229 | |
230 | // region: getters |
231 | /// Get the year of the date. |
232 | /// |
233 | /// ```rust |
234 | /// # use time_macros::date; |
235 | /// assert_eq!(date!(2019 - 01 - 01).year(), 2019); |
236 | /// assert_eq!(date!(2019 - 12 - 31).year(), 2019); |
237 | /// assert_eq!(date!(2020 - 01 - 01).year(), 2020); |
238 | /// ``` |
239 | pub const fn year(self) -> i32 { |
240 | self.value >> 9 |
241 | } |
242 | |
243 | /// Get the month. |
244 | /// |
245 | /// ```rust |
246 | /// # use time::Month; |
247 | /// # use time_macros::date; |
248 | /// assert_eq!(date!(2019 - 01 - 01).month(), Month::January); |
249 | /// assert_eq!(date!(2019 - 12 - 31).month(), Month::December); |
250 | /// ``` |
251 | pub const fn month(self) -> Month { |
252 | self.month_day().0 |
253 | } |
254 | |
255 | /// Get the day of the month. |
256 | /// |
257 | /// The returned value will always be in the range `1..=31`. |
258 | /// |
259 | /// ```rust |
260 | /// # use time_macros::date; |
261 | /// assert_eq!(date!(2019 - 01 - 01).day(), 1); |
262 | /// assert_eq!(date!(2019 - 12 - 31).day(), 31); |
263 | /// ``` |
264 | pub const fn day(self) -> u8 { |
265 | self.month_day().1 |
266 | } |
267 | |
268 | /// Get the month and day. This is more efficient than fetching the components individually. |
269 | // For whatever reason, rustc has difficulty optimizing this function. It's significantly faster |
270 | // to write the statements out by hand. |
271 | pub(crate) const fn month_day(self) -> (Month, u8) { |
272 | /// The number of days up to and including the given month. Common years |
273 | /// are first, followed by leap years. |
274 | const CUMULATIVE_DAYS_IN_MONTH_COMMON_LEAP: [[u16; 11]; 2] = [ |
275 | [31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334], |
276 | [31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335], |
277 | ]; |
278 | |
279 | let days = CUMULATIVE_DAYS_IN_MONTH_COMMON_LEAP[is_leap_year(self.year()) as usize]; |
280 | let ordinal = self.ordinal(); |
281 | |
282 | if ordinal > days[10] { |
283 | (Month::December, (ordinal - days[10]) as _) |
284 | } else if ordinal > days[9] { |
285 | (Month::November, (ordinal - days[9]) as _) |
286 | } else if ordinal > days[8] { |
287 | (Month::October, (ordinal - days[8]) as _) |
288 | } else if ordinal > days[7] { |
289 | (Month::September, (ordinal - days[7]) as _) |
290 | } else if ordinal > days[6] { |
291 | (Month::August, (ordinal - days[6]) as _) |
292 | } else if ordinal > days[5] { |
293 | (Month::July, (ordinal - days[5]) as _) |
294 | } else if ordinal > days[4] { |
295 | (Month::June, (ordinal - days[4]) as _) |
296 | } else if ordinal > days[3] { |
297 | (Month::May, (ordinal - days[3]) as _) |
298 | } else if ordinal > days[2] { |
299 | (Month::April, (ordinal - days[2]) as _) |
300 | } else if ordinal > days[1] { |
301 | (Month::March, (ordinal - days[1]) as _) |
302 | } else if ordinal > days[0] { |
303 | (Month::February, (ordinal - days[0]) as _) |
304 | } else { |
305 | (Month::January, ordinal as _) |
306 | } |
307 | } |
308 | |
309 | /// Get the day of the year. |
310 | /// |
311 | /// The returned value will always be in the range `1..=366` (`1..=365` for common years). |
312 | /// |
313 | /// ```rust |
314 | /// # use time_macros::date; |
315 | /// assert_eq!(date!(2019 - 01 - 01).ordinal(), 1); |
316 | /// assert_eq!(date!(2019 - 12 - 31).ordinal(), 365); |
317 | /// ``` |
318 | pub const fn ordinal(self) -> u16 { |
319 | (self.value & 0x1FF) as _ |
320 | } |
321 | |
322 | /// Get the ISO 8601 year and week number. |
323 | pub(crate) const fn iso_year_week(self) -> (i32, u8) { |
324 | let (year, ordinal) = self.to_ordinal_date(); |
325 | |
326 | match ((ordinal + 10 - self.weekday().number_from_monday() as u16) / 7) as _ { |
327 | 0 => (year - 1, weeks_in_year(year - 1)), |
328 | 53 if weeks_in_year(year) == 52 => (year + 1, 1), |
329 | week => (year, week), |
330 | } |
331 | } |
332 | |
333 | /// Get the ISO week number. |
334 | /// |
335 | /// The returned value will always be in the range `1..=53`. |
336 | /// |
337 | /// ```rust |
338 | /// # use time_macros::date; |
339 | /// assert_eq!(date!(2019 - 01 - 01).iso_week(), 1); |
340 | /// assert_eq!(date!(2019 - 10 - 04).iso_week(), 40); |
341 | /// assert_eq!(date!(2020 - 01 - 01).iso_week(), 1); |
342 | /// assert_eq!(date!(2020 - 12 - 31).iso_week(), 53); |
343 | /// assert_eq!(date!(2021 - 01 - 01).iso_week(), 53); |
344 | /// ``` |
345 | pub const fn iso_week(self) -> u8 { |
346 | self.iso_year_week().1 |
347 | } |
348 | |
349 | /// Get the week number where week 1 begins on the first Sunday. |
350 | /// |
351 | /// The returned value will always be in the range `0..=53`. |
352 | /// |
353 | /// ```rust |
354 | /// # use time_macros::date; |
355 | /// assert_eq!(date!(2019 - 01 - 01).sunday_based_week(), 0); |
356 | /// assert_eq!(date!(2020 - 01 - 01).sunday_based_week(), 0); |
357 | /// assert_eq!(date!(2020 - 12 - 31).sunday_based_week(), 52); |
358 | /// assert_eq!(date!(2021 - 01 - 01).sunday_based_week(), 0); |
359 | /// ``` |
360 | pub const fn sunday_based_week(self) -> u8 { |
361 | ((self.ordinal() as i16 - self.weekday().number_days_from_sunday() as i16 + 6) / 7) as _ |
362 | } |
363 | |
364 | /// Get the week number where week 1 begins on the first Monday. |
365 | /// |
366 | /// The returned value will always be in the range `0..=53`. |
367 | /// |
368 | /// ```rust |
369 | /// # use time_macros::date; |
370 | /// assert_eq!(date!(2019 - 01 - 01).monday_based_week(), 0); |
371 | /// assert_eq!(date!(2020 - 01 - 01).monday_based_week(), 0); |
372 | /// assert_eq!(date!(2020 - 12 - 31).monday_based_week(), 52); |
373 | /// assert_eq!(date!(2021 - 01 - 01).monday_based_week(), 0); |
374 | /// ``` |
375 | pub const fn monday_based_week(self) -> u8 { |
376 | ((self.ordinal() as i16 - self.weekday().number_days_from_monday() as i16 + 6) / 7) as _ |
377 | } |
378 | |
379 | /// Get the year, month, and day. |
380 | /// |
381 | /// ```rust |
382 | /// # use time::Month; |
383 | /// # use time_macros::date; |
384 | /// assert_eq!( |
385 | /// date!(2019 - 01 - 01).to_calendar_date(), |
386 | /// (2019, Month::January, 1) |
387 | /// ); |
388 | /// ``` |
389 | pub const fn to_calendar_date(self) -> (i32, Month, u8) { |
390 | let (month, day) = self.month_day(); |
391 | (self.year(), month, day) |
392 | } |
393 | |
394 | /// Get the year and ordinal day number. |
395 | /// |
396 | /// ```rust |
397 | /// # use time_macros::date; |
398 | /// assert_eq!(date!(2019 - 01 - 01).to_ordinal_date(), (2019, 1)); |
399 | /// ``` |
400 | pub const fn to_ordinal_date(self) -> (i32, u16) { |
401 | (self.year(), self.ordinal()) |
402 | } |
403 | |
404 | /// Get the ISO 8601 year, week number, and weekday. |
405 | /// |
406 | /// ```rust |
407 | /// # use time::Weekday::*; |
408 | /// # use time_macros::date; |
409 | /// assert_eq!(date!(2019 - 01 - 01).to_iso_week_date(), (2019, 1, Tuesday)); |
410 | /// assert_eq!(date!(2019 - 10 - 04).to_iso_week_date(), (2019, 40, Friday)); |
411 | /// assert_eq!( |
412 | /// date!(2020 - 01 - 01).to_iso_week_date(), |
413 | /// (2020, 1, Wednesday) |
414 | /// ); |
415 | /// assert_eq!( |
416 | /// date!(2020 - 12 - 31).to_iso_week_date(), |
417 | /// (2020, 53, Thursday) |
418 | /// ); |
419 | /// assert_eq!(date!(2021 - 01 - 01).to_iso_week_date(), (2020, 53, Friday)); |
420 | /// ``` |
421 | pub const fn to_iso_week_date(self) -> (i32, u8, Weekday) { |
422 | let (year, ordinal) = self.to_ordinal_date(); |
423 | let weekday = self.weekday(); |
424 | |
425 | match ((ordinal + 10 - self.weekday().number_from_monday() as u16) / 7) as _ { |
426 | 0 => (year - 1, weeks_in_year(year - 1), weekday), |
427 | 53 if weeks_in_year(year) == 52 => (year + 1, 1, weekday), |
428 | week => (year, week, weekday), |
429 | } |
430 | } |
431 | |
432 | /// Get the weekday. |
433 | /// |
434 | /// ```rust |
435 | /// # use time::Weekday::*; |
436 | /// # use time_macros::date; |
437 | /// assert_eq!(date!(2019 - 01 - 01).weekday(), Tuesday); |
438 | /// assert_eq!(date!(2019 - 02 - 01).weekday(), Friday); |
439 | /// assert_eq!(date!(2019 - 03 - 01).weekday(), Friday); |
440 | /// assert_eq!(date!(2019 - 04 - 01).weekday(), Monday); |
441 | /// assert_eq!(date!(2019 - 05 - 01).weekday(), Wednesday); |
442 | /// assert_eq!(date!(2019 - 06 - 01).weekday(), Saturday); |
443 | /// assert_eq!(date!(2019 - 07 - 01).weekday(), Monday); |
444 | /// assert_eq!(date!(2019 - 08 - 01).weekday(), Thursday); |
445 | /// assert_eq!(date!(2019 - 09 - 01).weekday(), Sunday); |
446 | /// assert_eq!(date!(2019 - 10 - 01).weekday(), Tuesday); |
447 | /// assert_eq!(date!(2019 - 11 - 01).weekday(), Friday); |
448 | /// assert_eq!(date!(2019 - 12 - 01).weekday(), Sunday); |
449 | /// ``` |
450 | pub const fn weekday(self) -> Weekday { |
451 | match self.to_julian_day() % 7 { |
452 | -6 | 1 => Weekday::Tuesday, |
453 | -5 | 2 => Weekday::Wednesday, |
454 | -4 | 3 => Weekday::Thursday, |
455 | -3 | 4 => Weekday::Friday, |
456 | -2 | 5 => Weekday::Saturday, |
457 | -1 | 6 => Weekday::Sunday, |
458 | val => { |
459 | debug_assert!(val == 0); |
460 | Weekday::Monday |
461 | } |
462 | } |
463 | } |
464 | |
465 | /// Get the next calendar date. |
466 | /// |
467 | /// ```rust |
468 | /// # use time::Date; |
469 | /// # use time_macros::date; |
470 | /// assert_eq!( |
471 | /// date!(2019 - 01 - 01).next_day(), |
472 | /// Some(date!(2019 - 01 - 02)) |
473 | /// ); |
474 | /// assert_eq!( |
475 | /// date!(2019 - 01 - 31).next_day(), |
476 | /// Some(date!(2019 - 02 - 01)) |
477 | /// ); |
478 | /// assert_eq!( |
479 | /// date!(2019 - 12 - 31).next_day(), |
480 | /// Some(date!(2020 - 01 - 01)) |
481 | /// ); |
482 | /// assert_eq!(Date::MAX.next_day(), None); |
483 | /// ``` |
484 | pub const fn next_day(self) -> Option<Self> { |
485 | if self.ordinal() == 366 || (self.ordinal() == 365 && !is_leap_year(self.year())) { |
486 | if self.value == Self::MAX.value { |
487 | None |
488 | } else { |
489 | Some(Self::__from_ordinal_date_unchecked(self.year() + 1, 1)) |
490 | } |
491 | } else { |
492 | Some(Self { |
493 | value: self.value + 1, |
494 | }) |
495 | } |
496 | } |
497 | |
498 | /// Get the previous calendar date. |
499 | /// |
500 | /// ```rust |
501 | /// # use time::Date; |
502 | /// # use time_macros::date; |
503 | /// assert_eq!( |
504 | /// date!(2019 - 01 - 02).previous_day(), |
505 | /// Some(date!(2019 - 01 - 01)) |
506 | /// ); |
507 | /// assert_eq!( |
508 | /// date!(2019 - 02 - 01).previous_day(), |
509 | /// Some(date!(2019 - 01 - 31)) |
510 | /// ); |
511 | /// assert_eq!( |
512 | /// date!(2020 - 01 - 01).previous_day(), |
513 | /// Some(date!(2019 - 12 - 31)) |
514 | /// ); |
515 | /// assert_eq!(Date::MIN.previous_day(), None); |
516 | /// ``` |
517 | pub const fn previous_day(self) -> Option<Self> { |
518 | if self.ordinal() != 1 { |
519 | Some(Self { |
520 | value: self.value - 1, |
521 | }) |
522 | } else if self.value == Self::MIN.value { |
523 | None |
524 | } else { |
525 | Some(Self::__from_ordinal_date_unchecked( |
526 | self.year() - 1, |
527 | days_in_year(self.year() - 1), |
528 | )) |
529 | } |
530 | } |
531 | |
532 | /// Get the Julian day for the date. |
533 | /// |
534 | /// The algorithm to perform this conversion is derived from one provided by Peter Baum; it is |
535 | /// freely available [here](https://www.researchgate.net/publication/316558298_Date_Algorithms). |
536 | /// |
537 | /// ```rust |
538 | /// # use time_macros::date; |
539 | /// assert_eq!(date!(-4713 - 11 - 24).to_julian_day(), 0); |
540 | /// assert_eq!(date!(2000 - 01 - 01).to_julian_day(), 2_451_545); |
541 | /// assert_eq!(date!(2019 - 01 - 01).to_julian_day(), 2_458_485); |
542 | /// assert_eq!(date!(2019 - 12 - 31).to_julian_day(), 2_458_849); |
543 | /// ``` |
544 | pub const fn to_julian_day(self) -> i32 { |
545 | let year = self.year() - 1; |
546 | let ordinal = self.ordinal() as i32; |
547 | |
548 | ordinal + 365 * year + div_floor!(year, 4) - div_floor!(year, 100) |
549 | + div_floor!(year, 400) |
550 | + 1_721_425 |
551 | } |
552 | // endregion getters |
553 | |
554 | // region: checked arithmetic |
555 | /// Computes `self + duration`, returning `None` if an overflow occurred. |
556 | /// |
557 | /// ```rust |
558 | /// # use time::{Date, ext::NumericalDuration}; |
559 | /// # use time_macros::date; |
560 | /// assert_eq!(Date::MAX.checked_add(1.days()), None); |
561 | /// assert_eq!(Date::MIN.checked_add((-2).days()), None); |
562 | /// assert_eq!( |
563 | /// date!(2020 - 12 - 31).checked_add(2.days()), |
564 | /// Some(date!(2021 - 01 - 02)) |
565 | /// ); |
566 | /// ``` |
567 | /// |
568 | /// # Note |
569 | /// |
570 | /// This function only takes whole days into account. |
571 | /// |
572 | /// ```rust |
573 | /// # use time::{Date, ext::NumericalDuration}; |
574 | /// # use time_macros::date; |
575 | /// assert_eq!(Date::MAX.checked_add(23.hours()), Some(Date::MAX)); |
576 | /// assert_eq!(Date::MIN.checked_add((-23).hours()), Some(Date::MIN)); |
577 | /// assert_eq!( |
578 | /// date!(2020 - 12 - 31).checked_add(23.hours()), |
579 | /// Some(date!(2020 - 12 - 31)) |
580 | /// ); |
581 | /// assert_eq!( |
582 | /// date!(2020 - 12 - 31).checked_add(47.hours()), |
583 | /// Some(date!(2021 - 01 - 01)) |
584 | /// ); |
585 | /// ``` |
586 | pub const fn checked_add(self, duration: Duration) -> Option<Self> { |
587 | let whole_days = duration.whole_days(); |
588 | if whole_days < i32::MIN as i64 || whole_days > i32::MAX as i64 { |
589 | return None; |
590 | } |
591 | |
592 | let julian_day = const_try_opt!(self.to_julian_day().checked_add(whole_days as _)); |
593 | if let Ok(date) = Self::from_julian_day(julian_day) { |
594 | Some(date) |
595 | } else { |
596 | None |
597 | } |
598 | } |
599 | |
600 | /// Computes `self - duration`, returning `None` if an overflow occurred. |
601 | /// |
602 | /// ``` |
603 | /// # use time::{Date, ext::NumericalDuration}; |
604 | /// # use time_macros::date; |
605 | /// assert_eq!(Date::MAX.checked_sub((-2).days()), None); |
606 | /// assert_eq!(Date::MIN.checked_sub(1.days()), None); |
607 | /// assert_eq!( |
608 | /// date!(2020 - 12 - 31).checked_sub(2.days()), |
609 | /// Some(date!(2020 - 12 - 29)) |
610 | /// ); |
611 | /// ``` |
612 | /// |
613 | /// # Note |
614 | /// |
615 | /// This function only takes whole days into account. |
616 | /// |
617 | /// ``` |
618 | /// # use time::{Date, ext::NumericalDuration}; |
619 | /// # use time_macros::date; |
620 | /// assert_eq!(Date::MAX.checked_sub((-23).hours()), Some(Date::MAX)); |
621 | /// assert_eq!(Date::MIN.checked_sub(23.hours()), Some(Date::MIN)); |
622 | /// assert_eq!( |
623 | /// date!(2020 - 12 - 31).checked_sub(23.hours()), |
624 | /// Some(date!(2020 - 12 - 31)) |
625 | /// ); |
626 | /// assert_eq!( |
627 | /// date!(2020 - 12 - 31).checked_sub(47.hours()), |
628 | /// Some(date!(2020 - 12 - 30)) |
629 | /// ); |
630 | /// ``` |
631 | pub const fn checked_sub(self, duration: Duration) -> Option<Self> { |
632 | let whole_days = duration.whole_days(); |
633 | if whole_days < i32::MIN as i64 || whole_days > i32::MAX as i64 { |
634 | return None; |
635 | } |
636 | |
637 | let julian_day = const_try_opt!(self.to_julian_day().checked_sub(whole_days as _)); |
638 | if let Ok(date) = Self::from_julian_day(julian_day) { |
639 | Some(date) |
640 | } else { |
641 | None |
642 | } |
643 | } |
644 | // endregion: checked arithmetic |
645 | |
646 | // region: saturating arithmetic |
647 | /// Computes `self + duration`, saturating value on overflow. |
648 | /// |
649 | /// ```rust |
650 | /// # use time::{Date, ext::NumericalDuration}; |
651 | /// # use time_macros::date; |
652 | /// assert_eq!(Date::MAX.saturating_add(1.days()), Date::MAX); |
653 | /// assert_eq!(Date::MIN.saturating_add((-2).days()), Date::MIN); |
654 | /// assert_eq!( |
655 | /// date!(2020 - 12 - 31).saturating_add(2.days()), |
656 | /// date!(2021 - 01 - 02) |
657 | /// ); |
658 | /// ``` |
659 | /// |
660 | /// # Note |
661 | /// |
662 | /// This function only takes whole days into account. |
663 | /// |
664 | /// ```rust |
665 | /// # use time::ext::NumericalDuration; |
666 | /// # use time_macros::date; |
667 | /// assert_eq!( |
668 | /// date!(2020 - 12 - 31).saturating_add(23.hours()), |
669 | /// date!(2020 - 12 - 31) |
670 | /// ); |
671 | /// assert_eq!( |
672 | /// date!(2020 - 12 - 31).saturating_add(47.hours()), |
673 | /// date!(2021 - 01 - 01) |
674 | /// ); |
675 | /// ``` |
676 | pub const fn saturating_add(self, duration: Duration) -> Self { |
677 | if let Some(datetime) = self.checked_add(duration) { |
678 | datetime |
679 | } else if duration.is_negative() { |
680 | Self::MIN |
681 | } else { |
682 | debug_assert!(duration.is_positive()); |
683 | Self::MAX |
684 | } |
685 | } |
686 | |
687 | /// Computes `self - duration`, saturating value on overflow. |
688 | /// |
689 | /// ``` |
690 | /// # use time::{Date, ext::NumericalDuration}; |
691 | /// # use time_macros::date; |
692 | /// assert_eq!(Date::MAX.saturating_sub((-2).days()), Date::MAX); |
693 | /// assert_eq!(Date::MIN.saturating_sub(1.days()), Date::MIN); |
694 | /// assert_eq!( |
695 | /// date!(2020 - 12 - 31).saturating_sub(2.days()), |
696 | /// date!(2020 - 12 - 29) |
697 | /// ); |
698 | /// ``` |
699 | /// |
700 | /// # Note |
701 | /// |
702 | /// This function only takes whole days into account. |
703 | /// |
704 | /// ``` |
705 | /// # use time::ext::NumericalDuration; |
706 | /// # use time_macros::date; |
707 | /// assert_eq!( |
708 | /// date!(2020 - 12 - 31).saturating_sub(23.hours()), |
709 | /// date!(2020 - 12 - 31) |
710 | /// ); |
711 | /// assert_eq!( |
712 | /// date!(2020 - 12 - 31).saturating_sub(47.hours()), |
713 | /// date!(2020 - 12 - 30) |
714 | /// ); |
715 | /// ``` |
716 | pub const fn saturating_sub(self, duration: Duration) -> Self { |
717 | if let Some(datetime) = self.checked_sub(duration) { |
718 | datetime |
719 | } else if duration.is_negative() { |
720 | Self::MAX |
721 | } else { |
722 | debug_assert!(duration.is_positive()); |
723 | Self::MIN |
724 | } |
725 | } |
726 | // region: saturating arithmetic |
727 | |
728 | // region: replacement |
729 | /// Replace the year. The month and day will be unchanged. |
730 | /// |
731 | /// ```rust |
732 | /// # use time_macros::date; |
733 | /// assert_eq!( |
734 | /// date!(2022 - 02 - 18).replace_year(2019), |
735 | /// Ok(date!(2019 - 02 - 18)) |
736 | /// ); |
737 | /// assert!(date!(2022 - 02 - 18).replace_year(-1_000_000_000).is_err()); // -1_000_000_000 isn't a valid year |
738 | /// assert!(date!(2022 - 02 - 18).replace_year(1_000_000_000).is_err()); // 1_000_000_000 isn't a valid year |
739 | /// ``` |
740 | #[must_use = "This method does not mutate the original `Date`." ] |
741 | pub const fn replace_year(self, year: i32) -> Result<Self, error::ComponentRange> { |
742 | ensure_value_in_range!(year in MIN_YEAR => MAX_YEAR); |
743 | |
744 | let ordinal = self.ordinal(); |
745 | |
746 | // Dates in January and February are unaffected by leap years. |
747 | if ordinal <= 59 { |
748 | return Ok(Self::__from_ordinal_date_unchecked(year, ordinal)); |
749 | } |
750 | |
751 | match (is_leap_year(self.year()), is_leap_year(year)) { |
752 | (false, false) | (true, true) => Ok(Self::__from_ordinal_date_unchecked(year, ordinal)), |
753 | // February 29 does not exist in common years. |
754 | (true, false) if ordinal == 60 => Err(error::ComponentRange { |
755 | name: "day" , |
756 | value: 29, |
757 | minimum: 1, |
758 | maximum: 28, |
759 | conditional_range: true, |
760 | }), |
761 | // We're going from a common year to a leap year. Shift dates in March and later by |
762 | // one day. |
763 | (false, true) => Ok(Self::__from_ordinal_date_unchecked(year, ordinal + 1)), |
764 | // We're going from a leap year to a common year. Shift dates in January and |
765 | // February by one day. |
766 | (true, false) => Ok(Self::__from_ordinal_date_unchecked(year, ordinal - 1)), |
767 | } |
768 | } |
769 | |
770 | /// Replace the month of the year. |
771 | /// |
772 | /// ```rust |
773 | /// # use time_macros::date; |
774 | /// # use time::Month; |
775 | /// assert_eq!( |
776 | /// date!(2022 - 02 - 18).replace_month(Month::January), |
777 | /// Ok(date!(2022 - 01 - 18)) |
778 | /// ); |
779 | /// assert!( |
780 | /// date!(2022 - 01 - 30) |
781 | /// .replace_month(Month::February) |
782 | /// .is_err() |
783 | /// ); // 30 isn't a valid day in February |
784 | /// ``` |
785 | #[must_use = "This method does not mutate the original `Date`." ] |
786 | pub const fn replace_month(self, month: Month) -> Result<Self, error::ComponentRange> { |
787 | let (year, _, day) = self.to_calendar_date(); |
788 | Self::from_calendar_date(year, month, day) |
789 | } |
790 | |
791 | /// Replace the day of the month. |
792 | /// |
793 | /// ```rust |
794 | /// # use time_macros::date; |
795 | /// assert_eq!( |
796 | /// date!(2022 - 02 - 18).replace_day(1), |
797 | /// Ok(date!(2022 - 02 - 01)) |
798 | /// ); |
799 | /// assert!(date!(2022 - 02 - 18).replace_day(0).is_err()); // 0 isn't a valid day |
800 | /// assert!(date!(2022 - 02 - 18).replace_day(30).is_err()); // 30 isn't a valid day in February |
801 | /// ``` |
802 | #[must_use = "This method does not mutate the original `Date`." ] |
803 | pub const fn replace_day(self, day: u8) -> Result<Self, error::ComponentRange> { |
804 | // Days 1-28 are present in every month, so we can skip checking. |
805 | if day == 0 || day >= 29 { |
806 | ensure_value_in_range!( |
807 | day conditionally in 1 => days_in_year_month(self.year(), self.month()) |
808 | ); |
809 | } |
810 | |
811 | Ok(Self::__from_ordinal_date_unchecked( |
812 | self.year(), |
813 | (self.ordinal() as i16 - self.day() as i16 + day as i16) as _, |
814 | )) |
815 | } |
816 | // endregion replacement |
817 | } |
818 | |
819 | // region: attach time |
820 | /// Methods to add a [`Time`] component, resulting in a [`PrimitiveDateTime`]. |
821 | impl Date { |
822 | /// Create a [`PrimitiveDateTime`] using the existing date. The [`Time`] component will be set |
823 | /// to midnight. |
824 | /// |
825 | /// ```rust |
826 | /// # use time_macros::{date, datetime}; |
827 | /// assert_eq!(date!(1970-01-01).midnight(), datetime!(1970-01-01 0:00)); |
828 | /// ``` |
829 | pub const fn midnight(self) -> PrimitiveDateTime { |
830 | PrimitiveDateTime::new(self, Time::MIDNIGHT) |
831 | } |
832 | |
833 | /// Create a [`PrimitiveDateTime`] using the existing date and the provided [`Time`]. |
834 | /// |
835 | /// ```rust |
836 | /// # use time_macros::{date, datetime, time}; |
837 | /// assert_eq!( |
838 | /// date!(1970-01-01).with_time(time!(0:00)), |
839 | /// datetime!(1970-01-01 0:00), |
840 | /// ); |
841 | /// ``` |
842 | pub const fn with_time(self, time: Time) -> PrimitiveDateTime { |
843 | PrimitiveDateTime::new(self, time) |
844 | } |
845 | |
846 | /// Attempt to create a [`PrimitiveDateTime`] using the existing date and the provided time. |
847 | /// |
848 | /// ```rust |
849 | /// # use time_macros::date; |
850 | /// assert!(date!(1970 - 01 - 01).with_hms(0, 0, 0).is_ok()); |
851 | /// assert!(date!(1970 - 01 - 01).with_hms(24, 0, 0).is_err()); |
852 | /// ``` |
853 | pub const fn with_hms( |
854 | self, |
855 | hour: u8, |
856 | minute: u8, |
857 | second: u8, |
858 | ) -> Result<PrimitiveDateTime, error::ComponentRange> { |
859 | Ok(PrimitiveDateTime::new( |
860 | self, |
861 | const_try!(Time::from_hms(hour, minute, second)), |
862 | )) |
863 | } |
864 | |
865 | /// Attempt to create a [`PrimitiveDateTime`] using the existing date and the provided time. |
866 | /// |
867 | /// ```rust |
868 | /// # use time_macros::date; |
869 | /// assert!(date!(1970 - 01 - 01).with_hms_milli(0, 0, 0, 0).is_ok()); |
870 | /// assert!(date!(1970 - 01 - 01).with_hms_milli(24, 0, 0, 0).is_err()); |
871 | /// ``` |
872 | pub const fn with_hms_milli( |
873 | self, |
874 | hour: u8, |
875 | minute: u8, |
876 | second: u8, |
877 | millisecond: u16, |
878 | ) -> Result<PrimitiveDateTime, error::ComponentRange> { |
879 | Ok(PrimitiveDateTime::new( |
880 | self, |
881 | const_try!(Time::from_hms_milli(hour, minute, second, millisecond)), |
882 | )) |
883 | } |
884 | |
885 | /// Attempt to create a [`PrimitiveDateTime`] using the existing date and the provided time. |
886 | /// |
887 | /// ```rust |
888 | /// # use time_macros::date; |
889 | /// assert!(date!(1970 - 01 - 01).with_hms_micro(0, 0, 0, 0).is_ok()); |
890 | /// assert!(date!(1970 - 01 - 01).with_hms_micro(24, 0, 0, 0).is_err()); |
891 | /// ``` |
892 | pub const fn with_hms_micro( |
893 | self, |
894 | hour: u8, |
895 | minute: u8, |
896 | second: u8, |
897 | microsecond: u32, |
898 | ) -> Result<PrimitiveDateTime, error::ComponentRange> { |
899 | Ok(PrimitiveDateTime::new( |
900 | self, |
901 | const_try!(Time::from_hms_micro(hour, minute, second, microsecond)), |
902 | )) |
903 | } |
904 | |
905 | /// Attempt to create a [`PrimitiveDateTime`] using the existing date and the provided time. |
906 | /// |
907 | /// ```rust |
908 | /// # use time_macros::date; |
909 | /// assert!(date!(1970 - 01 - 01).with_hms_nano(0, 0, 0, 0).is_ok()); |
910 | /// assert!(date!(1970 - 01 - 01).with_hms_nano(24, 0, 0, 0).is_err()); |
911 | /// ``` |
912 | pub const fn with_hms_nano( |
913 | self, |
914 | hour: u8, |
915 | minute: u8, |
916 | second: u8, |
917 | nanosecond: u32, |
918 | ) -> Result<PrimitiveDateTime, error::ComponentRange> { |
919 | Ok(PrimitiveDateTime::new( |
920 | self, |
921 | const_try!(Time::from_hms_nano(hour, minute, second, nanosecond)), |
922 | )) |
923 | } |
924 | } |
925 | // endregion attach time |
926 | |
927 | // region: formatting & parsing |
928 | #[cfg (feature = "formatting" )] |
929 | impl Date { |
930 | /// Format the `Date` using the provided [format description](crate::format_description). |
931 | pub fn format_into( |
932 | self, |
933 | output: &mut impl io::Write, |
934 | format: &(impl Formattable + ?Sized), |
935 | ) -> Result<usize, error::Format> { |
936 | format.format_into(output, date:Some(self), time:None, offset:None) |
937 | } |
938 | |
939 | /// Format the `Date` using the provided [format description](crate::format_description). |
940 | /// |
941 | /// ```rust |
942 | /// # use time::{format_description}; |
943 | /// # use time_macros::date; |
944 | /// let format = format_description::parse("[year]-[month]-[day]" )?; |
945 | /// assert_eq!(date!(2020 - 01 - 02).format(&format)?, "2020-01-02" ); |
946 | /// # Ok::<_, time::Error>(()) |
947 | /// ``` |
948 | pub fn format(self, format: &(impl Formattable + ?Sized)) -> Result<String, error::Format> { |
949 | format.format(date:Some(self), time:None, offset:None) |
950 | } |
951 | } |
952 | |
953 | #[cfg (feature = "parsing" )] |
954 | impl Date { |
955 | /// Parse a `Date` from the input using the provided [format |
956 | /// description](crate::format_description). |
957 | /// |
958 | /// ```rust |
959 | /// # use time::Date; |
960 | /// # use time_macros::{date, format_description}; |
961 | /// let format = format_description!("[year]-[month]-[day]"); |
962 | /// assert_eq!(Date::parse("2020-01-02", &format)?, date!(2020 - 01 - 02)); |
963 | /// # Ok::<_, time::Error>(()) |
964 | /// ``` |
965 | pub fn parse( |
966 | input: &str, |
967 | description: &(impl Parsable + ?Sized), |
968 | ) -> Result<Self, error::Parse> { |
969 | description.parse_date(input.as_bytes()) |
970 | } |
971 | } |
972 | |
973 | impl fmt::Display for Date { |
974 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
975 | if cfg!(feature = "large-dates" ) && self.year().abs() >= 10_000 { |
976 | write!( |
977 | f, |
978 | " {:+}- {:02}- {:02}" , |
979 | self.year(), |
980 | self.month() as u8, |
981 | self.day() |
982 | ) |
983 | } else { |
984 | write!( |
985 | f, |
986 | " {:0width$}- {:02}- {:02}" , |
987 | self.year(), |
988 | self.month() as u8, |
989 | self.day(), |
990 | width = 4 + (self.year() < 0) as usize |
991 | ) |
992 | } |
993 | } |
994 | } |
995 | |
996 | impl fmt::Debug for Date { |
997 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { |
998 | fmt::Display::fmt(self, f) |
999 | } |
1000 | } |
1001 | // endregion formatting & parsing |
1002 | |
1003 | // region: trait impls |
1004 | impl Add<Duration> for Date { |
1005 | type Output = Self; |
1006 | |
1007 | fn add(self, duration: Duration) -> Self::Output { |
1008 | self.checked_add(duration) |
1009 | .expect(msg:"overflow adding duration to date" ) |
1010 | } |
1011 | } |
1012 | |
1013 | impl Add<StdDuration> for Date { |
1014 | type Output = Self; |
1015 | |
1016 | fn add(self, duration: StdDuration) -> Self::Output { |
1017 | Self::from_julian_day( |
1018 | self.to_julian_day() + (duration.as_secs() / Second.per(Day) as u64) as i32, |
1019 | ) |
1020 | .expect(msg:"overflow adding duration to date" ) |
1021 | } |
1022 | } |
1023 | |
1024 | impl_add_assign!(Date: Duration, StdDuration); |
1025 | |
1026 | impl Sub<Duration> for Date { |
1027 | type Output = Self; |
1028 | |
1029 | fn sub(self, duration: Duration) -> Self::Output { |
1030 | self.checked_sub(duration) |
1031 | .expect(msg:"overflow subtracting duration from date" ) |
1032 | } |
1033 | } |
1034 | |
1035 | impl Sub<StdDuration> for Date { |
1036 | type Output = Self; |
1037 | |
1038 | fn sub(self, duration: StdDuration) -> Self::Output { |
1039 | Self::from_julian_day( |
1040 | self.to_julian_day() - (duration.as_secs() / Second.per(Day) as u64) as i32, |
1041 | ) |
1042 | .expect(msg:"overflow subtracting duration from date" ) |
1043 | } |
1044 | } |
1045 | |
1046 | impl_sub_assign!(Date: Duration, StdDuration); |
1047 | |
1048 | impl Sub for Date { |
1049 | type Output = Duration; |
1050 | |
1051 | fn sub(self, other: Self) -> Self::Output { |
1052 | Duration::days((self.to_julian_day() - other.to_julian_day()) as _) |
1053 | } |
1054 | } |
1055 | // endregion trait impls |
1056 | |