Skip to main content

rfc5545_types/
rrule.rs

1//! Model types for recurrence rules.
2
3use std::{collections::BTreeSet, fmt::Debug, num::NonZero};
4
5use weekday_num_set::WeekdayNumSet;
6
7use calendar_types::{
8    primitive::Sign,
9    time::{IsoWeek, Month, Weekday},
10};
11
12use crate::time::DateTimeOrDate;
13
14// TODO: implement another mixed representation set module for
15// year_day_num
16
17pub mod weekday_num_set;
18
19/// A recurrence rule (RFC 5545 §3.3.10).
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct RRule {
22    /// The [`Freq`] value together with the BYxxx rules it allows.
23    pub freq: FreqByRules,
24    /// The BYxxx rules which do not depend on the [`Freq`] value.
25    pub core_by_rules: CoreByRules,
26    /// The INTERVAL part.
27    pub interval: Option<Interval>,
28    /// The COUNT or UNTIL part.
29    pub termination: Option<Termination>,
30    /// The WKST part.
31    pub week_start: Option<Weekday>,
32}
33
34/// The termination condition for a recurrence rule: either a count or an until date.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum Termination {
37    /// End after a fixed number of occurrences.
38    Count(u64),
39    /// End at or before a specific date or datetime.
40    Until(DateTimeOrDate),
41}
42
43/// The value of the INTERVAL rule part.
44#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
45pub struct Interval(pub(crate) NonZero<u64>);
46
47impl Interval {
48    /// Constructs an `Interval` from a non-zero value.
49    pub const fn new(value: NonZero<u64>) -> Self {
50        Self(value)
51    }
52
53    /// Returns the interval value.
54    pub const fn get(self) -> NonZero<u64> {
55        self.0
56    }
57}
58
59impl Default for Interval {
60    fn default() -> Self {
61        Self(std::num::NonZeroU64::MIN)
62    }
63}
64
65/// The frequency of a recurrence rule.
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum Freq {
68    Secondly,
69    Minutely,
70    Hourly,
71    Daily,
72    Weekly,
73    Monthly,
74    Yearly,
75}
76
77impl From<&FreqByRules> for Freq {
78    fn from(value: &FreqByRules) -> Self {
79        match value {
80            FreqByRules::Secondly(_) => Self::Secondly,
81            FreqByRules::Minutely(_) => Self::Minutely,
82            FreqByRules::Hourly(_) => Self::Hourly,
83            FreqByRules::Daily(_) => Self::Daily,
84            FreqByRules::Weekly => Self::Weekly,
85            FreqByRules::Monthly(_) => Self::Monthly,
86            FreqByRules::Yearly(_) => Self::Yearly,
87        }
88    }
89}
90
91/// The [`Freq`] value together with the frequency-dependent BYxxx rules it permits.
92///
93/// This enum enforces at the type level that each frequency only carries the BYxxx rules
94/// allowed by RFC 5545 (page 44).
95#[derive(Debug, Clone, PartialEq, Eq)]
96pub enum FreqByRules {
97    /// SECONDLY frequency with BYMONTHDAY and BYYEARDAY rules.
98    Secondly(ByPeriodDayRules),
99    /// MINUTELY frequency with BYMONTHDAY and BYYEARDAY rules.
100    Minutely(ByPeriodDayRules),
101    /// HOURLY frequency with BYMONTHDAY and BYYEARDAY rules.
102    Hourly(ByPeriodDayRules),
103    /// DAILY frequency with BYMONTHDAY rule.
104    Daily(ByMonthDayRule),
105    /// WEEKLY frequency (no frequency-dependent BYxxx rules).
106    Weekly,
107    /// MONTHLY frequency with BYMONTHDAY rule.
108    Monthly(ByMonthDayRule),
109    /// YEARLY frequency with BYMONTHDAY, BYYEARDAY, and BYWEEKNO rules.
110    Yearly(YearlyByRules),
111}
112
113/// The BYxxx rules which are permitted for any [`Freq`].
114#[derive(Debug, Default, Clone, PartialEq, Eq)]
115pub struct CoreByRules {
116    /// The BYSECOND rule.
117    pub by_second: Option<SecondSet>,
118    /// The BYMINUTE rule.
119    pub by_minute: Option<MinuteSet>,
120    /// The BYHOUR rule.
121    pub by_hour: Option<HourSet>,
122    /// The BYMONTH rule.
123    pub by_month: Option<MonthSet>,
124    /// The BYDAY rule.
125    pub by_day: Option<WeekdayNumSet>,
126    /// The BYSETPOS rule.
127    pub by_set_pos: Option<BTreeSet<YearDayNum>>,
128}
129
130/// The BYYEARDAY and BYMONTHDAY rules.
131#[derive(Debug, Clone, PartialEq, Eq)]
132pub struct ByPeriodDayRules {
133    /// The BYMONTHDAY rule.
134    pub by_month_day: Option<MonthDaySet>,
135    /// The BYYEARDAY rule.
136    pub by_year_day: Option<BTreeSet<YearDayNum>>,
137}
138
139/// The BYMONTHDAY rule.
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141pub struct ByMonthDayRule {
142    /// The BYMONTHDAY rule.
143    pub by_month_day: Option<MonthDaySet>,
144}
145
146/// The BYWEEKNO, BYYEARDAY, and BYMONTHDAY rules.
147#[derive(Debug, Default, Clone, PartialEq, Eq)]
148pub struct YearlyByRules {
149    /// The BYMONTHDAY rule.
150    pub by_month_day: Option<MonthDaySet>,
151    /// The BYYEARDAY rule.
152    pub by_year_day: Option<BTreeSet<YearDayNum>>,
153    /// The BYWEEKNO rule.
154    pub by_week_no: Option<WeekNoSet>,
155}
156
157/// A signed year of the day, i.e. the range -366..=366 not including 0.
158#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
159pub struct YearDayNum(NonZero<i16>);
160
161impl YearDayNum {
162    /// Returns the signed year-day value (-366..=-1 or 1..=366).
163    pub const fn get(self) -> i16 {
164        self.0.get()
165    }
166
167    /// Creates a `YearDayNum` from a sign and a 1-based day index (1–366).
168    pub const fn from_signed_index(sign: Sign, index: u16) -> Option<Self> {
169        match index {
170            1..=366 => {
171                let value = (index as i16) * (sign as i16);
172
173                // SAFETY: index is certainly non-zero, and sign is ±1; hence
174                // their product cannot be zero (nor can it overflow to zero).
175                Some(Self(unsafe { NonZero::new_unchecked(value) }))
176            }
177            _ => None,
178        }
179    }
180}
181
182/// A value corresponding to the `weekdaynum` grammar rule.
183#[derive(Clone, Copy, PartialEq, Eq, Hash)]
184pub struct WeekdayNum {
185    /// The optional signed ordinal (e.g. +2 for "second", -1 for "last").
186    pub ordinal: Option<(Sign, IsoWeek)>,
187    /// The day of the week.
188    pub weekday: Weekday,
189}
190
191impl Debug for WeekdayNum {
192    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193        if let Some((sign, week)) = self.ordinal {
194            match sign {
195                Sign::Pos => write!(f, "+"),
196                Sign::Neg => write!(f, "-"),
197            }?;
198
199            let w = week as u8;
200            write!(f, "{w:02}")?;
201        }
202
203        write!(
204            f,
205            "{}",
206            match self.weekday {
207                Weekday::Monday => "MO",
208                Weekday::Tuesday => "TU",
209                Weekday::Wednesday => "WE",
210                Weekday::Thursday => "TH",
211                Weekday::Friday => "FR",
212                Weekday::Saturday => "SA",
213                Weekday::Sunday => "SU",
214            }
215        )
216    }
217}
218
219impl PartialOrd for WeekdayNum {
220    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
221        Some(self.cmp(other))
222    }
223}
224
225impl Ord for WeekdayNum {
226    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
227        // the product of (sign as i16) with (week as i16) is a number in
228        // the range from -53 to +53 (not including zero), so i16::MIN is
229        // certainly less than the entire range
230
231        let lhs = self
232            .ordinal
233            .map(|(sign, week)| (sign as i16) * (week as i16))
234            .unwrap_or(i16::MIN);
235
236        let rhs = other
237            .ordinal
238            .map(|(sign, week)| (sign as i16) * (week as i16))
239            .unwrap_or(i16::MIN);
240
241        (lhs, self.weekday).cmp(&(rhs, other.weekday))
242    }
243}
244
245/// A bitset of values from 0 through 60.
246///
247/// ```text
248/// 0                                                          60
249/// |                                                           |
250/// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx001 (0-63)
251///                                                                |
252///                                                               msb
253/// ```
254#[derive(Clone, Copy, PartialEq, Eq, Hash)]
255pub struct SecondSet(NonZero<u64>);
256
257impl Debug for SecondSet {
258    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
259        let mut set = BTreeSet::new();
260
261        for second in Second::iter() {
262            if self.get(second) {
263                set.insert(second);
264            }
265        }
266
267        write!(f, "{set:#?}")
268    }
269}
270
271impl SecondSet {
272    pub(crate) const EMPTY: Self = Self(NonZero::new(1 << 63).unwrap());
273
274    /// Returns `true` if `second` is in this set.
275    pub const fn get(&self, second: Second) -> bool {
276        let mask = 1 << (second as u8);
277        (self.0.get() & mask) != 0
278    }
279
280    /// Inserts `second` into this set.
281    pub const fn set(&mut self, second: Second) {
282        let mask = 1 << (second as u8);
283        let updated = self.0.get() | mask;
284
285        // SAFETY: bitwise OR cannot reduce the number of set bits
286        *self = Self(unsafe { NonZero::new_unchecked(updated) })
287    }
288}
289
290impl Default for SecondSet {
291    fn default() -> Self {
292        Self::EMPTY
293    }
294}
295
296/// A second (ℤ mod 61), ranging from S0 through S60.
297#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
298#[repr(u8)]
299pub enum Second {
300    S0,
301    S1,
302    S2,
303    S3,
304    S4,
305    S5,
306    S6,
307    S7,
308    S8,
309    S9,
310    S10,
311    S11,
312    S12,
313    S13,
314    S14,
315    S15,
316    S16,
317    S17,
318    S18,
319    S19,
320    S20,
321    S21,
322    S22,
323    S23,
324    S24,
325    S25,
326    S26,
327    S27,
328    S28,
329    S29,
330    S30,
331    S31,
332    S32,
333    S33,
334    S34,
335    S35,
336    S36,
337    S37,
338    S38,
339    S39,
340    S40,
341    S41,
342    S42,
343    S43,
344    S44,
345    S45,
346    S46,
347    S47,
348    S48,
349    S49,
350    S50,
351    S51,
352    S52,
353    S53,
354    S54,
355    S55,
356    S56,
357    S57,
358    S58,
359    S59,
360    S60,
361}
362
363impl Debug for Second {
364    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
365        let s = *self as u8;
366        write!(f, "{s:02}")
367    }
368}
369
370impl Second {
371    /// Converts a `u8` discriminant (0–60) to a `Second`.
372    pub const fn from_repr(repr: u8) -> Option<Self> {
373        match repr {
374            0..=60 => {
375                // SAFETY: the valid discriminants of Self are exactly the
376                // values of the range 0..=60.
377                Some(unsafe { std::mem::transmute::<u8, Self>(repr) })
378            }
379            _ => None,
380        }
381    }
382
383    /// Returns an iterator over all 61 second values (S0 through S60).
384    pub fn iter() -> impl ExactSizeIterator<Item = Self> {
385        (0..=60u8).map(|s| {
386            // SAFETY: the range 0..=60u8 is exactly the range of valid Second
387            // discriminants.
388            unsafe { Self::from_repr(s).unwrap_unchecked() }
389        })
390    }
391}
392
393/// A bitset of values from 0 through 59.
394///
395/// ```text
396/// 0                                                         59
397/// |                                                          |
398/// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx0001 (0-63)
399///                                                                |
400///                                                               msb
401/// ```
402#[derive(Clone, Copy, PartialEq, Eq, Hash)]
403pub struct MinuteSet(NonZero<u64>);
404
405impl Debug for MinuteSet {
406    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
407        let mut set = BTreeSet::new();
408
409        for minute in Minute::iter() {
410            if self.get(minute) {
411                set.insert(minute);
412            }
413        }
414
415        write!(f, "{set:#?}")
416    }
417}
418
419impl MinuteSet {
420    pub(crate) const EMPTY: Self = Self(NonZero::new(1 << 63).unwrap());
421
422    /// Returns `true` if `minute` is in this set.
423    pub const fn get(&self, minute: Minute) -> bool {
424        let mask = 1 << (minute as u8);
425        (self.0.get() & mask) != 0
426    }
427
428    /// Inserts `minute` into this set.
429    pub const fn set(&mut self, minute: Minute) {
430        let mask = 1 << (minute as u8);
431        let updated = self.0.get() | mask;
432
433        // SAFETY: bitwise OR cannot reduce the number of set bits
434        *self = Self(unsafe { NonZero::new_unchecked(updated) })
435    }
436}
437
438impl Default for MinuteSet {
439    fn default() -> Self {
440        Self::EMPTY
441    }
442}
443
444/// A minute (ℤ mod 60), ranging from M0 through M59.
445#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
446#[repr(u8)]
447pub enum Minute {
448    M0,
449    M1,
450    M2,
451    M3,
452    M4,
453    M5,
454    M6,
455    M7,
456    M8,
457    M9,
458    M10,
459    M11,
460    M12,
461    M13,
462    M14,
463    M15,
464    M16,
465    M17,
466    M18,
467    M19,
468    M20,
469    M21,
470    M22,
471    M23,
472    M24,
473    M25,
474    M26,
475    M27,
476    M28,
477    M29,
478    M30,
479    M31,
480    M32,
481    M33,
482    M34,
483    M35,
484    M36,
485    M37,
486    M38,
487    M39,
488    M40,
489    M41,
490    M42,
491    M43,
492    M44,
493    M45,
494    M46,
495    M47,
496    M48,
497    M49,
498    M50,
499    M51,
500    M52,
501    M53,
502    M54,
503    M55,
504    M56,
505    M57,
506    M58,
507    M59,
508}
509
510impl Debug for Minute {
511    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
512        let m = *self as u8;
513        write!(f, "{m:02}")
514    }
515}
516
517impl Minute {
518    /// Converts a `u8` discriminant (0–59) to a `Minute`.
519    pub const fn from_repr(repr: u8) -> Option<Self> {
520        match repr {
521            0..=59 => {
522                // SAFETY: the discriminants of Self are exactly the values
523                // in the range 0..=59.
524                Some(unsafe { std::mem::transmute::<u8, Self>(repr) })
525            }
526            _ => None,
527        }
528    }
529
530    /// Returns an iterator over all 60 minute values (M0 through M59).
531    pub fn iter() -> impl ExactSizeIterator<Item = Self> {
532        (0..=59u8).map(|m| {
533            // SAFETY: 0..=59u8 is exactly the range of valid discriminants
534            // for Minute.
535            unsafe { Self::from_repr(m).unwrap_unchecked() }
536        })
537    }
538}
539
540/// A bitset of values from 0 through 23.
541///
542/// ```text
543/// 0                      23
544/// |                      |
545/// xxxxxxxxxxxxxxxxxxxxxxxx00000001 (0-31)
546///                                |
547///                               msb
548/// ```
549#[derive(Clone, Copy, PartialEq, Eq, Hash)]
550pub struct HourSet(NonZero<u32>);
551
552impl Debug for HourSet {
553    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
554        let mut set = BTreeSet::new();
555
556        for hour in Hour::iter() {
557            if self.get(hour) {
558                set.insert(hour);
559            }
560        }
561
562        write!(f, "{set:#?}")
563    }
564}
565
566impl HourSet {
567    pub(crate) const EMPTY: Self = Self(NonZero::new(1 << 31).unwrap());
568
569    /// Returns `true` if `hour` is in this set.
570    pub const fn get(&self, hour: Hour) -> bool {
571        let mask = 1 << (hour as u8);
572        (self.0.get() & mask) != 0
573    }
574
575    /// Inserts `hour` into this set.
576    pub const fn set(&mut self, hour: Hour) {
577        let mask = 1 << (hour as u8);
578        let updated = self.0.get() | mask;
579
580        // SAFETY: bitwise OR cannot reduce the number of set bits
581        *self = Self(unsafe { NonZero::new_unchecked(updated) })
582    }
583}
584
585impl Default for HourSet {
586    fn default() -> Self {
587        Self::EMPTY
588    }
589}
590
591/// An hour of the day, ranging from H0 through H23.
592#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
593#[repr(u8)]
594pub enum Hour {
595    H0,
596    H1,
597    H2,
598    H3,
599    H4,
600    H5,
601    H6,
602    H7,
603    H8,
604    H9,
605    H10,
606    H11,
607    H12,
608    H13,
609    H14,
610    H15,
611    H16,
612    H17,
613    H18,
614    H19,
615    H20,
616    H21,
617    H22,
618    H23,
619}
620
621impl Debug for Hour {
622    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
623        let h = *self as u8;
624        write!(f, "{h:02}")
625    }
626}
627
628impl Hour {
629    /// Converts a `u8` discriminant (0–23) to an `Hour`.
630    pub const fn from_repr(repr: u8) -> Option<Self> {
631        match repr {
632            0..=23 => {
633                // SAFETY: the discriminants of Self are exactly the values
634                // in the range 0..=23.
635                Some(unsafe { std::mem::transmute::<u8, Self>(repr) })
636            }
637            _ => None,
638        }
639    }
640
641    /// Returns an iterator over all 24 hour values (H0 through H23).
642    pub fn iter() -> impl ExactSizeIterator<Item = Self> {
643        (0..=23u8).map(|h| {
644            // SAFETY: 0..=23u8 is exactly the range of valid discriminants
645            // of Hour.
646            unsafe { Self::from_repr(h).unwrap_unchecked() }
647        })
648    }
649}
650
651/// A bitset of values from 1 through 12. The most significant bit is always set
652/// to guarantee that the entire set is never zero.
653///
654/// ```text
655///  1          12
656///  |          |   
657/// 0xxxxxxxxxxxx001 (0-15)
658/// |              |
659/// lsb           msb
660/// ```
661#[derive(Clone, Copy, PartialEq, Eq, Hash)]
662pub struct MonthSet(NonZero<u16>);
663
664impl Debug for MonthSet {
665    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
666        let mut set = BTreeSet::new();
667
668        for month in Month::iter() {
669            if self.get(month) {
670                set.insert(month);
671            }
672        }
673
674        write!(f, "{set:#?}")
675    }
676}
677
678impl MonthSet {
679    pub(crate) const EMPTY: Self = Self(NonZero::new(1 << 15).unwrap());
680
681    /// Returns `true` if `index` is in this set.
682    pub const fn get(&self, index: Month) -> bool {
683        let mask = 1 << index.number().get();
684        (self.0.get() & mask) != 0
685    }
686
687    /// Inserts `index` into this set.
688    pub const fn set(&mut self, index: Month) {
689        let mask = 1 << index.number().get();
690        let updated = self.0.get() | mask;
691
692        // SAFETY: bitwise OR cannot reduce the number of set bits
693        *self = Self(unsafe { NonZero::new_unchecked(updated) })
694    }
695}
696
697impl Default for MonthSet {
698    fn default() -> Self {
699        Self::EMPTY
700    }
701}
702
703/// A bitset of values from -31 through -1 and from 1 through 31. The most
704/// significant bit is always to set to guarantee the entire set is nonzero.
705///
706/// ```text
707///  1                             31                            -31
708///  |                             |                              |
709/// 0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1 (0-63)
710/// |                               |                              |
711/// lsb                            -1                              msb
712/// ```
713#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
714pub struct MonthDaySet(NonZero<u64>);
715
716/// A valid index into a [`MonthDaySet`].
717#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
718pub struct MonthDaySetIndex(NonZero<u8>);
719
720impl MonthDaySet {
721    pub(crate) const EMPTY: Self = Self(NonZero::new(1 << 63).unwrap());
722
723    /// Returns `true` if `index` is in this set.
724    pub const fn get(&self, index: MonthDaySetIndex) -> bool {
725        let mask = 1 << index.0.get();
726        (self.0.get() & mask) != 0
727    }
728
729    /// Inserts `index` into this set.
730    pub const fn set(&mut self, index: MonthDaySetIndex) {
731        let mask = 1 << index.0.get();
732        let updated = self.0.get() | mask;
733
734        // SAFETY: bitwise OR cannot reduce the number of set bits
735        *self = Self(unsafe { NonZero::new_unchecked(updated) })
736    }
737}
738
739impl MonthDaySetIndex {
740    /// Creates an index from a sign and day (e.g. `+D15` or `-D1`).
741    pub const fn from_signed_month_day(sign: Sign, day: MonthDay) -> Self {
742        let day = day as u8;
743        let offset = match sign {
744            Sign::Pos => 0,
745            Sign::Neg => 31,
746        };
747
748        // SAFETY: (day as u8) lies in the range 1..=31
749        Self(unsafe { NonZero::new_unchecked(day + offset) })
750    }
751}
752
753impl Default for MonthDaySet {
754    fn default() -> Self {
755        Self::EMPTY
756    }
757}
758
759/// A bitset of values from -53 through -1 and from 1 through 53. The highest
760/// bit is always set so we can guarantee the entire bitset is never 0.
761///
762/// ```text
763///  1                                                   53
764///  |                                                   |
765/// 0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx0000000000 (0-63)
766/// 0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx0000000001 (64-127)
767///  |                                                   |         |
768/// -1                                                  -53       msb
769/// ```
770#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
771pub struct WeekNoSet(NonZero<u128>);
772
773/// A valid index into a [`WeekNoSet`].
774#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
775pub struct WeekNoSetIndex(NonZero<u8>);
776
777impl WeekNoSet {
778    pub(crate) const EMPTY: Self = Self(NonZero::new(1 << 127).unwrap());
779
780    /// Returns `true` if `index` is in this set.
781    pub const fn get(&self, index: WeekNoSetIndex) -> bool {
782        let mask = 1 << (index.0.get());
783        (mask & self.0.get()) != 0
784    }
785
786    /// Inserts `index` into this set.
787    pub const fn set(&mut self, index: WeekNoSetIndex) {
788        let mask = 1 << (index.0.get());
789        let updated = mask | self.0.get();
790
791        // SAFETY: bitwise OR cannot reduce the number of set bits
792        *self = Self(unsafe { NonZero::new_unchecked(updated) })
793    }
794}
795
796impl WeekNoSetIndex {
797    /// Creates an index from a sign and ISO week number.
798    pub const fn from_signed_week(sign: Sign, week: IsoWeek) -> Self {
799        let week = week as u8;
800        let offset = match sign {
801            Sign::Pos => 0,
802            Sign::Neg => 64,
803        };
804
805        // SAFETY: (week as u8) is guaranteed to lie in the range 1..=53
806        Self(unsafe { NonZero::new_unchecked(week + offset) })
807    }
808}
809
810impl Default for WeekNoSet {
811    fn default() -> Self {
812        Self::EMPTY
813    }
814}
815
816/// A particular day in a month, ranging from D1 to D31.
817#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
818#[repr(u8)]
819pub enum MonthDay {
820    D1 = 1,
821    D2,
822    D3,
823    D4,
824    D5,
825    D6,
826    D7,
827    D8,
828    D9,
829    D10,
830    D11,
831    D12,
832    D13,
833    D14,
834    D15,
835    D16,
836    D17,
837    D18,
838    D19,
839    D20,
840    D21,
841    D22,
842    D23,
843    D24,
844    D25,
845    D26,
846    D27,
847    D28,
848    D29,
849    D30,
850    D31,
851}
852
853impl MonthDay {
854    /// Converts a `u8` discriminant (1–31) to a `MonthDay`.
855    pub const fn from_repr(repr: u8) -> Option<Self> {
856        match repr {
857            1..=31 => {
858                // SAFETY: the discriminants of Self are exactly the values
859                // of the range 1..=31.
860                Some(unsafe { std::mem::transmute::<u8, Self>(repr) })
861            }
862            _ => None,
863        }
864    }
865}
866
867/// The name of a recurrence rule part.
868#[derive(Debug, Clone, Copy, PartialEq, Eq)]
869pub enum PartName {
870    Freq,
871    Until,
872    Count,
873    Interval,
874    BySecond,
875    ByMinute,
876    ByHour,
877    ByDay,
878    ByMonthDay,
879    ByYearDay,
880    ByWeekNo,
881    ByMonth,
882    BySetPos,
883    WkSt,
884}
885
886impl From<&Part> for PartName {
887    fn from(value: &Part) -> Self {
888        match value {
889            Part::Freq(_) => Self::Freq,
890            Part::Until(_) => Self::Until,
891            Part::Count(_) => Self::Count,
892            Part::Interval(_) => Self::Interval,
893            Part::BySecond(_) => Self::BySecond,
894            Part::ByMinute(_) => Self::ByMinute,
895            Part::ByHour(_) => Self::ByHour,
896            Part::ByDay(_) => Self::ByDay,
897            Part::ByMonthDay(_) => Self::ByMonthDay,
898            Part::ByYearDay(_) => Self::ByYearDay,
899            Part::ByWeekNo(_) => Self::ByWeekNo,
900            Part::ByMonth(_) => Self::ByMonth,
901            Part::BySetPos(_) => Self::BySetPos,
902            Part::WkSt(_) => Self::WkSt,
903        }
904    }
905}
906
907impl From<ByRuleName> for PartName {
908    fn from(value: ByRuleName) -> Self {
909        match value {
910            ByRuleName::BySecond => Self::BySecond,
911            ByRuleName::ByMinute => Self::ByMinute,
912            ByRuleName::ByHour => Self::ByHour,
913            ByRuleName::ByDay => Self::ByDay,
914            ByRuleName::ByMonthDay => Self::ByMonthDay,
915            ByRuleName::ByYearDay => Self::ByYearDay,
916            ByRuleName::ByWeekNo => Self::ByWeekNo,
917            ByRuleName::ByMonth => Self::ByMonth,
918            ByRuleName::BySetPos => Self::BySetPos,
919        }
920    }
921}
922
923impl PartName {
924    /// Returns the corresponding [`ByRuleName`], or `None` if this is not a BYxxx part.
925    pub const fn as_by_rule(&self) -> Option<ByRuleName> {
926        match self {
927            PartName::BySecond => Some(ByRuleName::BySecond),
928            PartName::ByMinute => Some(ByRuleName::ByMinute),
929            PartName::ByHour => Some(ByRuleName::ByHour),
930            PartName::ByDay => Some(ByRuleName::ByDay),
931            PartName::ByMonthDay => Some(ByRuleName::ByMonthDay),
932            PartName::ByYearDay => Some(ByRuleName::ByYearDay),
933            PartName::ByWeekNo => Some(ByRuleName::ByWeekNo),
934            PartName::ByMonth => Some(ByRuleName::ByMonth),
935            PartName::BySetPos => Some(ByRuleName::BySetPos),
936            _ => None,
937        }
938    }
939}
940
941/// The names of the BYxxx parts.
942#[derive(Debug, Clone, Copy, PartialEq, Eq)]
943pub enum ByRuleName {
944    BySecond,
945    ByMinute,
946    ByHour,
947    ByDay,
948    ByMonthDay,
949    ByYearDay,
950    ByWeekNo,
951    ByMonth,
952    BySetPos,
953}
954
955/// The values in the table on page 43 of RFC 5545.
956#[derive(Debug, Clone, Copy, PartialEq, Eq)]
957pub enum ByRuleBehavior {
958    Limit,
959    Expand,
960    Note1,
961    Note2,
962}
963
964impl ByRuleName {
965    /// Returns the [`ByRuleBehavior`] of `self` with the given [`Freq`],
966    /// as described in the table from RFC 5545 page 44.
967    pub const fn behavior_with(&self, freq: Freq) -> Option<ByRuleBehavior> {
968        match (*self, freq) {
969            (Self::ByMonth, Freq::Yearly) => Some(ByRuleBehavior::Expand),
970            (Self::ByWeekNo, Freq::Yearly) => Some(ByRuleBehavior::Expand),
971            (Self::ByWeekNo, _) => None,
972            (Self::ByYearDay, Freq::Secondly | Freq::Minutely | Freq::Hourly) => {
973                Some(ByRuleBehavior::Limit)
974            }
975            (Self::ByYearDay, Freq::Yearly) => Some(ByRuleBehavior::Expand),
976            (Self::ByYearDay, _) => None,
977            (Self::ByMonthDay, Freq::Weekly) => None,
978            (Self::ByMonthDay, Freq::Monthly | Freq::Yearly) => Some(ByRuleBehavior::Expand),
979            (Self::ByDay, Freq::Weekly) => Some(ByRuleBehavior::Expand),
980            (Self::ByDay, Freq::Monthly) => Some(ByRuleBehavior::Note1),
981            (Self::ByDay, Freq::Yearly) => Some(ByRuleBehavior::Note2),
982            (Self::ByHour, Freq::Secondly | Freq::Minutely | Freq::Hourly) => {
983                Some(ByRuleBehavior::Limit)
984            }
985            (Self::ByHour, _) => Some(ByRuleBehavior::Expand),
986            (Self::ByMinute, Freq::Secondly | Freq::Minutely) => Some(ByRuleBehavior::Limit),
987            (Self::ByMinute, _) => Some(ByRuleBehavior::Expand),
988            (Self::BySecond, Freq::Secondly) => Some(ByRuleBehavior::Limit),
989            (Self::BySecond, _) => Some(ByRuleBehavior::Expand),
990            _ => Some(ByRuleBehavior::Limit),
991        }
992    }
993}
994
995/// A variant in the `recur-rule-part` grammar rule.
996#[derive(Debug, Clone, PartialEq, Eq)]
997pub enum Part {
998    Freq(Freq),
999    Until(DateTimeOrDate),
1000    Count(u64),
1001    Interval(Interval),
1002    BySecond(SecondSet),
1003    ByMinute(MinuteSet),
1004    ByHour(HourSet),
1005    ByDay(WeekdayNumSet),
1006    ByMonthDay(MonthDaySet),
1007    ByYearDay(BTreeSet<YearDayNum>),
1008    ByWeekNo(WeekNoSet),
1009    ByMonth(MonthSet),
1010    BySetPos(BTreeSet<YearDayNum>),
1011    WkSt(Weekday),
1012}
1013
1014#[cfg(test)]
1015mod tests {
1016    use super::*;
1017
1018    #[test]
1019    fn second_set_empty() {
1020        let empty = SecondSet::default();
1021        let bitstring = format!("{:b}", empty.0);
1022        assert_eq!(bitstring.len(), 64);
1023
1024        let mut chars = bitstring.chars();
1025        assert_eq!(chars.next(), Some('1'));
1026
1027        for char in chars {
1028            assert_eq!(char, '0');
1029        }
1030    }
1031
1032    #[test]
1033    fn second_from_index() {
1034        for i in 0..=60 {
1035            assert!(Second::from_repr(i).is_some());
1036        }
1037
1038        for i in 61..=255 {
1039            assert!(Second::from_repr(i).is_none());
1040        }
1041    }
1042
1043    #[test]
1044    fn minute_set_empty() {
1045        let empty = MinuteSet::default();
1046        let bitstring = format!("{:b}", empty.0);
1047        assert_eq!(bitstring.len(), 64);
1048
1049        let mut chars = bitstring.chars();
1050        assert_eq!(chars.next(), Some('1'));
1051
1052        for char in chars {
1053            assert_eq!(char, '0');
1054        }
1055    }
1056
1057    #[test]
1058    fn minute_from_index() {
1059        for i in 0..=59 {
1060            assert!(Minute::from_repr(i).is_some());
1061        }
1062
1063        for i in 60..=255 {
1064            assert!(Minute::from_repr(i).is_none());
1065        }
1066    }
1067
1068    #[test]
1069    fn hour_set_empty() {
1070        let empty = HourSet::default();
1071        let bitstring = format!("{:b}", empty.0);
1072        assert_eq!(bitstring.len(), 32);
1073
1074        let mut chars = bitstring.chars();
1075        assert_eq!(chars.next(), Some('1'));
1076
1077        for char in chars {
1078            assert_eq!(char, '0');
1079        }
1080    }
1081
1082    #[test]
1083    fn hour_set_bit_twiddling() {
1084        let mut set = HourSet::default();
1085
1086        let i1 = Hour::H0;
1087        let i2 = Hour::H3;
1088        let i3 = Hour::H4;
1089        let i4 = Hour::H13;
1090        let i5 = Hour::H22;
1091
1092        for i in [i1, i2, i3, i4, i5] {
1093            assert!(!set.get(i));
1094        }
1095
1096        for i in [i1, i2, i3, i4, i5] {
1097            set.set(i);
1098        }
1099
1100        for i in [i1, i2, i3, i4, i5] {
1101            assert!(set.get(i));
1102        }
1103    }
1104
1105    #[test]
1106    fn month_set_empty() {
1107        let empty = MonthSet::default();
1108        let bitstring = format!("{:b}", empty.0);
1109        assert_eq!(bitstring.len(), 16);
1110
1111        let mut chars = bitstring.chars();
1112        assert_eq!(chars.next(), Some('1'));
1113
1114        for char in chars {
1115            assert_eq!(char, '0');
1116        }
1117    }
1118
1119    #[test]
1120    fn month_set_bit_twiddling() {
1121        let mut month_set = MonthSet::default();
1122
1123        let i1 = Month::Jan;
1124        let i2 = Month::Apr;
1125        let i3 = Month::Aug;
1126        let i4 = Month::Sep;
1127
1128        for i in [i1, i2, i3, i4] {
1129            assert!(!month_set.get(i));
1130        }
1131
1132        for i in [i1, i2, i3, i4] {
1133            month_set.set(i);
1134        }
1135
1136        for i in [i1, i2, i3, i4] {
1137            assert!(month_set.get(i));
1138        }
1139    }
1140
1141    #[test]
1142    fn month_day_set_empty() {
1143        let empty = MonthDaySet::default();
1144        let bitstring = format!("{:b}", empty.0);
1145        assert_eq!(bitstring.len(), 64);
1146
1147        let mut chars = bitstring.chars();
1148        assert_eq!(chars.next(), Some('1'));
1149
1150        for char in chars {
1151            assert_eq!(char, '0');
1152        }
1153    }
1154
1155    #[test]
1156    fn month_day_set_index_from_signed_month_day() {
1157        assert_eq!(
1158            MonthDaySetIndex::from_signed_month_day(Sign::Pos, MonthDay::D1)
1159                .0
1160                .get(),
1161            1
1162        );
1163
1164        assert_eq!(
1165            MonthDaySetIndex::from_signed_month_day(Sign::Pos, MonthDay::D31)
1166                .0
1167                .get(),
1168            31
1169        );
1170
1171        assert_eq!(
1172            MonthDaySetIndex::from_signed_month_day(Sign::Neg, MonthDay::D1)
1173                .0
1174                .get(),
1175            31 + 1
1176        );
1177
1178        assert_eq!(
1179            MonthDaySetIndex::from_signed_month_day(Sign::Neg, MonthDay::D31)
1180                .0
1181                .get(),
1182            31 + 31
1183        );
1184    }
1185
1186    #[test]
1187    fn month_day_set_bit_twiddling() {
1188        let mut month_day_set = MonthDaySet::default();
1189
1190        let i1 = MonthDaySetIndex::from_signed_month_day(Sign::Pos, MonthDay::D1);
1191        let i2 = MonthDaySetIndex::from_signed_month_day(Sign::Pos, MonthDay::D25);
1192        let i3 = MonthDaySetIndex::from_signed_month_day(Sign::Neg, MonthDay::D6);
1193
1194        for i in [i1, i2, i3] {
1195            assert!(!month_day_set.get(i));
1196        }
1197
1198        for i in [i1, i2, i3] {
1199            month_day_set.set(i);
1200        }
1201
1202        for i in [i1, i2, i3] {
1203            assert!(month_day_set.get(i));
1204        }
1205    }
1206
1207    #[test]
1208    fn week_no_set_empty() {
1209        let empty = WeekNoSet::default();
1210        let bitstring = format!("{:b}", empty.0);
1211        assert_eq!(bitstring.len(), 128);
1212
1213        let mut chars = bitstring.chars();
1214        assert_eq!(chars.next(), Some('1'));
1215
1216        for char in chars {
1217            assert_eq!(char, '0');
1218        }
1219    }
1220
1221    #[test]
1222    fn week_no_set_index_from_signed_week() {
1223        assert_eq!(
1224            WeekNoSetIndex::from_signed_week(Sign::Pos, IsoWeek::W1),
1225            WeekNoSetIndex(NonZero::new(1).unwrap())
1226        );
1227
1228        assert_eq!(
1229            WeekNoSetIndex::from_signed_week(Sign::Neg, IsoWeek::W1),
1230            WeekNoSetIndex(NonZero::new(64 + 1).unwrap())
1231        );
1232
1233        assert_eq!(
1234            WeekNoSetIndex::from_signed_week(Sign::Pos, IsoWeek::W53),
1235            WeekNoSetIndex(NonZero::new(53).unwrap())
1236        );
1237
1238        assert_eq!(
1239            WeekNoSetIndex::from_signed_week(Sign::Neg, IsoWeek::W53),
1240            WeekNoSetIndex(NonZero::new(64 + 53).unwrap())
1241        );
1242    }
1243
1244    #[test]
1245    fn week_no_set_bit_twiddling() {
1246        let mut week_no_set = WeekNoSet::default();
1247
1248        let i1 = WeekNoSetIndex::from_signed_week(Sign::Pos, IsoWeek::W12);
1249        let i2 = WeekNoSetIndex::from_signed_week(Sign::Neg, IsoWeek::W8);
1250        let i3 = WeekNoSetIndex::from_signed_week(Sign::Neg, IsoWeek::W37);
1251
1252        for i in [i1, i2, i3] {
1253            assert!(!week_no_set.get(i));
1254        }
1255
1256        for i in [i1, i2, i3] {
1257            week_no_set.set(i);
1258        }
1259
1260        for i in [i1, i2, i3] {
1261            assert!(week_no_set.get(i));
1262        }
1263    }
1264
1265    #[test]
1266    fn weekday_num_ord_impl() {
1267        let none_monday = WeekdayNum {
1268            ordinal: None,
1269            weekday: Weekday::Monday,
1270        };
1271
1272        let none_tuesday = WeekdayNum {
1273            ordinal: None,
1274            weekday: Weekday::Tuesday,
1275        };
1276
1277        let none_friday = WeekdayNum {
1278            ordinal: None,
1279            weekday: Weekday::Friday,
1280        };
1281
1282        assert!(none_monday < none_tuesday);
1283        assert!(none_monday < none_friday);
1284        assert!(none_tuesday < none_friday);
1285
1286        let sub_53_monday = WeekdayNum {
1287            ordinal: Some((Sign::Neg, IsoWeek::W53)),
1288            weekday: Weekday::Monday,
1289        };
1290
1291        let sub_50_monday = WeekdayNum {
1292            ordinal: Some((Sign::Neg, IsoWeek::W50)),
1293            weekday: Weekday::Monday,
1294        };
1295
1296        let sub_53_wednesday = WeekdayNum {
1297            ordinal: Some((Sign::Neg, IsoWeek::W53)),
1298            weekday: Weekday::Wednesday,
1299        };
1300
1301        let sub_50_thursday = WeekdayNum {
1302            ordinal: Some((Sign::Neg, IsoWeek::W50)),
1303            weekday: Weekday::Thursday,
1304        };
1305
1306        assert!(none_monday < sub_53_wednesday);
1307        assert!(none_tuesday < sub_53_wednesday);
1308        assert!(none_friday < sub_53_wednesday);
1309
1310        assert!(sub_53_monday < sub_53_wednesday);
1311        assert!(sub_53_monday < sub_50_monday);
1312        assert!(sub_53_wednesday < sub_50_monday);
1313        assert!(sub_53_wednesday < sub_50_thursday);
1314        assert!(sub_50_monday < sub_50_thursday);
1315
1316        let pos_53_monday = WeekdayNum {
1317            ordinal: Some((Sign::Pos, IsoWeek::W53)),
1318            weekday: Weekday::Monday,
1319        };
1320
1321        let pos_50_monday = WeekdayNum {
1322            ordinal: Some((Sign::Pos, IsoWeek::W50)),
1323            weekday: Weekday::Monday,
1324        };
1325
1326        let pos_53_wednesday = WeekdayNum {
1327            ordinal: Some((Sign::Pos, IsoWeek::W53)),
1328            weekday: Weekday::Wednesday,
1329        };
1330
1331        let pos_50_thursday = WeekdayNum {
1332            ordinal: Some((Sign::Pos, IsoWeek::W50)),
1333            weekday: Weekday::Thursday,
1334        };
1335
1336        assert!(sub_53_monday < pos_53_monday);
1337        assert!(sub_50_monday < pos_50_monday);
1338        assert!(sub_50_thursday < pos_50_thursday);
1339
1340        assert!(pos_50_thursday < pos_53_wednesday);
1341        assert!(pos_53_monday < pos_53_wednesday);
1342    }
1343
1344    #[test]
1345    fn behavior_with_table() {
1346        let freqs = [
1347            Freq::Secondly,
1348            Freq::Minutely,
1349            Freq::Hourly,
1350            Freq::Daily,
1351            Freq::Weekly,
1352            Freq::Monthly,
1353            Freq::Yearly,
1354        ];
1355
1356        let by_rules = [
1357            ByRuleName::ByMonth,
1358            ByRuleName::ByWeekNo,
1359            ByRuleName::ByYearDay,
1360            ByRuleName::ByMonthDay,
1361            ByRuleName::ByDay,
1362            ByRuleName::ByHour,
1363            ByRuleName::ByMinute,
1364            ByRuleName::BySecond,
1365            ByRuleName::BySetPos,
1366        ];
1367
1368        #[allow(non_snake_case)]
1369        let Limit = Some(ByRuleBehavior::Limit);
1370        #[allow(non_snake_case)]
1371        let Expand = Some(ByRuleBehavior::Expand);
1372        #[allow(non_snake_case)]
1373        let Note1 = Some(ByRuleBehavior::Note1);
1374        #[allow(non_snake_case)]
1375        let Note2 = Some(ByRuleBehavior::Note2);
1376
1377        // corresponds to the table from RFC 5545 (page 44).
1378        let table = [
1379            [Limit, Limit, Limit, Limit, Limit, Limit, Expand],
1380            [None, None, None, None, None, None, Expand],
1381            [Limit, Limit, Limit, None, None, None, Expand],
1382            [Limit, Limit, Limit, Limit, None, Expand, Expand],
1383            [Limit, Limit, Limit, Limit, Expand, Note1, Note2],
1384            [Limit, Limit, Limit, Expand, Expand, Expand, Expand],
1385            [Limit, Limit, Expand, Expand, Expand, Expand, Expand],
1386            [Limit, Expand, Expand, Expand, Expand, Expand, Expand],
1387            [Limit, Limit, Limit, Limit, Limit, Limit, Limit],
1388        ];
1389
1390        for (i, &rule) in by_rules.iter().enumerate() {
1391            for (j, &freq) in freqs.iter().enumerate() {
1392                let expected = table[i][j];
1393                let got = rule.behavior_with(freq);
1394                assert_eq!(got, expected);
1395            }
1396        }
1397    }
1398}