use crate::error::{Error, err};
use crate::pattern::utils::{advance_by_until, closest_to, pick_best};
use crate::{DateTimeRange, Pattern, private};
use jiff::{Span, civil::DateTime};
#[derive(Debug, Clone)]
pub struct Interval {
span: Span,
offset: Option<Span>,
}
impl Interval {
#[inline]
pub fn new(span: Span) -> Interval {
Interval::try_new(span).expect("invalid interval span")
}
pub fn try_new(span: Span) -> Result<Interval, Error> {
if !span.is_positive() {
return Err(err!("interval must be positive but got {span}"));
}
Ok(Interval { span, offset: None })
}
#[inline]
#[must_use]
pub fn offset(self, offset: Span) -> Interval {
self.try_offset(offset).expect("invalid offset span")
}
pub fn try_offset(mut self, offset: Span) -> Result<Interval, Error> {
if offset.is_negative() {
return Err(err!("offset must be zero or positive but got {offset}"));
}
self.offset = Some(offset);
Ok(self)
}
fn add_offset(&self, instant: DateTime) -> Option<DateTime> {
let Some(offset) = self.offset else {
return Some(instant);
};
instant.checked_add(offset).ok()
}
}
impl Pattern for Interval {
fn next_after(&self, instant: DateTime, range: DateTimeRange) -> Option<DateTime> {
let start = self.add_offset(range.start)?;
if start >= range.end {
return None;
}
let fixpoint = self.add_offset(range.fixpoint())?;
if instant < start {
if start == fixpoint {
return Some(start);
}
let date = advance_by_until(fixpoint, self.span, start);
if date >= start {
return Some(date);
}
return date
.checked_add(self.span)
.ok()
.filter(|&next| next < range.end);
}
let date = advance_by_until(fixpoint, self.span, instant.min(range.end));
if date == range.end {
return None;
}
date.checked_add(self.span)
.ok()
.filter(|&next| next < range.end)
}
fn previous_before(&self, instant: DateTime, range: DateTimeRange) -> Option<DateTime> {
let start = self.add_offset(range.start)?;
if instant <= start || start >= range.end {
return None;
}
let fixpoint = self.add_offset(range.fixpoint())?;
let date = advance_by_until(fixpoint, self.span, instant.min(range.end));
if date < instant {
return Some(date);
}
date.checked_sub(self.span)
.ok()
.filter(|&prev| prev >= start)
}
fn closest_to(&self, instant: DateTime, range: DateTimeRange) -> Option<DateTime> {
let start = self.add_offset(range.start)?;
if start >= range.end {
return None;
}
let fixpoint = self.add_offset(range.fixpoint())?;
let date = advance_by_until(fixpoint, self.span, instant.max(start).min(range.end));
let prev = if date == range.end {
date.checked_sub(self.span).ok()
} else {
Some(date)
}
.filter(|&prev| prev >= start);
let next = date
.checked_add(self.span)
.ok()
.filter(|&next| next < range.end);
pick_best(prev, next, |prev, next| closest_to(instant, prev, next))
}
}
impl private::Sealed for Interval {}
impl PartialEq for Interval {
fn eq(&self, other: &Self) -> bool {
self.span.fieldwise() == other.span
}
}
impl PartialEq<&Interval> for Interval {
fn eq(&self, other: &&Self) -> bool {
self.span.fieldwise() == other.span
}
}
impl Eq for Interval {}