Skip to content

Commit

Permalink
Calculate next/prev weekday occurrences (#594)
Browse files Browse the repository at this point in the history
  • Loading branch information
HubertK05 authored Jul 8, 2023
1 parent c435649 commit 133c3a9
Show file tree
Hide file tree
Showing 2 changed files with 242 additions and 0 deletions.
74 changes: 74 additions & 0 deletions tests/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -982,3 +982,77 @@ fn replace_day() {
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
}

#[test]
fn next_occurrence_test() {
assert_eq!(date!(2023 - 06 - 25).next_occurrence(Weekday::Monday), date!(2023 - 06 - 26));
assert_eq!(date!(2023 - 06 - 26).next_occurrence(Weekday::Monday), date!(2023 - 07 - 03));
assert_eq!(date!(2023 - 06 - 27).next_occurrence(Weekday::Monday), date!(2023 - 07 - 03));
assert_eq!(date!(2023 - 06 - 28).next_occurrence(Weekday::Monday), date!(2023 - 07 - 03));
assert_eq!(date!(2023 - 06 - 29).next_occurrence(Weekday::Monday), date!(2023 - 07 - 03));
assert_eq!(date!(2023 - 06 - 30).next_occurrence(Weekday::Monday), date!(2023 - 07 - 03));
assert_eq!(date!(2023 - 07 - 01).next_occurrence(Weekday::Monday), date!(2023 - 07 - 03));
assert_eq!(date!(2023 - 07 - 02).next_occurrence(Weekday::Monday), date!(2023 - 07 - 03));
assert_eq!(date!(2023 - 07 - 03).next_occurrence(Weekday::Monday), date!(2023 - 07 - 10));
}

#[test]
fn prev_occurrence_test() {
assert_eq!(date!(2023 - 07 - 07).prev_occurrence(Weekday::Thursday), date!(2023 - 07 - 06));
assert_eq!(date!(2023 - 07 - 06).prev_occurrence(Weekday::Thursday), date!(2023 - 06 - 29));
assert_eq!(date!(2023 - 07 - 05).prev_occurrence(Weekday::Thursday), date!(2023 - 06 - 29));
assert_eq!(date!(2023 - 07 - 04).prev_occurrence(Weekday::Thursday), date!(2023 - 06 - 29));
assert_eq!(date!(2023 - 07 - 03).prev_occurrence(Weekday::Thursday), date!(2023 - 06 - 29));
assert_eq!(date!(2023 - 07 - 02).prev_occurrence(Weekday::Thursday), date!(2023 - 06 - 29));
assert_eq!(date!(2023 - 07 - 01).prev_occurrence(Weekday::Thursday), date!(2023 - 06 - 29));
assert_eq!(date!(2023 - 06 - 30).prev_occurrence(Weekday::Thursday), date!(2023 - 06 - 29));
assert_eq!(date!(2023 - 06 - 29).prev_occurrence(Weekday::Thursday), date!(2023 - 06 - 22));
}

#[test]
fn nth_next_occurrence_test() {
assert_eq!(date!(2023 - 06 - 25).nth_next_occurrence(Weekday::Monday, 5), date!(2023 - 07 - 24));
assert_eq!(date!(2023 - 06 - 26).nth_next_occurrence(Weekday::Monday, 5), date!(2023 - 07 - 31));
}

#[test]
fn nth_prev_occurrence_test() {
assert_eq!(date!(2023 - 06 - 27).nth_prev_occurrence(Weekday::Monday, 3), date!(2023 - 06 - 12));
assert_eq!(date!(2023 - 06 - 26).nth_prev_occurrence(Weekday::Monday, 3), date!(2023 - 06 - 05));
}

#[test]
#[should_panic]
fn next_occurrence_overflow_test() {
date!(+999999 - 12 - 25).next_occurrence(Weekday::Saturday);
}

#[test]
#[should_panic]
fn prev_occurrence_overflow_test() {
date!(-999999 - 01 - 07).prev_occurrence(Weekday::Sunday);
}

#[test]
#[should_panic]
fn nth_next_occurrence_overflow_test() {
date!(+999999 - 12 - 25).nth_next_occurrence(Weekday::Saturday, 1);
}

#[test]
#[should_panic]
fn nth_next_occurence_zeroth_occurence_test() {
date!(2023 - 06 - 25).nth_next_occurrence(Weekday::Monday, 0);
}

#[test]
#[should_panic]
fn nth_prev_occurence_zeroth_occurence_test() {
date!(2023 - 06 - 27).nth_prev_occurrence(Weekday::Monday, 0);
}

#[test]
#[should_panic]
fn nth_prev_occurrence_overflow_test() {
date!(-999999 - 01 - 07).nth_prev_occurrence(Weekday::Sunday, 1);
}
168 changes: 168 additions & 0 deletions time/src/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,106 @@ impl Date {
}
}

/// Calculates the first occurrence of a weekday that is strictly later than a given `Date`.
///
/// # Panics
/// Panics if an overflow occurred.
///
/// # Examples
/// ```
/// # use time::Weekday;
/// # use time_macros::date;
/// assert_eq!(
/// date!(2023 - 06 - 28).next_occurrence(Weekday::Monday),
/// date!(2023 - 07 - 03)
/// );
/// assert_eq!(
/// date!(2023 - 06 - 19).next_occurrence(Weekday::Monday),
/// date!(2023 - 06 - 26)
/// );
/// ```
pub const fn next_occurrence(self, weekday: Weekday) -> Self {
expect_opt!(
self.checked_next_occurrence(weekday),
"overflow calculating the next occurrence of a weekday"
)
}

/// Calculates the first occurrence of a weekday that is strictly earlier than a given `Date`.
///
/// # Panics
/// Panics if an overflow occurred.
///
/// # Examples
/// ```
/// # use time::Weekday;
/// # use time_macros::date;
/// assert_eq!(
/// date!(2023 - 06 - 28).prev_occurrence(Weekday::Monday),
/// date!(2023 - 06 - 26)
/// );
/// assert_eq!(
/// date!(2023 - 06 - 19).prev_occurrence(Weekday::Monday),
/// date!(2023 - 06 - 12)
/// );
/// ```
pub const fn prev_occurrence(self, weekday: Weekday) -> Self {
expect_opt!(
self.checked_prev_occurrence(weekday),
"overflow calculating the previous occurrence of a weekday"
)
}

/// Calculates the `n`th occurrence of a weekday that is strictly later than a given `Date`.
///
/// # Panics
/// Panics if an overflow occurred or if `n == 0`.
///
/// # Examples
/// ```
/// # use time::Weekday;
/// # use time_macros::date;
/// assert_eq!(
/// date!(2023 - 06 - 25).nth_next_occurrence(Weekday::Monday, 5),
/// date!(2023 - 07 - 24)
/// );
/// assert_eq!(
/// date!(2023 - 06 - 26).nth_next_occurrence(Weekday::Monday, 5),
/// date!(2023 - 07 - 31)
/// );
/// ```
pub const fn nth_next_occurrence(self, weekday: Weekday, n: u8) -> Self {
expect_opt!(
self.checked_nth_next_occurrence(weekday, n),
"overflow calculating the next occurrence of a weekday"
)
}

/// Calculates the `n`th occurrence of a weekday that is strictly earlier than a given `Date`.
///
/// # Panics
/// Panics if an overflow occurred or if `n == 0`.
///
/// # Examples
/// ```
/// # use time::Weekday;
/// # use time_macros::date;
/// assert_eq!(
/// date!(2023 - 06 - 27).nth_prev_occurrence(Weekday::Monday, 3),
/// date!(2023 - 06 - 12)
/// );
/// assert_eq!(
/// date!(2023 - 06 - 26).nth_prev_occurrence(Weekday::Monday, 3),
/// date!(2023 - 06 - 05)
/// );
/// ```
pub const fn nth_prev_occurrence(self, weekday: Weekday, n: u8) -> Self {
expect_opt!(
self.checked_nth_prev_occurrence(weekday, n),
"overflow calculating the previous occurrence of a weekday"
)
}

/// Get the Julian day for the date.
///
/// The algorithm to perform this conversion is derived from one provided by Peter Baum; it is
Expand Down Expand Up @@ -641,6 +741,74 @@ impl Date {
None
}
}

/// Calculates the first occurrence of a weekday that is strictly later than a given `Date`.
/// Returns `None` if an overflow occurred.
pub(crate) const fn checked_next_occurrence(self, weekday: Weekday) -> Option<Self> {
let day_diff = match weekday as i8 - self.weekday() as i8 {
1 | -6 => 1,
2 | -5 => 2,
3 | -4 => 3,
4 | -3 => 4,
5 | -2 => 5,
6 | -1 => 6,
val => {
debug_assert!(val == 0);
7
}
};

self.checked_add(Duration::days(day_diff))
}

/// Calculates the first occurrence of a weekday that is strictly earlier than a given `Date`.
/// Returns `None` if an overflow occurred.
pub(crate) const fn checked_prev_occurrence(self, weekday: Weekday) -> Option<Self> {
let day_diff = match weekday as i8 - self.weekday() as i8 {
1 | -6 => 6,
2 | -5 => 5,
3 | -4 => 4,
4 | -3 => 3,
5 | -2 => 2,
6 | -1 => 1,
val => {
debug_assert!(val == 0);
7
}
};

self.checked_sub(Duration::days(day_diff))
}

/// Calculates the `n`th occurrence of a weekday that is strictly later than a given `Date`.
/// Returns `None` if an overflow occurred or if `n == 0`.
pub(crate) const fn checked_nth_next_occurrence(self, weekday: Weekday, n: u8) -> Option<Self> {
if n == 0 {
return None;
}

let next_occ = self.checked_next_occurrence(weekday);
if let Some(val) = next_occ {
val.checked_add(Duration::weeks(n as i64 - 1))
} else {
None
}
}

/// Calculates the `n`th occurrence of a weekday that is strictly earlier than a given `Date`.
/// Returns `None` if an overflow occurred or if `n == 0`.
pub(crate) const fn checked_nth_prev_occurrence(self, weekday: Weekday, n: u8) -> Option<Self> {
if n == 0 {
return None;
}

let next_occ = self.checked_prev_occurrence(weekday);
if let Some(val) = next_occ {
val.checked_sub(Duration::weeks(n as i64 - 1))
} else {
None
}
}
// endregion: checked arithmetic

// region: saturating arithmetic
Expand Down

0 comments on commit 133c3a9

Please sign in to comment.