///|
/// Mutable Matplotlib rrulewrapper. The underlying recurrence stays naive;
/// selected query methods attach the current effective timezone on return.
pub struct RRuleWrapper {
priv base : DateTimeZone?
priv context : DateParserContext?
priv mut construct : WrapperOptions
priv mut timezone : DateTimeZone?
priv mut core : RRule
}
///|
/// Construct with optional explicit base zone and deterministic host clock.
/// dtstart may be omitted. null names pass Python-style None; null dtstart/until
/// reproduce the wrapper's checked date-normalization error. Configuration
/// arrays are retained for later set calls; core query selectors are snapshots.
/// bymonth_value/bymonthday_value retain scalar keyword forms for YearLocator.
///
/// ```mbt check
/// test {
/// let start = @date.RRuleDateTime::naive(@date.datetime(2024, 1, 1))
/// let rule = @date.RRuleWrapper::new(@date.Daily, dtstart=start, count=3)
/// assert_eq(
/// rule.xafter(start, count=2, inc=true).map(value => value.date().day()),
/// [1, 2],
/// )
/// rule.set(interval=2)
/// assert_eq(rule.raw_iter().to_array().map(value => value.date().day()), [
/// 1, 3, 5,
/// ])
/// }
/// ```
pub fn RRuleWrapper::new(
frequency : RRuleFrequency,
timezone? : DateTimeZone,
context? : DateParserContext,
dtstart? : RRuleDateTime,
until? : RRuleDateTime,
interval~ : Int32 = 1,
wkst~ : Int32 = 0,
count? : Int32,
bymonth? : Array[Int32],
bymonthday? : Array[Int32],
bymonth_value? : Int32,
bymonthday_value? : Int32,
byyearday? : Array[Int32],
byweekno? : Array[Int32],
byeaster? : Array[Int32],
byweekday? : Array[Int32],
byhour? : Array[Int32],
byminute? : Array[Int32],
bysecond? : Array[Int32],
bysetpos? : Array[Int32],
byweekdays? : Array[RelativeWeekday],
cache~ : Bool = false,
null~ : Array[String] = [],
) -> RRuleWrapper raise DateError {
if (bymonth is Some(_) && bymonth_value is Some(_)) ||
(bymonthday is Some(_) && bymonthday_value is Some(_)) {
raise DateError::InvalidDate(
message="use array or scalar month/day keyword, not both",
)
}
let options : WrapperOptions = {
frequency,
interval,
wkst,
dtstart: wrapper_date_option(dtstart),
until: wrapper_date_option(until),
count,
cache,
bymonth_form: match bymonth_value {
Some(value) => ScalarCalendar(value)
None => if bymonth is Some(_) { ArrayCalendar } else { OmittedCalendar }
},
bymonthday_form: match bymonthday_value {
Some(value) => ScalarCalendar(value)
None =>
if bymonthday is Some(_) {
ArrayCalendar
} else {
OmittedCalendar
}
},
bymonth: match bymonth_value {
Some(value) => Some([value])
None => bymonth
},
bymonthday: match bymonthday_value {
Some(value) => Some([value])
None => bymonthday
},
byyearday,
byweekno,
byeaster,
byweekday,
byhour,
byminute,
bysecond,
bysetpos,
byweekdays,
}
let (construct, effective) = options.with_null(null).normalized(timezone)
let core = construct.build_rule(context)
{ base: timezone, context, construct, timezone: effective, core, }
}
///|
/// Update the stored construction arguments and rebuild the naive recurrence.
/// Follows the original failure stages: normalization can leave new arguments;
/// a later core error can also leave new timezone metadata with the old rule.
pub fn RRuleWrapper::set(
self : RRuleWrapper,
frequency? : RRuleFrequency,
dtstart? : RRuleDateTime,
until? : RRuleDateTime,
interval? : Int32,
wkst? : Int32,
count? : Int32,
bymonth? : Array[Int32],
bymonthday? : Array[Int32],
bymonth_value? : Int32,
bymonthday_value? : Int32,
byyearday? : Array[Int32],
byweekno? : Array[Int32],
byeaster? : Array[Int32],
byweekday? : Array[Int32],
byhour? : Array[Int32],
byminute? : Array[Int32],
bysecond? : Array[Int32],
bysetpos? : Array[Int32],
byweekdays? : Array[RelativeWeekday],
cache? : Bool,
null~ : Array[String] = [],
) -> Unit raise DateError {
let previous = self.construct
if (bymonth is Some(_) && bymonth_value is Some(_)) ||
(bymonthday is Some(_) && bymonthday_value is Some(_)) {
raise DateError::InvalidDate(
message="use array or scalar month/day keyword, not both",
)
}
let options : WrapperOptions = {
frequency: frequency.unwrap_or(previous.frequency),
interval: interval.unwrap_or(previous.interval),
wkst: wkst.unwrap_or(previous.wkst),
dtstart: match dtstart {
Some(value) => DateValue(value)
None => previous.dtstart
},
until: match until {
Some(value) => DateValue(value)
None => previous.until
},
count: relative_prefer(count, previous.count),
cache: cache.unwrap_or(previous.cache),
bymonth_form: match bymonth_value {
Some(value) => ScalarCalendar(value)
None =>
if bymonth is Some(_) {
ArrayCalendar
} else {
previous.bymonth_form
}
},
bymonthday_form: match bymonthday_value {
Some(value) => ScalarCalendar(value)
None =>
if bymonthday is Some(_) {
ArrayCalendar
} else {
previous.bymonthday_form
}
},
bymonth: match bymonth_value {
Some(value) => Some([value])
None => relative_prefer(bymonth, previous.bymonth)
},
bymonthday: match bymonthday_value {
Some(value) => Some([value])
None => relative_prefer(bymonthday, previous.bymonthday)
},
byyearday: relative_prefer(byyearday, previous.byyearday),
byweekno: relative_prefer(byweekno, previous.byweekno),
byeaster: relative_prefer(byeaster, previous.byeaster),
byweekday: if byweekdays is Some(_) && byweekday is None {
None
} else {
relative_prefer(byweekday, previous.byweekday)
},
byhour: relative_prefer(byhour, previous.byhour),
byminute: relative_prefer(byminute, previous.byminute),
bysecond: relative_prefer(bysecond, previous.bysecond),
bysetpos: relative_prefer(bysetpos, previous.bysetpos),
byweekdays: if byweekday is Some(_) && byweekdays is None {
None
} else {
relative_prefer(byweekdays, previous.byweekdays)
},
}
self.construct = options.with_null(null)
let (construct, effective) = self.construct.normalized(self.base)
self.construct = construct
self.timezone = effective
self.core = construct.build_rule(self.context)
}
///|
/// Independent construction arrays and empty cache; base/context handles are
/// retained. This explicit copy is a typed snapshot, including failed-set state.
pub fn RRuleWrapper::copy(self : RRuleWrapper) -> RRuleWrapper {
{
..self,
construct: self.construct.snapshot(),
core: {
rule: self.core.rule,
cache: recurrence_cache(self.core.cache_enabled()),
},
}
}
///|
pub fn RRuleWrapper::timezone(self : RRuleWrapper) -> DateTimeZone? {
self.timezone
}
///|
pub fn RRuleWrapper::base_timezone(self : RRuleWrapper) -> DateTimeZone? {
self.base
}
///|
pub fn RRuleWrapper::frequency(self : RRuleWrapper) -> RRuleFrequency {
self.core.frequency()
}
///|
pub fn RRuleWrapper::interval(self : RRuleWrapper) -> Int32 {
self.core.interval()
}
///|
pub fn RRuleWrapper::cache_enabled(self : RRuleWrapper) -> Bool {
self.core.cache_enabled()
}
///|
pub fn RRuleWrapper::dtstart(self : RRuleWrapper) -> RRuleDateTime {
RRuleDateTime::naive(self.core.dtstart())
}
///|
pub fn RRuleWrapper::until(self : RRuleWrapper) -> RRuleDateTime? {
self.core.until().map(RRuleDateTime::naive)
}
///|
pub fn RRuleWrapper::count_limit(self : RRuleWrapper) -> Int32? {
self.core.count()
}
///|
/// Snapshot of the underlying naive recurrence. Public RRule ordinal adapters
/// retain their existing behavior; raw_iter provides an epoch-free date stream.
pub fn RRuleWrapper::raw_rule(self : RRuleWrapper) -> RRule {
self.core
}
///|
fn RRuleWrapper::naive_reader(
self : RRuleWrapper,
lower? : DateTime,
upper? : DateTime,
) -> Iter[DateTime] {
let core = self.core
match core.cache {
Some(cache) => cache.iterator(() => rrule_naive_stream(core.rule))
None => rrule_naive_stream(core.rule, lower?, upper?)
}
}
///|
/// Explicit access to a native, nonraising Iter snapshot of naive dates.
/// checked_raw_iter() preserves checked iteration errors; this legacy extension
/// represents unreachable clock masks as an empty stream.
pub fn RRuleWrapper::raw_iter(self : RRuleWrapper) -> Iter[RRuleDateTime] {
self.naive_reader().map(RRuleDateTime::naive)
}
///|
/// Count the underlying recurrence to its natural end, preserving cache behavior.
/// Unreachable cross-field clock masks raise as the source count() does.
pub fn RRuleWrapper::occurrence_count(
self : RRuleWrapper,
) -> Int64 raise DateError {
let mut count = 0L
let reader = self.checked_reader()
while reader.next_date() is Some(_) {
count += 1L
}
count
}