Skip to main content

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}