///|
priv struct RecurrenceCache {
values : Array[DateTime]
mut source : Iter[DateTime]?
mut complete : Bool
}
///|
fn recurrence_cache(enabled : Bool) -> RecurrenceCache? {
if enabled {
Some({ values: [], source: None, complete: false, })
} else {
None
}
}
///|
fn RecurrenceCache::iterator(
self : RecurrenceCache,
factory : () -> Iter[DateTime],
) -> Iter[DateTime] {
let mut index = 0
Iter::new(fn() {
if index < self.values.length() {
let value = self.values[index]
index += 1
return Some(value)
}
if self.complete {
return None
}
if self.source is None {
self.source = Some(factory())
}
match self.source.unwrap().next() {
Some(value) => {
self.values.push(value)
index += 1
Some(value)
}
None => {
self.complete = true
self.source = None
None
}
}
})
}
///|
// Delay candidate construction until the reader actually requests a value.
// Only calendar-boundary conversions can be unavailable for a validated rule;
// these are clipped to the same supported Gregorian/UTC domain as locators.
fn rrule_stream(rule : DateRule, lower : DateTime?) -> Iter[DateTime] {
let mut source : Iter[Double]? = None
let mut finished = false
Iter::new(fn() {
if finished {
return None
}
if source is None {
let start = rule.dtstart.unwrap()
let first = match lower {
Some(value) if value.to_ordinal() > start.to_ordinal() => value
_ => start
}
let last = try! datetime(9999, 12, 31, hour=23, minute=59, second=59)
if first.to_ordinal() > last.to_ordinal() {
finished = true
return None
}
let locator = date_locator_from_rule(rule)
let first_date = rule.local_date(first)
let last_date = rule.local_date(last)
source = Some(
recurrence_ordinals(
locator,
first.to_ordinal(),
last.to_ordinal(),
first_date,
last_date,
),
)
}
while true {
match source.unwrap().next() {
Some(ordinal) => {
let value = match rule.timezone {
Some(zone) =>
zone.generated_datetime(ordinal) catch {
_ => continue
}
None =>
decoded_ordinal(
ordinal,
rule.dtstart.unwrap().offset_seconds,
true,
) catch {
_ => continue
}
}
return Some(value)
}
None => {
finished = true
return None
}
}
}
None
})
}
///|
/// Return whether this rule shares a lazily generated occurrence cache.
pub fn RRule::cache_enabled(self : RRule) -> Bool {
self.cache is Some(_)
}
///|
/// Return an independent lazy reader starting at DTSTART. Native Iter supports
/// `next`, `take`, `map` and `for`. It stops at count/until or the supported
/// calendar boundary; unlike eager arrays it has no 100000 occurrence cap.
///
/// ```mbt check
/// test {
/// let rule = @date.rrule(@date.Daily, @date.datetime(2024, 1, 1), cache=true)
/// assert_eq(rule.iter().take(3).to_array().map(value => value.day()), [1, 2, 3])
/// assert_eq(rule.iter().next().unwrap().day(), 1)
/// }
/// ```
pub fn RRule::iter(self : RRule) -> Iter[DateTime] {
let rule = self.rule
match self.cache {
Some(cache) => cache.iterator(() => rrule_stream(rule, None))
None => rrule_stream(rule, None)
}
}
///|
/// Lazily yield occurrences after an instant, including equality with `inc`.
/// An omitted count streams to the rule's natural end; nonpositive counts
/// return an empty iterator. Each call has an independent reader position.
pub fn RRule::iter_after(
self : RRule,
value : DateTime,
count? : Int32,
inc~ : Bool = false,
) -> Iter[DateTime] {
if count is Some(limit) && limit <= 0 {
return Iter::empty()
}
let target = value.to_ordinal()
let source = match self.cache {
Some(_) => self.iter()
None => rrule_stream(self.rule, Some(value))
}
let values = source.filter(candidate => {
if inc {
candidate.to_ordinal() >= target
} else {
candidate.to_ordinal() > target
}
})
match count {
Some(limit) => values.take(limit)
None => values
}
}