1use crate::date::{CivilDateTime, Weekday};
9
10#[cfg(test)]
11mod tests;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15#[non_exhaustive]
16pub enum DayOfMonthModifier {
17 Last,
19 LastOffset {
21 days: u8,
23 },
24 LastWeekday,
26 NearestWeekday {
33 day: u8,
35 },
36}
37
38impl DayOfMonthModifier {
39 #[must_use]
41 pub fn matches(self, date: &CivilDateTime) -> bool {
42 let last = date.days_in_month();
43 match self {
44 Self::Last => date.day() == last,
45 Self::LastOffset { days } => match last.checked_sub(days) {
46 Some(target) if target >= 1 => date.day() == target,
47 _ => false,
51 },
52 Self::LastWeekday => match last_weekday_of_month(date) {
53 Some(target) => date.day() == target,
54 None => false,
55 },
56 Self::NearestWeekday { day } => match nearest_weekday(date, day) {
57 Some(target) => date.day() == target,
58 None => false,
59 },
60 }
61 }
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
66#[non_exhaustive]
67pub enum DayOfWeekModifier {
68 Last {
70 weekday: Weekday,
72 },
73 Nth {
75 weekday: Weekday,
77 nth: u8,
79 },
80}
81
82impl DayOfWeekModifier {
83 #[must_use]
85 #[inline(always)]
86 pub fn matches(self, date: &CivilDateTime) -> bool {
87 match self {
88 Self::Last { weekday } => {
89 date.weekday() == weekday && date.day().saturating_add(7) > date.days_in_month()
90 }
91 Self::Nth { weekday, nth } => {
92 date.weekday() == weekday && (date.day().saturating_sub(1) / 7).saturating_add(1) == nth
93 }
94 }
95 }
96}
97
98fn last_weekday_of_month(date: &CivilDateTime) -> Option<u8> {
100 let mut day = date.days_in_month();
101 while day >= 1 {
103 if date.weekday_of_day(day).is_weekday() {
104 return Some(day);
105 }
106 day = day.checked_sub(1)?;
107 }
108 None
109}
110
111fn nearest_weekday(date: &CivilDateTime, target: u8) -> Option<u8> {
113 let last = date.days_in_month();
114 if target < 1 || target > last {
115 return None;
116 }
117
118 match date.weekday_of_day(target) {
119 Weekday::Saturday => {
120 let back = target.checked_sub(1)?;
121 if back >= 1 {
124 Some(back)
125 } else {
126 let forward = target.checked_add(2)?;
127 (forward <= last).then_some(forward)
128 }
129 }
130 Weekday::Sunday => {
131 let forward = target.checked_add(1)?;
132 if forward <= last {
133 Some(forward)
134 } else {
135 let back = target.checked_sub(2)?;
136 (back >= 1).then_some(back)
137 }
138 }
139 _ => Some(target),
140 }
141}