twilight_model/guild/scheduled_event/recurrence_rule.rs
1//! Definitions for how often [`GuildScheduledEvent`]s should recur.
2//!
3//! Discord's recurrence rule is a subset of the behaviors defined in the
4//! [iCalendar RFC] and implemented by [python's dateutil rrule].
5//!
6//! # Limitations
7//!
8//! Discord's implementation of recurrence rules has a number of limitations
9//! which are not set in stone. Examples of possible limitations are mutually
10//! exclusive fields or fields that can't be set by applications.
11//!
12//! Refer to [Discord Docs/System Limitations] for up-to-date information on
13//! these limitations.
14//!
15//! [Discord Docs/System Limitations]: https://docs.discord.com/developers/resources/guild-scheduled-event#system-limitations
16//! [`GuildScheduledEvent`]: super::GuildScheduledEvent
17//! [iCalendar RFC]: https://datatracker.ietf.org/doc/html/rfc5545
18//! [python's dateutil rrule]: https://dateutil.readthedocs.io/en/stable/rrule.html
19
20use crate::util::Timestamp;
21use serde::{Deserialize, Serialize};
22
23/// Definition for how often a [`GuildScheduledEvent`] should recur.
24///
25/// Refer to the module-level documentation for more information.
26///
27/// [`GuildScheduledEvent`]: super::GuildScheduledEvent
28#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
29pub struct RecurrenceRule {
30 /// Set of specific months to recur on.
31 pub by_month: Option<Vec<RecurrenceRuleMonth>>,
32 /// Set of specific dates within a month to recur on.
33 pub by_month_day: Option<Vec<u8>>,
34 /// List of specific days within a specific week (1 through 5, inclusively)
35 /// to recur on.
36 pub by_n_weekday: Option<Vec<RecurrenceRuleNWeekday>>,
37 /// Set of specific days within a week for the event to recur on.
38 pub by_weekday: Option<Vec<RecurrenceRuleWeekday>>,
39 /// Set of days within a year to recur on.
40 ///
41 /// Valid values are in the range 1 through 364, inclusively.
42 ///
43 /// This field is currently not able to be set externally.
44 pub by_year_day: Option<Vec<u16>>,
45 /// Total amount of times that the event is allowed to recur before
46 /// stopping.
47 ///
48 /// This field is currently not able to be set externally.
49 pub count: Option<u64>,
50 /// Ending time of the recurrence interval.
51 ///
52 /// This field is currently not able to be set externally.
53 pub end: Option<Timestamp>,
54 /// How often the event occurs.
55 pub frequency: RecurrenceRuleFrequency,
56 /// Spacing between the events, defined by frequency. For example,
57 /// [`frequency`][Self::frequency] of
58 /// [weekly][RecurrenceRuleFrequency::Weekly] and an interval of 2 would be
59 /// "every-other week".
60 pub interval: u64,
61 /// Starting time of the recurrence interval.
62 pub start: Timestamp,
63}
64
65/// How often the event recurs.
66///
67/// The numerical representation of this is 0-indexed, with value 0 being
68/// [`Yearly`][Self::Yearly].
69#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
70#[non_exhaustive]
71#[serde(from = "u8", into = "u8")]
72pub enum RecurrenceRuleFrequency {
73 /// Recurrence frequency is daily.
74 Daily,
75 /// Recurrence frequency is weekly.
76 Weekly,
77 /// Recurrence frequency is monthly.
78 Monthly,
79 /// Recurrence frequency is yearly.
80 Yearly,
81 /// Recurrence frequency is an unknown value.
82 Unknown(u8),
83}
84
85impl From<u8> for RecurrenceRuleFrequency {
86 fn from(value: u8) -> Self {
87 match value {
88 0 => Self::Yearly,
89 1 => Self::Monthly,
90 2 => Self::Weekly,
91 3 => Self::Daily,
92 unknown => Self::Unknown(unknown),
93 }
94 }
95}
96
97impl From<RecurrenceRuleFrequency> for u8 {
98 fn from(value: RecurrenceRuleFrequency) -> Self {
99 match value {
100 RecurrenceRuleFrequency::Daily => 3,
101 RecurrenceRuleFrequency::Weekly => 2,
102 RecurrenceRuleFrequency::Monthly => 1,
103 RecurrenceRuleFrequency::Yearly => 0,
104 RecurrenceRuleFrequency::Unknown(unknown) => unknown,
105 }
106 }
107}
108
109/// Month to recur on.
110///
111/// The numerical representation of this is 1-indexed, with value 1 being
112/// [`January`][Self::January].
113#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
114#[non_exhaustive]
115#[serde(from = "u8", into = "u8")]
116pub enum RecurrenceRuleMonth {
117 /// First month of the year.
118 January,
119 /// Second month of the year.
120 February,
121 /// Third month of the year.
122 March,
123 /// Fourth month of the year.
124 April,
125 /// Fifth month of the year.
126 May,
127 /// Sixth month of the year.
128 June,
129 /// Seventh month of the year.
130 July,
131 /// Eighth month of the year.
132 August,
133 /// Ninth month of the year.
134 September,
135 /// Tenth month of the year.
136 October,
137 /// Eleventh month of the year.
138 November,
139 /// Twelfth month of the year.
140 December,
141}
142
143impl From<u8> for RecurrenceRuleMonth {
144 /// Convert from a numerical representation to a defined month.
145 ///
146 /// # Panics
147 ///
148 /// Panics if the value is invalid; that is, 0 or greater than 12.
149 fn from(value: u8) -> Self {
150 match value {
151 1 => Self::January,
152 2 => Self::February,
153 3 => Self::March,
154 4 => Self::April,
155 5 => Self::May,
156 6 => Self::June,
157 7 => Self::July,
158 8 => Self::August,
159 9 => Self::September,
160 10 => Self::October,
161 11 => Self::November,
162 12 => Self::December,
163 other => {
164 // It all started on the 13th hour of the 13th day of the 13th
165 // month. We were there to discuss the misprinted calendars the
166 // school had purchased.
167 panic!("unknown recurrence rule month: {other}");
168 }
169 }
170 }
171}
172
173impl From<RecurrenceRuleMonth> for u8 {
174 fn from(value: RecurrenceRuleMonth) -> Self {
175 match value {
176 RecurrenceRuleMonth::January => 1,
177 RecurrenceRuleMonth::February => 2,
178 RecurrenceRuleMonth::March => 3,
179 RecurrenceRuleMonth::April => 4,
180 RecurrenceRuleMonth::May => 5,
181 RecurrenceRuleMonth::June => 6,
182 RecurrenceRuleMonth::July => 7,
183 RecurrenceRuleMonth::August => 8,
184 RecurrenceRuleMonth::September => 9,
185 RecurrenceRuleMonth::October => 10,
186 RecurrenceRuleMonth::November => 11,
187 RecurrenceRuleMonth::December => 12,
188 }
189 }
190}
191/// Weekday to recur on.
192///
193/// The numerical representation of this is 0-indexed, with value 0 being
194/// [`Monday`][Self::Monday].
195#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
196#[non_exhaustive]
197#[serde(from = "u8", into = "u8")]
198pub enum RecurrenceRuleWeekday {
199 Monday,
200 Tuesday,
201 Wednesday,
202 Thursday,
203 Friday,
204 Saturday,
205 Sunday,
206}
207
208impl From<u8> for RecurrenceRuleWeekday {
209 fn from(value: u8) -> Self {
210 match value {
211 0 => Self::Monday,
212 1 => Self::Tuesday,
213 2 => Self::Wednesday,
214 3 => Self::Thursday,
215 4 => Self::Friday,
216 5 => Self::Saturday,
217 6 => Self::Sunday,
218 other => {
219 // We're unlikely to move away from the Gregorian calendar soon.
220 panic!("unknown recurrence rule weekday: {other}");
221 }
222 }
223 }
224}
225
226impl From<RecurrenceRuleWeekday> for u8 {
227 fn from(value: RecurrenceRuleWeekday) -> Self {
228 match value {
229 RecurrenceRuleWeekday::Monday => 0,
230 RecurrenceRuleWeekday::Tuesday => 1,
231 RecurrenceRuleWeekday::Wednesday => 2,
232 RecurrenceRuleWeekday::Thursday => 3,
233 RecurrenceRuleWeekday::Friday => 4,
234 RecurrenceRuleWeekday::Saturday => 5,
235 RecurrenceRuleWeekday::Sunday => 6,
236 }
237 }
238}
239
240#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
241pub struct RecurrenceRuleNWeekday {
242 /// Day within the week to reoccur on.
243 pub day: RecurrenceRuleWeekday,
244 /// Week to reoccur on.
245 ///
246 /// Must be a value between 1 through 5, inclusively.
247 pub n: u8,
248}
249
250#[cfg(test)]
251mod tests {
252 use super::{RecurrenceRuleMonth, RecurrenceRuleWeekday};
253
254 /// Test that `RecurrenceRuleMonth` is 1-indexed and converting from 0
255 /// panics.
256 #[should_panic(expected = "unknown recurrence rule month: 0")]
257 #[test]
258 fn month_one_indexed() {
259 let _ = RecurrenceRuleMonth::from(0u8);
260 }
261
262 /// Test that `RecurrenceRuleMonth` does not have 13 months.
263 #[should_panic(expected = "unknown recurrence rule month: 13")]
264 #[test]
265 fn month_thirteen_invalid() {
266 let _ = RecurrenceRuleMonth::from(13u8);
267 }
268
269 /// Test that `RecurrenceRuleWeekday` does not have 8 days.
270 #[should_panic(expected = "unknown recurrence rule weekday: 7")]
271 #[test]
272 fn weekday_eight_invalid() {
273 let _ = RecurrenceRuleWeekday::from(7u8);
274 }
275}