#![deny(unsafe_code)]
#![warn(missing_docs)]
#![warn(clippy::pedantic)]
#![allow(clippy::unreadable_literal)]
#![doc = include_str!("../README.md")]
pub mod occurrences;
use bitflags::bitflags;
use chrono::NaiveDate;
use getset::{Getters, Setters};
use occurrences::Iter;
#[derive(Getters, Setters, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[getset(get = "pub")]
pub struct RecurrenceRule {
not_before: NaiveDate,
not_after: Option<NaiveDate>,
#[getset(set = "pub")]
max_occurrences: Option<u64>,
#[getset(set = "pub")]
frequency: Frequency,
#[getset(set = "pub")]
day_filter: DayFilter,
#[getset(set = "pub")]
resolve: ResolveDirection,
}
impl RecurrenceRule {
#[must_use]
pub fn new(frequency: Frequency, not_before: NaiveDate) -> Self {
Self {
not_before,
not_after: None,
max_occurrences: None,
frequency,
day_filter: DayFilter::EVERYDAY,
resolve: ResolveDirection::IntoFuture,
}
}
#[must_use = "If you do not need to validate the checking, use `set_not_before_unchecked"]
pub fn set_not_before(&mut self, not_before: NaiveDate) -> bool {
if let Some(not_after) = &self.not_after {
if ¬_before > not_after {
return false;
}
}
self.set_not_before_unchecked(not_before);
true
}
pub fn set_not_before_unchecked(&mut self, not_before: NaiveDate) {
self.not_before = not_before;
}
#[must_use = "If you do not need to validate the checking, use `set_not_after_unchecked"]
pub fn set_not_after(&mut self, not_after: Option<NaiveDate>) -> bool {
if let Some(not_after_dt) = ¬_after {
if not_after_dt < &self.not_before {
return false;
}
}
self.set_not_after_unchecked(not_after);
true
}
pub fn set_not_after_unchecked(&mut self, not_after: Option<NaiveDate>) {
self.not_after = not_after;
}
#[must_use]
pub fn iter_after(&self, start_point: &NaiveDate) -> Iter {
Iter {
currently_at: *start_point,
index: 0,
rule: self,
}
}
#[must_use]
pub fn iter(&self) -> Iter {
self.iter_after(
&self
.not_before()
.checked_sub_days(chrono::Days::new(1))
.unwrap(),
)
}
}
impl<'a> IntoIterator for &'a RecurrenceRule {
type Item = occurrences::Occurrence;
type IntoIter = occurrences::Iter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Frequency {
Weekly {
days: DayFilter,
},
Monthly {
date: u8,
},
Yearly {
date: u8,
month: u8,
},
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ResolveDirection {
IntoFuture,
IntoPast,
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DayFilter: u8 {
const MONDAY = 0b1000000;
const TUESDAY = 0b0100000;
const WEDNESDAY = 0b0010000;
const THURSDAY = 0b0001000;
const FRIDAY = 0b0000100;
const SATURDAY = 0b0000010;
const SUNDAY = 0b0000001;
const EVERYDAY = Self::MONDAY.bits()
| Self::TUESDAY.bits()
| Self::WEDNESDAY.bits()
| Self::THURSDAY.bits()
| Self::FRIDAY.bits()
| Self::SATURDAY.bits()
| Self::SUNDAY.bits();
const ANYDAY = Self::EVERYDAY.bits();
const WEEKDAYS = Self::MONDAY.bits()
| Self::TUESDAY.bits()
| Self::WEDNESDAY.bits()
| Self::THURSDAY.bits()
| Self::FRIDAY.bits();
const WEEKENDS = Self::SATURDAY.bits()
| Self::SUNDAY.bits();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_not_before_checking() {
let mut rule = RecurrenceRule::new(
Frequency::Monthly { date: 1 },
NaiveDate::from_ymd_opt(2000, 1, 1).unwrap(),
);
rule.set_not_after_unchecked(Some(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap()));
let allowed = rule.set_not_before(NaiveDate::from_ymd_opt(2001, 1, 1).unwrap());
assert!(!allowed);
let allowed = rule.set_not_before(NaiveDate::from_ymd_opt(1999, 1, 1).unwrap());
assert!(allowed);
}
#[test]
fn test_not_after_checking() {
let mut rule = RecurrenceRule::new(
Frequency::Monthly { date: 1 },
NaiveDate::from_ymd_opt(2000, 1, 1).unwrap(),
);
let allowed = rule.set_not_after(Some(NaiveDate::from_ymd_opt(1999, 1, 1).unwrap()));
assert!(!allowed);
let allowed = rule.set_not_after(Some(NaiveDate::from_ymd_opt(2001, 1, 1).unwrap()));
assert!(allowed);
}
}