//! The [`Date`] struct and its associated `impl`s. use core::fmt; use core::ops::{Add, Sub}; use core::time::Duration as StdDuration; #[cfg(feature = "formatting")] use std::io; #[cfg(feature = "formatting")] use crate::formatting::Formattable; #[cfg(feature = "parsing")] use crate::parsing::Parsable; use crate::util::{days_in_year, days_in_year_month, is_leap_year, weeks_in_year}; use crate::{error, Duration, Month, PrimitiveDateTime, Time, Weekday}; /// The minimum valid year. pub(crate) const MIN_YEAR: i32 = if cfg!(feature = "large-dates") { -999_999 } else { -9999 }; /// The maximum valid year. pub(crate) const MAX_YEAR: i32 = if cfg!(feature = "large-dates") { 999_999 } else { 9999 }; /// Date in the proleptic Gregorian calendar. /// /// By default, years between ±9999 inclusive are representable. This can be expanded to ±999,999 /// inclusive by enabling the `large-dates` crate feature. Doing so has performance implications /// and introduces some ambiguities when parsing. #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Date { /// Bitpacked field containing both the year and ordinal. // | xx | xxxxxxxxxxxxxxxxxxxxx | xxxxxxxxx | // | 2 bits | 21 bits | 9 bits | // | unassigned | year | ordinal | // The year is 15 bits when `large-dates` is not enabled. value: i32, } impl Date { /// The minimum valid `Date`. /// /// The value of this may vary depending on the feature flags enabled. pub const MIN: Self = Self::__from_ordinal_date_unchecked(MIN_YEAR, 1); /// The maximum valid `Date`. /// /// The value of this may vary depending on the feature flags enabled. pub const MAX: Self = Self::__from_ordinal_date_unchecked(MAX_YEAR, days_in_year(MAX_YEAR)); // region: constructors /// Construct a `Date` from the year and ordinal values, the validity of which must be /// guaranteed by the caller. #[doc(hidden)] pub const fn __from_ordinal_date_unchecked(year: i32, ordinal: u16) -> Self { debug_assert!(year >= MIN_YEAR); debug_assert!(year <= MAX_YEAR); debug_assert!(ordinal != 0); debug_assert!(ordinal <= days_in_year(year)); Self { value: (year << 9) | ordinal as i32, } } /// Attempt to create a `Date` from the year, month, and day. /// /// ```rust /// # use time::{Date, Month}; /// assert!(Date::from_calendar_date(2019, Month::January, 1).is_ok()); /// assert!(Date::from_calendar_date(2019, Month::December, 31).is_ok()); /// ``` /// /// ```rust /// # use time::{Date, Month}; /// assert!(Date::from_calendar_date(2019, Month::February, 29).is_err()); // 2019 isn't a leap year. /// ``` pub const fn from_calendar_date( year: i32, month: Month, day: u8, ) -> Result { /// Cumulative days through the beginning of a month in both common and leap years. const DAYS_CUMULATIVE_COMMON_LEAP: [[u16; 12]; 2] = [ [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334], [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335], ]; ensure_value_in_range!(year in MIN_YEAR => MAX_YEAR); ensure_value_in_range!(day conditionally in 1 => days_in_year_month(year, month)); Ok(Self::__from_ordinal_date_unchecked( year, DAYS_CUMULATIVE_COMMON_LEAP[is_leap_year(year) as usize][month as usize - 1] + day as u16, )) } /// Attempt to create a `Date` from the year and ordinal day number. /// /// ```rust /// # use time::Date; /// assert!(Date::from_ordinal_date(2019, 1).is_ok()); /// assert!(Date::from_ordinal_date(2019, 365).is_ok()); /// ``` /// /// ```rust /// # use time::Date; /// assert!(Date::from_ordinal_date(2019, 366).is_err()); // 2019 isn't a leap year. /// ``` pub const fn from_ordinal_date(year: i32, ordinal: u16) -> Result { ensure_value_in_range!(year in MIN_YEAR => MAX_YEAR); ensure_value_in_range!(ordinal conditionally in 1 => days_in_year(year)); Ok(Self::__from_ordinal_date_unchecked(year, ordinal)) } /// Attempt to create a `Date` from the ISO year, week, and weekday. /// /// ```rust /// # use time::{Date, Weekday::*}; /// assert!(Date::from_iso_week_date(2019, 1, Monday).is_ok()); /// assert!(Date::from_iso_week_date(2019, 1, Tuesday).is_ok()); /// assert!(Date::from_iso_week_date(2020, 53, Friday).is_ok()); /// ``` /// /// ```rust /// # use time::{Date, Weekday::*}; /// assert!(Date::from_iso_week_date(2019, 53, Monday).is_err()); // 2019 doesn't have 53 weeks. /// ``` pub const fn from_iso_week_date( year: i32, week: u8, weekday: Weekday, ) -> Result { ensure_value_in_range!(year in MIN_YEAR => MAX_YEAR); ensure_value_in_range!(week conditionally in 1 => weeks_in_year(year)); let adj_year = year - 1; let raw = 365 * adj_year + div_floor!(adj_year, 4) - div_floor!(adj_year, 100) + div_floor!(adj_year, 400); let jan_4 = match (raw % 7) as i8 { -6 | 1 => 8, -5 | 2 => 9, -4 | 3 => 10, -3 | 4 => 4, -2 | 5 => 5, -1 | 6 => 6, _ => 7, }; let ordinal = week as i16 * 7 + weekday.number_from_monday() as i16 - jan_4; Ok(if ordinal <= 0 { Self::__from_ordinal_date_unchecked( year - 1, (ordinal as u16).wrapping_add(days_in_year(year - 1)), ) } else if ordinal > days_in_year(year) as i16 { Self::__from_ordinal_date_unchecked(year + 1, ordinal as u16 - days_in_year(year)) } else { Self::__from_ordinal_date_unchecked(year, ordinal as _) }) } /// Create a `Date` from the Julian day. /// /// The algorithm to perform this conversion is derived from one provided by Peter Baum; it is /// freely available [here](https://www.researchgate.net/publication/316558298_Date_Algorithms). /// /// ```rust /// # use time::Date; /// # use time_macros::date; /// assert_eq!(Date::from_julian_day(0), Ok(date!(-4713 - 11 - 24))); /// assert_eq!(Date::from_julian_day(2_451_545), Ok(date!(2000 - 01 - 01))); /// assert_eq!(Date::from_julian_day(2_458_485), Ok(date!(2019 - 01 - 01))); /// assert_eq!(Date::from_julian_day(2_458_849), Ok(date!(2019 - 12 - 31))); /// ``` #[doc(alias = "from_julian_date")] pub const fn from_julian_day(julian_day: i32) -> Result { ensure_value_in_range!( julian_day in Self::MIN.to_julian_day() => Self::MAX.to_julian_day() ); Ok(Self::from_julian_day_unchecked(julian_day)) } /// Create a `Date` from the Julian day. /// /// This does not check the validity of the provided Julian day, and as such may result in an /// internally invalid value. #[doc(alias = "from_julian_date_unchecked")] pub(crate) const fn from_julian_day_unchecked(julian_day: i32) -> Self { debug_assert!(julian_day >= Self::MIN.to_julian_day()); debug_assert!(julian_day <= Self::MAX.to_julian_day()); // To avoid a potential overflow, the value may need to be widened for some arithmetic. let z = julian_day - 1_721_119; let (mut year, mut ordinal) = if julian_day < -19_752_948 || julian_day > 23_195_514 { let g = 100 * z as i64 - 25; let a = (g / 3_652_425) as i32; let b = a - a / 4; let year = div_floor!(100 * b as i64 + g, 36525) as i32; let ordinal = (b + z - div_floor!(36525 * year as i64, 100) as i32) as _; (year, ordinal) } else { let g = 100 * z - 25; let a = g / 3_652_425; let b = a - a / 4; let year = div_floor!(100 * b + g, 36525); let ordinal = (b + z - div_floor!(36525 * year, 100)) as _; (year, ordinal) }; if is_leap_year(year) { ordinal += 60; cascade!(ordinal in 1..367 => year); } else { ordinal += 59; cascade!(ordinal in 1..366 => year); } Self::__from_ordinal_date_unchecked(year, ordinal) } // endregion constructors // region: getters /// Get the year of the date. /// /// ```rust /// # use time_macros::date; /// assert_eq!(date!(2019 - 01 - 01).year(), 2019); /// assert_eq!(date!(2019 - 12 - 31).year(), 2019); /// assert_eq!(date!(2020 - 01 - 01).year(), 2020); /// ``` pub const fn year(self) -> i32 { self.value >> 9 } /// Get the month. /// /// ```rust /// # use time::Month; /// # use time_macros::date; /// assert_eq!(date!(2019 - 01 - 01).month(), Month::January); /// assert_eq!(date!(2019 - 12 - 31).month(), Month::December); /// ``` pub const fn month(self) -> Month { self.month_day().0 } /// Get the day of the month. /// /// The returned value will always be in the range `1..=31`. /// /// ```rust /// # use time_macros::date; /// assert_eq!(date!(2019 - 01 - 01).day(), 1); /// assert_eq!(date!(2019 - 12 - 31).day(), 31); /// ``` pub const fn day(self) -> u8 { self.month_day().1 } /// Get the month and day. This is more efficient than fetching the components individually. // For whatever reason, rustc has difficulty optimizing this function. It's significantly faster // to write the statements out by hand. pub(crate) const fn month_day(self) -> (Month, u8) { /// The number of days up to and including the given month. Common years /// are first, followed by leap years. const CUMULATIVE_DAYS_IN_MONTH_COMMON_LEAP: [[u16; 11]; 2] = [ [31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334], [31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335], ]; let days = CUMULATIVE_DAYS_IN_MONTH_COMMON_LEAP[is_leap_year(self.year()) as usize]; let ordinal = self.ordinal(); if ordinal > days[10] { (Month::December, (ordinal - days[10]) as _) } else if ordinal > days[9] { (Month::November, (ordinal - days[9]) as _) } else if ordinal > days[8] { (Month::October, (ordinal - days[8]) as _) } else if ordinal > days[7] { (Month::September, (ordinal - days[7]) as _) } else if ordinal > days[6] { (Month::August, (ordinal - days[6]) as _) } else if ordinal > days[5] { (Month::July, (ordinal - days[5]) as _) } else if ordinal > days[4] { (Month::June, (ordinal - days[4]) as _) } else if ordinal > days[3] { (Month::May, (ordinal - days[3]) as _) } else if ordinal > days[2] { (Month::April, (ordinal - days[2]) as _) } else if ordinal > days[1] { (Month::March, (ordinal - days[1]) as _) } else if ordinal > days[0] { (Month::February, (ordinal - days[0]) as _) } else { (Month::January, ordinal as _) } } /// Get the day of the year. /// /// The returned value will always be in the range `1..=366` (`1..=365` for common years). /// /// ```rust /// # use time_macros::date; /// assert_eq!(date!(2019 - 01 - 01).ordinal(), 1); /// assert_eq!(date!(2019 - 12 - 31).ordinal(), 365); /// ``` pub const fn ordinal(self) -> u16 { (self.value & 0x1FF) as _ } /// Get the ISO 8601 year and week number. pub(crate) const fn iso_year_week(self) -> (i32, u8) { let (year, ordinal) = self.to_ordinal_date(); match ((ordinal + 10 - self.weekday().number_from_monday() as u16) / 7) as _ { 0 => (year - 1, weeks_in_year(year - 1)), 53 if weeks_in_year(year) == 52 => (year + 1, 1), week => (year, week), } } /// Get the ISO week number. /// /// The returned value will always be in the range `1..=53`. /// /// ```rust /// # use time_macros::date; /// assert_eq!(date!(2019 - 01 - 01).iso_week(), 1); /// assert_eq!(date!(2019 - 10 - 04).iso_week(), 40); /// assert_eq!(date!(2020 - 01 - 01).iso_week(), 1); /// assert_eq!(date!(2020 - 12 - 31).iso_week(), 53); /// assert_eq!(date!(2021 - 01 - 01).iso_week(), 53); /// ``` pub const fn iso_week(self) -> u8 { self.iso_year_week().1 } /// Get the week number where week 1 begins on the first Sunday. /// /// The returned value will always be in the range `0..=53`. /// /// ```rust /// # use time_macros::date; /// assert_eq!(date!(2019 - 01 - 01).sunday_based_week(), 0); /// assert_eq!(date!(2020 - 01 - 01).sunday_based_week(), 0); /// assert_eq!(date!(2020 - 12 - 31).sunday_based_week(), 52); /// assert_eq!(date!(2021 - 01 - 01).sunday_based_week(), 0); /// ``` pub const fn sunday_based_week(self) -> u8 { ((self.ordinal() as i16 - self.weekday().number_days_from_sunday() as i16 + 6) / 7) as _ } /// Get the week number where week 1 begins on the first Monday. /// /// The returned value will always be in the range `0..=53`. /// /// ```rust /// # use time_macros::date; /// assert_eq!(date!(2019 - 01 - 01).monday_based_week(), 0); /// assert_eq!(date!(2020 - 01 - 01).monday_based_week(), 0); /// assert_eq!(date!(2020 - 12 - 31).monday_based_week(), 52); /// assert_eq!(date!(2021 - 01 - 01).monday_based_week(), 0); /// ``` pub const fn monday_based_week(self) -> u8 { ((self.ordinal() as i16 - self.weekday().number_days_from_monday() as i16 + 6) / 7) as _ } /// Get the year, month, and day. /// /// ```rust /// # use time::Month; /// # use time_macros::date; /// assert_eq!( /// date!(2019 - 01 - 01).to_calendar_date(), /// (2019, Month::January, 1) /// ); /// ``` pub const fn to_calendar_date(self) -> (i32, Month, u8) { let (month, day) = self.month_day(); (self.year(), month, day) } /// Get the year and ordinal day number. /// /// ```rust /// # use time_macros::date; /// assert_eq!(date!(2019 - 01 - 01).to_ordinal_date(), (2019, 1)); /// ``` pub const fn to_ordinal_date(self) -> (i32, u16) { (self.year(), self.ordinal()) } /// Get the ISO 8601 year, week number, and weekday. /// /// ```rust /// # use time::Weekday::*; /// # use time_macros::date; /// assert_eq!(date!(2019 - 01 - 01).to_iso_week_date(), (2019, 1, Tuesday)); /// assert_eq!(date!(2019 - 10 - 04).to_iso_week_date(), (2019, 40, Friday)); /// assert_eq!( /// date!(2020 - 01 - 01).to_iso_week_date(), /// (2020, 1, Wednesday) /// ); /// assert_eq!( /// date!(2020 - 12 - 31).to_iso_week_date(), /// (2020, 53, Thursday) /// ); /// assert_eq!(date!(2021 - 01 - 01).to_iso_week_date(), (2020, 53, Friday)); /// ``` pub const fn to_iso_week_date(self) -> (i32, u8, Weekday) { let (year, ordinal) = self.to_ordinal_date(); let weekday = self.weekday(); match ((ordinal + 10 - self.weekday().number_from_monday() as u16) / 7) as _ { 0 => (year - 1, weeks_in_year(year - 1), weekday), 53 if weeks_in_year(year) == 52 => (year + 1, 1, weekday), week => (year, week, weekday), } } /// Get the weekday. /// /// ```rust /// # use time::Weekday::*; /// # use time_macros::date; /// assert_eq!(date!(2019 - 01 - 01).weekday(), Tuesday); /// assert_eq!(date!(2019 - 02 - 01).weekday(), Friday); /// assert_eq!(date!(2019 - 03 - 01).weekday(), Friday); /// assert_eq!(date!(2019 - 04 - 01).weekday(), Monday); /// assert_eq!(date!(2019 - 05 - 01).weekday(), Wednesday); /// assert_eq!(date!(2019 - 06 - 01).weekday(), Saturday); /// assert_eq!(date!(2019 - 07 - 01).weekday(), Monday); /// assert_eq!(date!(2019 - 08 - 01).weekday(), Thursday); /// assert_eq!(date!(2019 - 09 - 01).weekday(), Sunday); /// assert_eq!(date!(2019 - 10 - 01).weekday(), Tuesday); /// assert_eq!(date!(2019 - 11 - 01).weekday(), Friday); /// assert_eq!(date!(2019 - 12 - 01).weekday(), Sunday); /// ``` pub const fn weekday(self) -> Weekday { match self.to_julian_day() % 7 { -6 | 1 => Weekday::Tuesday, -5 | 2 => Weekday::Wednesday, -4 | 3 => Weekday::Thursday, -3 | 4 => Weekday::Friday, -2 | 5 => Weekday::Saturday, -1 | 6 => Weekday::Sunday, val => { debug_assert!(val == 0); Weekday::Monday } } } /// Get the next calendar date. /// /// ```rust /// # use time::Date; /// # use time_macros::date; /// assert_eq!( /// date!(2019 - 01 - 01).next_day(), /// Some(date!(2019 - 01 - 02)) /// ); /// assert_eq!( /// date!(2019 - 01 - 31).next_day(), /// Some(date!(2019 - 02 - 01)) /// ); /// assert_eq!( /// date!(2019 - 12 - 31).next_day(), /// Some(date!(2020 - 01 - 01)) /// ); /// assert_eq!(Date::MAX.next_day(), None); /// ``` pub const fn next_day(self) -> Option { if self.ordinal() == 366 || (self.ordinal() == 365 && !is_leap_year(self.year())) { if self.value == Self::MAX.value { None } else { Some(Self::__from_ordinal_date_unchecked(self.year() + 1, 1)) } } else { Some(Self { value: self.value + 1, }) } } /// Get the previous calendar date. /// /// ```rust /// # use time::Date; /// # use time_macros::date; /// assert_eq!( /// date!(2019 - 01 - 02).previous_day(), /// Some(date!(2019 - 01 - 01)) /// ); /// assert_eq!( /// date!(2019 - 02 - 01).previous_day(), /// Some(date!(2019 - 01 - 31)) /// ); /// assert_eq!( /// date!(2020 - 01 - 01).previous_day(), /// Some(date!(2019 - 12 - 31)) /// ); /// assert_eq!(Date::MIN.previous_day(), None); /// ``` pub const fn previous_day(self) -> Option { if self.ordinal() != 1 { Some(Self { value: self.value - 1, }) } else if self.value == Self::MIN.value { None } else { Some(Self::__from_ordinal_date_unchecked( self.year() - 1, days_in_year(self.year() - 1), )) } } /// Get the Julian day for the date. /// /// The algorithm to perform this conversion is derived from one provided by Peter Baum; it is /// freely available [here](https://www.researchgate.net/publication/316558298_Date_Algorithms). /// /// ```rust /// # use time_macros::date; /// assert_eq!(date!(-4713 - 11 - 24).to_julian_day(), 0); /// assert_eq!(date!(2000 - 01 - 01).to_julian_day(), 2_451_545); /// assert_eq!(date!(2019 - 01 - 01).to_julian_day(), 2_458_485); /// assert_eq!(date!(2019 - 12 - 31).to_julian_day(), 2_458_849); /// ``` pub const fn to_julian_day(self) -> i32 { let year = self.year() - 1; let ordinal = self.ordinal() as i32; ordinal + 365 * year + div_floor!(year, 4) - div_floor!(year, 100) + div_floor!(year, 400) + 1_721_425 } // endregion getters // region: checked arithmetic /// Computes `self + duration`, returning `None` if an overflow occurred. /// /// ```rust /// # use time::{Date, ext::NumericalDuration}; /// # use time_macros::date; /// assert_eq!(Date::MAX.checked_add(1.days()), None); /// assert_eq!(Date::MIN.checked_add((-2).days()), None); /// assert_eq!( /// date!(2020 - 12 - 31).checked_add(2.days()), /// Some(date!(2021 - 01 - 02)) /// ); /// ``` /// /// # Note /// /// This function only takes whole days into account. /// /// ```rust /// # use time::{Date, ext::NumericalDuration}; /// # use time_macros::date; /// assert_eq!(Date::MAX.checked_add(23.hours()), Some(Date::MAX)); /// assert_eq!(Date::MIN.checked_add((-23).hours()), Some(Date::MIN)); /// assert_eq!( /// date!(2020 - 12 - 31).checked_add(23.hours()), /// Some(date!(2020 - 12 - 31)) /// ); /// assert_eq!( /// date!(2020 - 12 - 31).checked_add(47.hours()), /// Some(date!(2021 - 01 - 01)) /// ); /// ``` pub const fn checked_add(self, duration: Duration) -> Option { let whole_days = duration.whole_days(); if whole_days < i32::MIN as i64 || whole_days > i32::MAX as i64 { return None; } let julian_day = const_try_opt!(self.to_julian_day().checked_add(whole_days as _)); if let Ok(date) = Self::from_julian_day(julian_day) { Some(date) } else { None } } /// Computes `self - duration`, returning `None` if an overflow occurred. /// /// ``` /// # use time::{Date, ext::NumericalDuration}; /// # use time_macros::date; /// assert_eq!(Date::MAX.checked_sub((-2).days()), None); /// assert_eq!(Date::MIN.checked_sub(1.days()), None); /// assert_eq!( /// date!(2020 - 12 - 31).checked_sub(2.days()), /// Some(date!(2020 - 12 - 29)) /// ); /// ``` /// /// # Note /// /// This function only takes whole days into account. /// /// ``` /// # use time::{Date, ext::NumericalDuration}; /// # use time_macros::date; /// assert_eq!(Date::MAX.checked_sub((-23).hours()), Some(Date::MAX)); /// assert_eq!(Date::MIN.checked_sub(23.hours()), Some(Date::MIN)); /// assert_eq!( /// date!(2020 - 12 - 31).checked_sub(23.hours()), /// Some(date!(2020 - 12 - 31)) /// ); /// assert_eq!( /// date!(2020 - 12 - 31).checked_sub(47.hours()), /// Some(date!(2020 - 12 - 30)) /// ); /// ``` pub const fn checked_sub(self, duration: Duration) -> Option { let whole_days = duration.whole_days(); if whole_days < i32::MIN as i64 || whole_days > i32::MAX as i64 { return None; } let julian_day = const_try_opt!(self.to_julian_day().checked_sub(whole_days as _)); if let Ok(date) = Self::from_julian_day(julian_day) { Some(date) } else { None } } // endregion: checked arithmetic // region: saturating arithmetic /// Computes `self + duration`, saturating value on overflow. /// /// ```rust /// # use time::{Date, ext::NumericalDuration}; /// # use time_macros::date; /// assert_eq!(Date::MAX.saturating_add(1.days()), Date::MAX); /// assert_eq!(Date::MIN.saturating_add((-2).days()), Date::MIN); /// assert_eq!( /// date!(2020 - 12 - 31).saturating_add(2.days()), /// date!(2021 - 01 - 02) /// ); /// ``` /// /// # Note /// /// This function only takes whole days into account. /// /// ```rust /// # use time::ext::NumericalDuration; /// # use time_macros::date; /// assert_eq!( /// date!(2020 - 12 - 31).saturating_add(23.hours()), /// date!(2020 - 12 - 31) /// ); /// assert_eq!( /// date!(2020 - 12 - 31).saturating_add(47.hours()), /// date!(2021 - 01 - 01) /// ); /// ``` pub const fn saturating_add(self, duration: Duration) -> Self { if let Some(datetime) = self.checked_add(duration) { datetime } else if duration.is_negative() { Self::MIN } else { debug_assert!(duration.is_positive()); Self::MAX } } /// Computes `self - duration`, saturating value on overflow. /// /// ``` /// # use time::{Date, ext::NumericalDuration}; /// # use time_macros::date; /// assert_eq!(Date::MAX.saturating_sub((-2).days()), Date::MAX); /// assert_eq!(Date::MIN.saturating_sub(1.days()), Date::MIN); /// assert_eq!( /// date!(2020 - 12 - 31).saturating_sub(2.days()), /// date!(2020 - 12 - 29) /// ); /// ``` /// /// # Note /// /// This function only takes whole days into account. /// /// ``` /// # use time::ext::NumericalDuration; /// # use time_macros::date; /// assert_eq!( /// date!(2020 - 12 - 31).saturating_sub(23.hours()), /// date!(2020 - 12 - 31) /// ); /// assert_eq!( /// date!(2020 - 12 - 31).saturating_sub(47.hours()), /// date!(2020 - 12 - 30) /// ); /// ``` pub const fn saturating_sub(self, duration: Duration) -> Self { if let Some(datetime) = self.checked_sub(duration) { datetime } else if duration.is_negative() { Self::MAX } else { debug_assert!(duration.is_positive()); Self::MIN } } // region: saturating arithmetic // region: replacement /// Replace the year. The month and day will be unchanged. /// /// ```rust /// # use time_macros::date; /// assert_eq!( /// date!(2022 - 02 - 18).replace_year(2019), /// Ok(date!(2019 - 02 - 18)) /// ); /// assert!(date!(2022 - 02 - 18).replace_year(-1_000_000_000).is_err()); // -1_000_000_000 isn't a valid year /// assert!(date!(2022 - 02 - 18).replace_year(1_000_000_000).is_err()); // 1_000_000_000 isn't a valid year /// ``` #[must_use = "This method does not mutate the original `Date`."] pub const fn replace_year(self, year: i32) -> Result { ensure_value_in_range!(year in MIN_YEAR => MAX_YEAR); let ordinal = self.ordinal(); // Dates in January and February are unaffected by leap years. if ordinal <= 59 { return Ok(Self::__from_ordinal_date_unchecked(year, ordinal)); } match (is_leap_year(self.year()), is_leap_year(year)) { (false, false) | (true, true) => Ok(Self::__from_ordinal_date_unchecked(year, ordinal)), // February 29 does not exist in common years. (true, false) if ordinal == 60 => Err(error::ComponentRange { name: "day", value: 29, minimum: 1, maximum: 28, conditional_range: true, }), // We're going from a common year to a leap year. Shift dates in March and later by // one day. (false, true) => Ok(Self::__from_ordinal_date_unchecked(year, ordinal + 1)), // We're going from a leap year to a common year. Shift dates in January and // February by one day. (true, false) => Ok(Self::__from_ordinal_date_unchecked(year, ordinal - 1)), } } /// Replace the month of the year. /// /// ```rust /// # use time_macros::date; /// # use time::Month; /// assert_eq!( /// date!(2022 - 02 - 18).replace_month(Month::January), /// Ok(date!(2022 - 01 - 18)) /// ); /// assert!( /// date!(2022 - 01 - 30) /// .replace_month(Month::February) /// .is_err() /// ); // 30 isn't a valid day in February /// ``` #[must_use = "This method does not mutate the original `Date`."] pub const fn replace_month(self, month: Month) -> Result { let (year, _, day) = self.to_calendar_date(); Self::from_calendar_date(year, month, day) } /// Replace the day of the month. /// /// ```rust /// # use time_macros::date; /// assert_eq!( /// date!(2022 - 02 - 18).replace_day(1), /// Ok(date!(2022 - 02 - 01)) /// ); /// assert!(date!(2022 - 02 - 18).replace_day(0).is_err()); // 0 isn't a valid day /// assert!(date!(2022 - 02 - 18).replace_day(30).is_err()); // 30 isn't a valid day in February /// ``` #[must_use = "This method does not mutate the original `Date`."] pub const fn replace_day(self, day: u8) -> Result { // Days 1-28 are present in every month, so we can skip checking. if day == 0 || day >= 29 { ensure_value_in_range!( day conditionally in 1 => days_in_year_month(self.year(), self.month()) ); } Ok(Self::__from_ordinal_date_unchecked( self.year(), (self.ordinal() as i16 - self.day() as i16 + day as i16) as _, )) } // endregion replacement } // region: attach time /// Methods to add a [`Time`] component, resulting in a [`PrimitiveDateTime`]. impl Date { /// Create a [`PrimitiveDateTime`] using the existing date. The [`Time`] component will be set /// to midnight. /// /// ```rust /// # use time_macros::{date, datetime}; /// assert_eq!(date!(1970-01-01).midnight(), datetime!(1970-01-01 0:00)); /// ``` pub const fn midnight(self) -> PrimitiveDateTime { PrimitiveDateTime::new(self, Time::MIDNIGHT) } /// Create a [`PrimitiveDateTime`] using the existing date and the provided [`Time`]. /// /// ```rust /// # use time_macros::{date, datetime, time}; /// assert_eq!( /// date!(1970-01-01).with_time(time!(0:00)), /// datetime!(1970-01-01 0:00), /// ); /// ``` pub const fn with_time(self, time: Time) -> PrimitiveDateTime { PrimitiveDateTime::new(self, time) } /// Attempt to create a [`PrimitiveDateTime`] using the existing date and the provided time. /// /// ```rust /// # use time_macros::date; /// assert!(date!(1970 - 01 - 01).with_hms(0, 0, 0).is_ok()); /// assert!(date!(1970 - 01 - 01).with_hms(24, 0, 0).is_err()); /// ``` pub const fn with_hms( self, hour: u8, minute: u8, second: u8, ) -> Result { Ok(PrimitiveDateTime::new( self, const_try!(Time::from_hms(hour, minute, second)), )) } /// Attempt to create a [`PrimitiveDateTime`] using the existing date and the provided time. /// /// ```rust /// # use time_macros::date; /// assert!(date!(1970 - 01 - 01).with_hms_milli(0, 0, 0, 0).is_ok()); /// assert!(date!(1970 - 01 - 01).with_hms_milli(24, 0, 0, 0).is_err()); /// ``` pub const fn with_hms_milli( self, hour: u8, minute: u8, second: u8, millisecond: u16, ) -> Result { Ok(PrimitiveDateTime::new( self, const_try!(Time::from_hms_milli(hour, minute, second, millisecond)), )) } /// Attempt to create a [`PrimitiveDateTime`] using the existing date and the provided time. /// /// ```rust /// # use time_macros::date; /// assert!(date!(1970 - 01 - 01).with_hms_micro(0, 0, 0, 0).is_ok()); /// assert!(date!(1970 - 01 - 01).with_hms_micro(24, 0, 0, 0).is_err()); /// ``` pub const fn with_hms_micro( self, hour: u8, minute: u8, second: u8, microsecond: u32, ) -> Result { Ok(PrimitiveDateTime::new( self, const_try!(Time::from_hms_micro(hour, minute, second, microsecond)), )) } /// Attempt to create a [`PrimitiveDateTime`] using the existing date and the provided time. /// /// ```rust /// # use time_macros::date; /// assert!(date!(1970 - 01 - 01).with_hms_nano(0, 0, 0, 0).is_ok()); /// assert!(date!(1970 - 01 - 01).with_hms_nano(24, 0, 0, 0).is_err()); /// ``` pub const fn with_hms_nano( self, hour: u8, minute: u8, second: u8, nanosecond: u32, ) -> Result { Ok(PrimitiveDateTime::new( self, const_try!(Time::from_hms_nano(hour, minute, second, nanosecond)), )) } } // endregion attach time // region: formatting & parsing #[cfg(feature = "formatting")] impl Date { /// Format the `Date` using the provided [format description](crate::format_description). pub fn format_into( self, output: &mut impl io::Write, format: &(impl Formattable + ?Sized), ) -> Result { format.format_into(output, Some(self), None, None) } /// Format the `Date` using the provided [format description](crate::format_description). /// /// ```rust /// # use time::{format_description}; /// # use time_macros::date; /// let format = format_description::parse("[year]-[month]-[day]")?; /// assert_eq!(date!(2020 - 01 - 02).format(&format)?, "2020-01-02"); /// # Ok::<_, time::Error>(()) /// ``` pub fn format(self, format: &(impl Formattable + ?Sized)) -> Result { format.format(Some(self), None, None) } } #[cfg(feature = "parsing")] impl Date { /// Parse a `Date` from the input using the provided [format /// description](crate::format_description). /// /// ```rust /// # use time::Date; /// # use time_macros::{date, format_description}; /// let format = format_description!("[year]-[month]-[day]"); /// assert_eq!(Date::parse("2020-01-02", &format)?, date!(2020 - 01 - 02)); /// # Ok::<_, time::Error>(()) /// ``` pub fn parse( input: &str, description: &(impl Parsable + ?Sized), ) -> Result { description.parse_date(input.as_bytes()) } } impl fmt::Display for Date { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if cfg!(feature = "large-dates") && self.year().abs() >= 10_000 { write!( f, "{:+}-{:02}-{:02}", self.year(), self.month() as u8, self.day() ) } else { write!( f, "{:0width$}-{:02}-{:02}", self.year(), self.month() as u8, self.day(), width = 4 + (self.year() < 0) as usize ) } } } impl fmt::Debug for Date { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { fmt::Display::fmt(self, f) } } // endregion formatting & parsing // region: trait impls impl Add for Date { type Output = Self; fn add(self, duration: Duration) -> Self::Output { self.checked_add(duration) .expect("overflow adding duration to date") } } impl Add for Date { type Output = Self; fn add(self, duration: StdDuration) -> Self::Output { Self::from_julian_day(self.to_julian_day() + (duration.as_secs() / 86_400) as i32) .expect("overflow adding duration to date") } } impl_add_assign!(Date: Duration, StdDuration); impl Sub for Date { type Output = Self; fn sub(self, duration: Duration) -> Self::Output { self.checked_sub(duration) .expect("overflow subtracting duration from date") } } impl Sub for Date { type Output = Self; fn sub(self, duration: StdDuration) -> Self::Output { Self::from_julian_day(self.to_julian_day() - (duration.as_secs() / 86_400) as i32) .expect("overflow subtracting duration from date") } } impl_sub_assign!(Date: Duration, StdDuration); impl Sub for Date { type Output = Duration; fn sub(self, other: Self) -> Self::Output { Duration::days((self.to_julian_day() - other.to_julian_day()) as _) } } // endregion trait impls