Skip to content

Commit

Permalink
(re)optimize 24/7
Browse files Browse the repository at this point in the history
  • Loading branch information
remi-dupre committed Nov 26, 2024
1 parent 6ce92dd commit 28e9ffb
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 60 deletions.
12 changes: 10 additions & 2 deletions src/opening_hours.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ use crate::context::Context;
use crate::date_filter::DateFilter;
use crate::error::ParserError;
use crate::schedule::Schedule;
use crate::time_filter::{time_selector_intervals_at, time_selector_intervals_at_next_day};
use crate::time_filter::{
time_selector_intervals_at, time_selector_intervals_at_next_day, TimeFilter,
};
use crate::DateTimeRange;

/// The upper bound of dates handled by specification
Expand Down Expand Up @@ -90,7 +92,13 @@ impl OpeningHours {
fn next_change_hint(&self, date: NaiveDate) -> Option<NaiveDate> {
(self.expr.rules)
.iter()
.map(|rule| rule.day_selector.next_change_hint(date, &self.ctx))
.map(|rule| {
if rule.time_selector.is_immutable_full_day() && rule.day_selector.is_empty() {
Some(DATE_LIMIT.date())
} else {
rule.day_selector.next_change_hint(date, &self.ctx)
}
})
.min()
.flatten()
}
Expand Down
47 changes: 22 additions & 25 deletions src/tests/regression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,31 +126,28 @@ fn s009_pj_no_open_before_separator() {
);
}

// TODO: this is *has* to be slow with current implementation as the date
// filter "24/7" may change every day. This could be improved through
// a special filter on immutable full schedules.
// #[test]
// fn s010_pj_slow_after_24_7() -> Result<(), Error> {
// exec_with_timeout(Duration::from_millis(100), || {
// assert!("24/7 open ; 2021Jan-Feb off"
// .parse::<OpeningHours>()?
// .next_change(datetime!("2021-07-09 19:30"))
// .is_none());
//
// Ok::<(), Error>(())
// })?;
//
// exec_with_timeout(Duration::from_millis(100), || {
// assert!("24/7 open ; 2021 Jan 01-Feb 10 off"
// .parse::<OpeningHours>()?
// .next_change(datetime!("2021-07-09 19:30"))
// .is_none());
//
// Ok::<(), Error>(())
// })?;
//
// Ok(())
// }
#[test]
fn s010_pj_slow_after_24_7() -> Result<(), Error> {
exec_with_timeout(Duration::from_millis(100), || {
assert!("24/7 open ; 2021Jan-Feb off"
.parse::<OpeningHours>()?
.next_change(datetime!("2021-07-09 19:30"))
.is_none());

Ok::<(), Error>(())
})?;

exec_with_timeout(Duration::from_millis(100), || {
assert!("24/7 open ; 2021 Jan 01-Feb 10 off"
.parse::<OpeningHours>()?
.next_change(datetime!("2021-07-09 19:30"))
.is_none());

Ok::<(), Error>(())
})?;

Ok(())
}

#[test]
fn s011_fuzz_extreme_year() -> Result<(), Error> {
Expand Down
96 changes: 63 additions & 33 deletions src/time_filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,20 @@ pub(crate) fn time_selector_intervals_at(
time_selector: &ts::TimeSelector,
date: NaiveDate,
) -> impl Iterator<Item = Range<ExtendedTime>> + '_ {
ranges_union(
time_selector_as_naive(time_selector, date).filter_map(|range| {
let dstart = ExtendedTime::new(0, 0).unwrap();
let dend = ExtendedTime::new(24, 0).unwrap();
range_intersection(range, dstart..dend)
}),
)
ranges_union(time_selector.as_naive(date).filter_map(|range| {
let dstart = ExtendedTime::new(0, 0).unwrap();
let dend = ExtendedTime::new(24, 0).unwrap();
range_intersection(range, dstart..dend)
}))
}

pub(crate) fn time_selector_intervals_at_next_day(
time_selector: &ts::TimeSelector,
date: NaiveDate,
) -> impl Iterator<Item = Range<ExtendedTime>> + '_ {
ranges_union(
time_selector_as_naive(time_selector, date)
time_selector
.as_naive(date)
.filter_map(|range| {
let dstart = ExtendedTime::new(24, 0).unwrap();
let dend = ExtendedTime::new(48, 0).unwrap();
Expand All @@ -39,27 +38,43 @@ pub(crate) fn time_selector_intervals_at_next_day(
)
}

fn time_selector_as_naive(
time_selector: &ts::TimeSelector,
date: NaiveDate,
) -> impl Iterator<Item = Range<ExtendedTime>> + '_ {
time_selector
.time
.iter()
.map(move |span| span.as_naive(date))
}

/// Trait used to project a time representation to its naive representation at
/// a given date.
pub(crate) trait AsNaive {
type Output;
fn as_naive(&self, date: NaiveDate) -> Self::Output;
pub(crate) trait TimeFilter {
type Output<'a>
where
Self: 'a;

/// Check if this filter is always matching the full day.
fn is_immutable_full_day(&self) -> bool {
false
}

/// Project a time representation to its naive representation at a given date.
fn as_naive(&self, date: NaiveDate) -> Self::Output<'_>;
}

impl AsNaive for ts::TimeSpan {
type Output = Range<ExtendedTime>;
impl TimeFilter for ts::TimeSelector {
type Output<'a> = NaiveTimeSelectorIterator<'a>;

fn as_naive(&self, date: NaiveDate) -> Self::Output {
fn is_immutable_full_day(&self) -> bool {
self.time.iter().all(|span| span.is_immutable_full_day())
}

fn as_naive(&self, date: NaiveDate) -> Self::Output<'_> {
NaiveTimeSelectorIterator { date, inner: self.time.iter() }
}
}

impl TimeFilter for ts::TimeSpan {
type Output<'a> = Range<ExtendedTime>;

fn is_immutable_full_day(&self) -> bool {
self.range.start == ts::Time::Fixed(ExtendedTime::new(0, 0).unwrap())
&& self.range.end == ts::Time::Fixed(ExtendedTime::new(24, 0).unwrap())
}

fn as_naive(&self, date: NaiveDate) -> Self::Output<'_> {
let start = self.range.start.as_naive(date);
let end = self.range.end.as_naive(date);

Expand All @@ -78,32 +93,32 @@ impl AsNaive for ts::TimeSpan {
}
}

impl AsNaive for ts::Time {
type Output = ExtendedTime;
impl TimeFilter for ts::Time {
type Output<'a> = ExtendedTime;

fn as_naive(&self, date: NaiveDate) -> Self::Output {
fn as_naive(&self, date: NaiveDate) -> Self::Output<'_> {
match self {
ts::Time::Fixed(naive) => *naive,
ts::Time::Variable(variable) => variable.as_naive(date),
}
}
}

impl AsNaive for ts::VariableTime {
type Output = ExtendedTime;
impl TimeFilter for ts::VariableTime {
type Output<'a> = ExtendedTime;

fn as_naive(&self, date: NaiveDate) -> Self::Output {
fn as_naive(&self, date: NaiveDate) -> Self::Output<'_> {
self.event
.as_naive(date)
.add_minutes(self.offset)
.unwrap_or_else(|| ExtendedTime::new(0, 0).unwrap())
}
}

impl AsNaive for ts::TimeEvent {
type Output = ExtendedTime;
impl TimeFilter for ts::TimeEvent {
type Output<'a> = ExtendedTime;

fn as_naive(&self, _date: NaiveDate) -> Self::Output {
fn as_naive(&self, _date: NaiveDate) -> Self::Output<'_> {
// TODO: real computation based on the day (and position/timezone?)
match self {
Self::Dawn => ExtendedTime::new(6, 0).unwrap(),
Expand All @@ -113,3 +128,18 @@ impl AsNaive for ts::TimeEvent {
}
}
}

/// Output type for [`TimeSelector::as_naive`].
pub(crate) struct NaiveTimeSelectorIterator<'a> {
date: NaiveDate,
inner: std::slice::Iter<'a, ts::TimeSpan>,
}

impl<'a> Iterator for NaiveTimeSelectorIterator<'a> {
type Item = <ts::TimeSpan as TimeFilter>::Output<'a>;

fn next(&mut self) -> Option<Self::Item> {
let span = self.inner.next()?;
Some(span.as_naive(self.date))
}
}

0 comments on commit 28e9ffb

Please sign in to comment.