///|
/// A calendar recurrence set combines one or more rules and explicit dates,
/// then removes exception dates. This mirrors RFC 5545 recurrence semantics.
pub struct RecurrenceSet {
start : DateTime
rules : Array[RRule]
include_dates : Array[DateTime]
exclude_dates : Array[DateTime]
} derive(Eq, Debug)
///|
pub fn RecurrenceSet::new(start : DateTime) -> RecurrenceSet {
{ start, rules: [], include_dates: [], exclude_dates: [] }
}
///|
pub fn RecurrenceSet::add_rule(
self : RecurrenceSet,
rule : RRule,
) -> RecurrenceSet {
let rules = self.rules.copy()
rules.push(rule)
{ ..self, rules, }
}
///|
pub fn RecurrenceSet::add_date(
self : RecurrenceSet,
value : DateTime,
) -> RecurrenceSet {
let include_dates = self.include_dates.copy()
include_dates.push(value)
{ ..self, include_dates, }
}
///|
pub fn RecurrenceSet::exclude_date(
self : RecurrenceSet,
value : DateTime,
) -> RecurrenceSet {
let exclude_dates = self.exclude_dates.copy()
exclude_dates.push(value)
{ ..self, exclude_dates, }
}
///|
fn append_unique_datetime(target : Array[DateTime], value : DateTime) -> Unit {
if !target.contains(value) {
target.push(value)
}
}
///|
pub fn RecurrenceSet::expand(
self : RecurrenceSet,
options : ExpansionOptions,
) -> Array[DateTime] raise ExpansionError {
let result : Array[DateTime] = []
if self.rules.length() == 0 && datetime_in_window(self.start, options) {
append_unique_datetime(result, self.start)
}
for rule in self.rules {
for value in expand_rrule(self.start, rule, options) {
append_unique_datetime(result, value)
}
}
for value in self.include_dates {
if datetime_in_window(value, options) {
append_unique_datetime(result, value)
}
}
let filtered : Array[DateTime] = []
result.sort()
for value in result {
if !self.exclude_dates.contains(value) && filtered.length() < options.limit {
filtered.push(value)
}
}
filtered
}
///|
pub(all) enum SetOperation {
Union
Intersection
Difference
} derive(Eq, Debug)
///|
pub fn combine_occurrences(
left : Array[DateTime],
right : Array[DateTime],
operation : SetOperation,
) -> Array[DateTime] {
let result : Array[DateTime] = []
match operation {
Union => {
for value in left {
append_unique_datetime(result, value)
}
for value in right {
append_unique_datetime(result, value)
}
}
Intersection =>
for value in left {
if right.contains(value) {
append_unique_datetime(result, value)
}
}
Difference =>
for value in left {
if !right.contains(value) {
append_unique_datetime(result, value)
}
}
}
result.sort()
result
}
///|
pub struct RuleDifference {
only_left : Array[DateTime]
only_right : Array[DateTime]
shared : Array[DateTime]
} derive(Eq, Debug)
///|
pub fn compare_rules(
start : DateTime,
left : RRule,
right : RRule,
options : ExpansionOptions,
) -> RuleDifference raise ExpansionError {
let left_values = expand_rrule(start, left, options)
let right_values = expand_rrule(start, right, options)
{
only_left: combine_occurrences(left_values, right_values, Difference),
only_right: combine_occurrences(right_values, left_values, Difference),
shared: combine_occurrences(left_values, right_values, Intersection),
}
}