use std::cmp::{max, min, Ordering};
use std::ops::{Range, RangeInclusive};
use chrono::NaiveDateTime;
use opening_hours_syntax::rules::RuleKind;
use opening_hours_syntax::sorted_vec::UniqueSortedVec;
#[non_exhaustive]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DateTimeRange<'c> {
pub range: Range<NaiveDateTime>,
pub kind: RuleKind,
pub comments: UniqueSortedVec<&'c str>,
}
impl<'c> DateTimeRange<'c> {
pub(crate) fn new_with_sorted_comments(
range: Range<NaiveDateTime>,
kind: RuleKind,
comments: UniqueSortedVec<&'c str>,
) -> Self {
Self { range, kind, comments }
}
pub fn comments(&self) -> &[&'c str] {
&self.comments
}
pub fn into_comments(self) -> UniqueSortedVec<&'c str> {
self.comments
}
}
pub(crate) trait WrappingRange<T> {
fn wrapping_contains(&self, elt: &T) -> bool;
}
impl<T: PartialOrd> WrappingRange<T> for RangeInclusive<T> {
fn wrapping_contains(&self, elt: &T) -> bool {
if self.start() <= self.end() {
self.contains(elt)
} else {
self.start() <= elt || elt <= self.end()
}
}
}
pub(crate) trait RangeExt<T> {
fn compare(&self, elt: &T) -> Ordering;
}
impl<T: PartialOrd> RangeExt<T> for RangeInclusive<T> {
fn compare(&self, elt: &T) -> Ordering {
debug_assert!(self.start() <= self.end());
if elt < self.start() {
Ordering::Less
} else if elt > self.end() {
Ordering::Greater
} else {
Ordering::Equal
}
}
}
impl<T: PartialOrd> RangeExt<T> for Range<T> {
fn compare(&self, elt: &T) -> Ordering {
debug_assert!(self.start <= self.end);
if elt < &self.start {
Ordering::Less
} else if elt >= &self.end {
Ordering::Greater
} else {
Ordering::Equal
}
}
}
pub(crate) fn ranges_union<T: Ord>(
ranges: impl IntoIterator<Item = Range<T>>,
) -> impl Iterator<Item = Range<T>> {
let mut ranges: Vec<_> = ranges.into_iter().collect();
ranges.sort_unstable_by(|r1, r2| r1.start.cmp(&r2.start));
let mut ranges = ranges.into_iter();
let mut current_opt = ranges.next();
std::iter::from_fn(move || {
if let Some(ref mut current) = current_opt {
#[allow(clippy::while_let_on_iterator)]
while let Some(item) = ranges.next() {
if current.end >= item.start {
if item.end > current.end {
current.end = item.end;
}
} else {
return Some(current_opt.replace(item).unwrap());
}
}
Some(current_opt.take().unwrap())
} else {
None
}
})
}
pub(crate) fn range_intersection<T: Ord>(range_1: Range<T>, range_2: Range<T>) -> Option<Range<T>> {
let result = max(range_1.start, range_2.start)..min(range_1.end, range_2.end);
if result.start < result.end {
Some(result)
} else {
None
}
}
#[cfg(test)]
mod test {
use super::{range_intersection, ranges_union};
#[test]
fn test_unions() {
assert_eq!(
&ranges_union([1..5, 0..1, 3..7, 8..9]).collect::<Vec<_>>(),
&[0..7, 8..9]
);
}
#[test]
fn test_intersection() {
assert!(range_intersection(0..1, 1..2).is_none());
assert_eq!(range_intersection(0..3, 1..2).unwrap(), 1..2);
assert_eq!(range_intersection(0..3, 2..4).unwrap(), 2..3);
}
}