From a2f9480447e43e2c554d943e4513e05a902cb2e8 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Wed, 3 Jul 2024 09:41:06 -0400 Subject: [PATCH] progress --- src/civil/date.rs | 114 +++++++-------- src/civil/datetime.rs | 331 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 388 insertions(+), 57 deletions(-) diff --git a/src/civil/date.rs b/src/civil/date.rs index c817859..4094e4e 100644 --- a/src/civil/date.rs +++ b/src/civil/date.rs @@ -727,6 +727,58 @@ impl Date { is_leap_year(self.year_ranged()) } + /// Returns the date immediately following this one. + /// + /// # Errors + /// + /// This returns an error when this date is the maximum value. + /// + /// # Example + /// + /// ``` + /// use jiff::civil::Date; + /// + /// let d = Date::constant(2024, 2, 28); + /// assert_eq!(d.tomorrow()?, Date::constant(2024, 2, 29)); + /// + /// // The max doesn't have a tomorrow. + /// assert!(Date::MAX.tomorrow().is_err()); + /// + /// # Ok::<(), Box>(()) + /// ``` + #[inline] + pub fn tomorrow(self) -> Result { + let day = self.to_unix_epoch_days(); + let next = day.try_checked_add("days", C(1))?; + Ok(Date::from_unix_epoch_days(next)) + } + + /// Returns the date immediately preceding this one. + /// + /// # Errors + /// + /// This returns an error when this date is the minimum value. + /// + /// # Example + /// + /// ``` + /// use jiff::civil::Date; + /// + /// let d = Date::constant(2024, 3, 1); + /// assert_eq!(d.yesterday()?, Date::constant(2024, 2, 29)); + /// + /// // The min doesn't have a yesterday. + /// assert!(Date::MIN.yesterday().is_err()); + /// + /// # Ok::<(), Box>(()) + /// ``` + #[inline] + pub fn yesterday(self) -> Result { + let day = self.to_unix_epoch_days(); + let next = day.try_checked_sub("days", C(1))?; + Ok(Date::from_unix_epoch_days(next)) + } + /// Returns the "nth" weekday from the beginning or end of the month in /// which this date resides. /// @@ -842,8 +894,8 @@ impl Date { /// /// # Errors /// - /// This returns an error when `nth` is `0`, or if it would otherwise result - /// in a date that overflows the minimum/maximum values of `Date`. + /// This returns an error when `nth` is `0`, or if it would otherwise + /// result in a date that overflows the minimum/maximum values of `Date`. /// /// # Example /// @@ -915,9 +967,9 @@ impl Date { /// /// # Example: the start of Israeli summer time /// - /// Israeli law says (at present, 2024-03-11) that DST or "summer time" - /// starts on the Friday before the last Sunday in March. We can find that - /// date using both `nth_weekday` and [`Date::nth_weekday_of_month`]: + /// Israeli law says (at present, as of 2024-03-11) that DST or "summer + /// time" starts on the Friday before the last Sunday in March. We can find + /// that date using both `nth_weekday` and [`Date::nth_weekday_of_month`]: /// /// ``` /// use jiff::civil::{Date, Weekday}; @@ -997,58 +1049,6 @@ impl Date { } } - /// Returns the date immediately following this one. - /// - /// # Errors - /// - /// This returns an error when this date is the maximum value. - /// - /// # Example - /// - /// ``` - /// use jiff::civil::Date; - /// - /// let d = Date::constant(2024, 2, 28); - /// assert_eq!(d.tomorrow()?, Date::constant(2024, 2, 29)); - /// - /// // The max doesn't have a tomorrow. - /// assert!(Date::MAX.tomorrow().is_err()); - /// - /// # Ok::<(), Box>(()) - /// ``` - #[inline] - pub fn tomorrow(self) -> Result { - let day = self.to_unix_epoch_days(); - let next = day.try_checked_add("days", C(1))?; - Ok(Date::from_unix_epoch_days(next)) - } - - /// Returns the date immediately preceding this one. - /// - /// # Errors - /// - /// This returns an error when this date is the minimum value. - /// - /// # Example - /// - /// ``` - /// use jiff::civil::Date; - /// - /// let d = Date::constant(2024, 3, 1); - /// assert_eq!(d.yesterday()?, Date::constant(2024, 2, 29)); - /// - /// // The min doesn't have a yesterday. - /// assert!(Date::MIN.yesterday().is_err()); - /// - /// # Ok::<(), Box>(()) - /// ``` - #[inline] - pub fn yesterday(self) -> Result { - let day = self.to_unix_epoch_days(); - let next = day.try_checked_sub("days", C(1))?; - Ok(Date::from_unix_epoch_days(next)) - } - /// Construct an [ISO 8601 week date] from this Gregorian date. /// /// The [`ISOWeekDate`] type describes itself in more detail, but in diff --git a/src/civil/datetime.rs b/src/civil/datetime.rs index 2f60c40..1754f62 100644 --- a/src/civil/datetime.rs +++ b/src/civil/datetime.rs @@ -855,6 +855,289 @@ impl DateTime { self.date().in_leap_year() } + /// Returns the datetime with a date immediately following this one. + /// + /// The time in the datetime returned remains unchanged. + /// + /// # Errors + /// + /// This returns an error when this datetime's date is the maximum value. + /// + /// # Example + /// + /// ``` + /// use jiff::civil::{DateTime, date}; + /// + /// let dt = date(2024, 2, 28).at(7, 30, 0, 0); + /// assert_eq!(dt.tomorrow()?, date(2024, 2, 29).at(7, 30, 0, 0)); + /// + /// // The max doesn't have a tomorrow. + /// assert!(DateTime::MAX.tomorrow().is_err()); + /// + /// # Ok::<(), Box>(()) + /// ``` + #[inline] + pub fn tomorrow(self) -> Result { + Ok(DateTime::from_parts(self.date().tomorrow()?, self.time())) + } + + /// Returns the datetime with a date immediately preceding this one. + /// + /// The time in the datetime returned remains unchanged. + /// + /// # Errors + /// + /// This returns an error when this datetime's date is the minimum value. + /// + /// # Example + /// + /// ``` + /// use jiff::civil::{Date, date}; + /// + /// let dt = date(2024, 3, 1).at(7, 30, 0, 0); + /// assert_eq!(dt.yesterday()?, date(2024, 2, 29).at(7, 30, 0, 0)); + /// + /// // The min doesn't have a yesterday. + /// assert!(Date::MIN.yesterday().is_err()); + /// + /// # Ok::<(), Box>(()) + /// ``` + #[inline] + pub fn yesterday(self) -> Result { + Ok(DateTime::from_parts(self.date().yesterday()?, self.time())) + } + + /// Returns the "nth" weekday from the beginning or end of the month in + /// which this datetime resides. + /// + /// The `nth` parameter can be positive or negative. A positive value + /// computes the "nth" weekday from the beginning of the month. A negative + /// value computes the "nth" weekday from the end of the month. So for + /// example, use `-1` to "find the last weekday" in this date's month. + /// + /// The time in the datetime returned remains unchanged. + /// + /// # Errors + /// + /// This returns an error when `nth` is `0`, or if it is `5` or `-5` and + /// there is no 5th weekday from the beginning or end of the month. + /// + /// # Example + /// + /// This shows how to get the nth weekday in a month, starting from the + /// beginning of the month: + /// + /// ``` + /// use jiff::civil::{Weekday, date}; + /// + /// let dt = date(2017, 3, 1).at(7, 30, 0, 0); + /// let second_friday = dt.nth_weekday_of_month(2, Weekday::Friday)?; + /// assert_eq!(second_friday, date(2017, 3, 10).at(7, 30, 0, 0)); + /// + /// # Ok::<(), Box>(()) + /// ``` + /// + /// This shows how to do the reverse of the above. That is, the nth _last_ + /// weekday in a month: + /// + /// ``` + /// use jiff::civil::{Weekday, date}; + /// + /// let dt = date(2024, 3, 1).at(7, 30, 0, 0); + /// let last_thursday = dt.nth_weekday_of_month(-1, Weekday::Thursday)?; + /// assert_eq!(last_thursday, date(2024, 3, 28).at(7, 30, 0, 0)); + /// let second_last_thursday = dt.nth_weekday_of_month( + /// -2, + /// Weekday::Thursday, + /// )?; + /// assert_eq!(second_last_thursday, date(2024, 3, 21).at(7, 30, 0, 0)); + /// + /// # Ok::<(), Box>(()) + /// ``` + /// + /// This routine can return an error if there isn't an `nth` weekday + /// for this month. For example, March 2024 only has 4 Mondays: + /// + /// ``` + /// use jiff::civil::{Weekday, date}; + /// + /// let dt = date(2024, 3, 25).at(7, 30, 0, 0); + /// let fourth_monday = dt.nth_weekday_of_month(4, Weekday::Monday)?; + /// assert_eq!(fourth_monday, date(2024, 3, 25).at(7, 30, 0, 0)); + /// // There is no 5th Monday. + /// assert!(dt.nth_weekday_of_month(5, Weekday::Monday).is_err()); + /// // Same goes for counting backwards. + /// assert!(dt.nth_weekday_of_month(-5, Weekday::Monday).is_err()); + /// + /// # Ok::<(), Box>(()) + /// ``` + #[inline] + pub fn nth_weekday_of_month( + self, + nth: i8, + weekday: Weekday, + ) -> Result { + let date = self.date().nth_weekday_of_month(nth, weekday)?; + Ok(DateTime::from_parts(date, self.time())) + } + + /// Returns the "nth" weekday from this datetime, not including itself. + /// + /// The `nth` parameter can be positive or negative. A positive value + /// computes the "nth" weekday starting at the day after this date and + /// going forwards in time. A negative value computes the "nth" weekday + /// starting at the day before this date and going backwards in time. + /// + /// For example, if this date's weekday is a Sunday and the first Sunday is + /// asked for (that is, `date.nth_weekday(1, Weekday::Sunday)`), then the + /// result is a week from this date corresponding to the following Sunday. + /// + /// The time in the datetime returned remains unchanged. + /// + /// # Errors + /// + /// This returns an error when `nth` is `0`, or if it would otherwise + /// result in a date that overflows the minimum/maximum values of + /// `DateTime`. + /// + /// # Example + /// + /// This example shows how to find the "nth" weekday going forwards in + /// time: + /// + /// ``` + /// use jiff::civil::{Weekday, date}; + /// + /// // Use a Sunday in March as our start date. + /// let dt = date(2024, 3, 10).at(7, 30, 0, 0); + /// assert_eq!(dt.weekday(), Weekday::Sunday); + /// + /// // The first next Monday is tomorrow! + /// let next_monday = dt.nth_weekday(1, Weekday::Monday)?; + /// assert_eq!(next_monday, date(2024, 3, 11).at(7, 30, 0, 0)); + /// + /// // But the next Sunday is a week away, because this doesn't + /// // include the current weekday. + /// let next_sunday = dt.nth_weekday(1, Weekday::Sunday)?; + /// assert_eq!(next_sunday, date(2024, 3, 17).at(7, 30, 0, 0)); + /// + /// // "not this Thursday, but next Thursday" + /// let next_next_thursday = dt.nth_weekday(2, Weekday::Thursday)?; + /// assert_eq!(next_next_thursday, date(2024, 3, 21).at(7, 30, 0, 0)); + /// + /// # Ok::<(), Box>(()) + /// ``` + /// + /// This example shows how to find the "nth" weekday going backwards in + /// time: + /// + /// ``` + /// use jiff::civil::{Weekday, date}; + /// + /// // Use a Sunday in March as our start date. + /// let dt = date(2024, 3, 10).at(7, 30, 0, 0); + /// assert_eq!(dt.weekday(), Weekday::Sunday); + /// + /// // "last Saturday" was yesterday! + /// let last_saturday = dt.nth_weekday(-1, Weekday::Saturday)?; + /// assert_eq!(last_saturday, date(2024, 3, 9).at(7, 30, 0, 0)); + /// + /// // "last Sunday" was a week ago. + /// let last_sunday = dt.nth_weekday(-1, Weekday::Sunday)?; + /// assert_eq!(last_sunday, date(2024, 3, 3).at(7, 30, 0, 0)); + /// + /// // "not last Thursday, but the one before" + /// let prev_prev_thursday = dt.nth_weekday(-2, Weekday::Thursday)?; + /// assert_eq!(prev_prev_thursday, date(2024, 2, 29).at(7, 30, 0, 0)); + /// + /// # Ok::<(), Box>(()) + /// ``` + /// + /// This example shows that overflow results in an error in either + /// direction: + /// + /// ``` + /// use jiff::civil::{DateTime, Weekday}; + /// + /// let dt = DateTime::MAX; + /// assert_eq!(dt.weekday(), Weekday::Friday); + /// assert!(dt.nth_weekday(1, Weekday::Saturday).is_err()); + /// + /// let dt = DateTime::MIN; + /// assert_eq!(dt.weekday(), Weekday::Monday); + /// assert!(dt.nth_weekday(-1, Weekday::Sunday).is_err()); + /// ``` + /// + /// # Example: the start of Israeli summer time + /// + /// Israeli law says (at present, as of 2024-03-11) that DST or + /// "summer time" starts on the Friday before the last Sunday in + /// March. We can find that date using both `nth_weekday` and + /// [`DateTime::nth_weekday_of_month`]: + /// + /// ``` + /// use jiff::civil::{Weekday, date}; + /// + /// let march = date(2024, 3, 1).at(0, 0, 0, 0); + /// let last_sunday = march.nth_weekday_of_month(-1, Weekday::Sunday)?; + /// let dst_starts_on = last_sunday.nth_weekday(-1, Weekday::Friday)?; + /// assert_eq!(dst_starts_on, date(2024, 3, 29).at(0, 0, 0, 0)); + /// + /// # Ok::<(), Box>(()) + /// ``` + /// + /// # Example: getting the start of the week + /// + /// Given a date, one can use `nth_weekday` to determine the start of the + /// week in which the date resides in. This might vary based on whether + /// the weeks start on Sunday or Monday. This example shows how to handle + /// both. + /// + /// ``` + /// use jiff::civil::{Weekday, date}; + /// + /// let dt = date(2024, 3, 15).at(7, 30, 0, 0); + /// // For weeks starting with Sunday. + /// let start_of_week = dt.tomorrow()?.nth_weekday(-1, Weekday::Sunday)?; + /// assert_eq!(start_of_week, date(2024, 3, 10).at(7, 30, 0, 0)); + /// // For weeks starting with Monday. + /// let start_of_week = dt.tomorrow()?.nth_weekday(-1, Weekday::Monday)?; + /// assert_eq!(start_of_week, date(2024, 3, 11).at(7, 30, 0, 0)); + /// + /// # Ok::<(), Box>(()) + /// ``` + /// + /// In the above example, we first get the date after the current one + /// because `nth_weekday` does not consider itself when counting. This + /// works as expected even at the boundaries of a week: + /// + /// ``` + /// use jiff::civil::{Time, Weekday, date}; + /// + /// // The start of the week. + /// let dt = date(2024, 3, 10).at(0, 0, 0, 0); + /// let start_of_week = dt.tomorrow()?.nth_weekday(-1, Weekday::Sunday)?; + /// assert_eq!(start_of_week, date(2024, 3, 10).at(0, 0, 0, 0)); + /// // The end of the week. + /// let dt = date(2024, 3, 16).at(23, 59, 59, 999_999_999); + /// let start_of_week = dt + /// .tomorrow()? + /// .nth_weekday(-1, Weekday::Sunday)? + /// .with().time(Time::midnight()).build()?; + /// assert_eq!(start_of_week, date(2024, 3, 10).at(0, 0, 0, 0)); + /// + /// # Ok::<(), Box>(()) + /// ``` + #[inline] + pub fn nth_weekday( + self, + nth: i32, + weekday: Weekday, + ) -> Result { + let date = self.date().nth_weekday(nth, weekday)?; + Ok(DateTime::from_parts(date, self.time())) + } + /// Returns the date component of this datetime. /// /// # Example @@ -2896,6 +3179,54 @@ impl DateTimeWith { Ok(DateTime::from_parts(date, time)) } + /// Set the year, month and day fields via the `Date` given. + /// + /// This overrides any previous year, month or day settings. + /// + /// # Example + /// + /// This shows how to create a new datetime with a different date: + /// + /// ``` + /// use jiff::civil::date; + /// + /// let dt1 = date(2005, 11, 5).at(15, 30, 0, 0); + /// let dt2 = dt1.with().date(date(2017, 10, 31)).build()?; + /// // The date changes but the time remains the same. + /// assert_eq!(dt2, date(2017, 10, 31).at(15, 30, 0, 0)); + /// + /// # Ok::<(), Box>(()) + /// ``` + #[inline] + pub fn date(self, date: Date) -> DateTimeWith { + DateTimeWith { date_with: date.with(), ..self } + } + + /// Set the hour, minute, second, millisecond, microsecond and nanosecond + /// fields via the `Time` given. + /// + /// This overrides any previous hour, minute, second, millisecond, + /// microsecond, nanosecond or subsecond nanosecond settings. + /// + /// # Example + /// + /// This shows how to create a new datetime with a different time: + /// + /// ``` + /// use jiff::civil::{date, time}; + /// + /// let dt1 = date(2005, 11, 5).at(15, 30, 0, 0); + /// let dt2 = dt1.with().time(time(23, 59, 59, 123_456_789)).build()?; + /// // The time changes but the date remains the same. + /// assert_eq!(dt2, date(2005, 11, 5).at(23, 59, 59, 123_456_789)); + /// + /// # Ok::<(), Box>(()) + /// ``` + #[inline] + pub fn time(self, time: Time) -> DateTimeWith { + DateTimeWith { time_with: time.with(), ..self } + } + /// Set the year field on a [`DateTime`]. /// /// One can access this value via [`DateTime::year`].