///|
/// A Matplotlib RRuleLocator retaining a live mutable wrapper. Its display
/// timezone is captured independently of the wrapper's base/effective zone.
pub struct RRuleLocator {
priv rule : RRuleWrapper
priv timezone : DateTimeZone
priv year_base : Int32?
}
///|
pub fn RRuleLocator::new(
rule : RRuleWrapper,
tz? : String,
timezone? : DateTimeZone,
) -> RRuleLocator raise DateError {
{ rule, timezone: selected_timezone(tz, timezone), year_base: None, }
}
///|
/// Return the shared wrapper; later set calls affect this locator and adapters.
pub fn RRuleLocator::rule(self : RRuleLocator) -> RRuleWrapper {
self.rule
}
///|
pub fn RRuleLocator::timezone(self : RRuleLocator) -> DateTimeZone {
self.timezone
}
///|
pub fn RRuleLocator::with_timezone(
self : RRuleLocator,
timezone : DateTimeZone,
) -> RRuleLocator {
{ ..self, timezone, }
}
///|
pub fn RRuleLocator::with_offset(
self : RRuleLocator,
offset_minutes : Int32,
) -> RRuleLocator raise DateError {
self.with_timezone(fixed_timezone(offset_minutes))
}
///|
pub fn RRuleLocator::unit_for_frequency(frequency : RRuleFrequency) -> Double {
recurrence_unit(frequency)
}
///|
pub fn RRuleLocator::unit_scale(self : RRuleLocator) -> Double {
recurrence_unit(self.rule.frequency())
}
///|
pub fn RRuleLocator::interval(self : RRuleLocator) -> Int32 {
self.rule.interval()
}
///|
fn RRuleDateTime::with_arithmetic_date(
self : RRuleDateTime,
value : DateTime,
) -> RRuleDateTime {
if self.is_naive() {
RRuleDateTime::naive(value)
} else {
RRuleDateTime::aware(value)
}
}
///|
/// Expand the range by relativedelta, rebuilding the shared wrapper as the
/// original locator does. Direct bounds retain their own awareness and zones.
///
/// ```mbt check
/// test {
/// let first = @date.RRuleDateTime::naive(@date.datetime(2024, 1, 1))
/// let last = @date.RRuleDateTime::naive(@date.datetime(2024, 1, 3))
/// let rule = @date.RRuleWrapper::new(@date.Daily, dtstart=first)
/// let locator = @date.RRuleLocator::new(rule, tz="UTC")
/// assert_eq(locator.tick_values(first, last).length(), 3)
/// rule.set(frequency=@date.Hourly, interval=12)
/// assert_eq(locator.as_locator().interval(), 12)
/// }
/// ```
///
/// Empty rules return the original endpoints. Unlike the numeric adapter,
/// reversed direct bounds follow the source's signed calendar expansion.
pub fn RRuleLocator::tick_values(
self : RRuleLocator,
first : RRuleDateTime,
last : RRuleDateTime,
) -> Array[Double] raise DateError {
let (start, stop) = if self.year_base is Some(base) {
self.year_bounds(base, first, last)
} else {
if first.is_naive() != last.is_naive() {
raise DateError::InvalidDate(
message="cannot compare naive and aware rule dates",
)
}
let delta = RelativeDelta::between(last.date(), first.date())
let start = first.with_arithmetic_date(
first.date().sub_relative_delta(delta),
) catch {
_ => RRuleDateTime::aware(try! datetime(1, 1, 1))
}
let stop = last.with_arithmetic_date(last.date().add_relative_delta(delta)) catch {
_ =>
RRuleDateTime::aware(
try! datetime(9999, 12, 31, hour=23, minute=59, second=59),
)
}
(start, stop)
}
self.rule.set(dtstart=start, until=stop)
let values = self.rule.between(
if self.year_base is Some(_) {
start
} else {
first
},
if self.year_base is Some(_) {
stop
} else {
last
},
inc=true,
)
let values = if values.is_empty() { [first, last] } else { values }
date_values_to_numbers(values, self.rule.context)
}
///|
/// Share the wrapper with numeric axes. copy() of this DateLocator retains that
/// handle, so locator window updates and caller set calls remain observable.
pub fn RRuleLocator::as_locator(self : RRuleLocator) -> DateLocator {
{
kind: if self.year_base is Some(_) {
Years
} else {
Recurrence
},
interval: self.interval(),
selector: 0,
secondary: 0,
selector_enabled: false,
offset_minutes: self.timezone.initial_offset_minutes(),
timezone: Some(self.timezone),
selection: None,
secondary_selection: None,
holidays: [],
rule: None,
automatic: None,
wrapper_locator: Some(self),
microsecond_object: None,
}
}
///|
/// Return the live wrapper locator behind this adapter, if any.
pub fn DateLocator::rrule_locator(self : DateLocator) -> RRuleLocator? {
self.wrapper_locator
}
///|
fn RRuleLocator::numeric_tick_values(
self : RRuleLocator,
lower : Double,
upper : Double,
) -> Array[Double] raise DateError {
let first = RRuleDateTime::aware(
self.timezone.to_datetime(Double::min(lower, upper)),
)
let last = RRuleDateTime::aware(
self.timezone.to_datetime(Double::max(lower, upper)),
)
let values = self.tick_values(first, last)
if lower <= upper {
values
} else {
values.rev()
}
}