use crate::{
bounds::{Bound, BoundDate, is_leap, last_day},
types::Edtf,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Relation {
Before,
After,
Overlaps,
Contains,
Within,
Equal,
}
impl Relation {
pub const ALL: [Self; 6] = [
Self::Before,
Self::After,
Self::Overlaps,
Self::Contains,
Self::Within,
Self::Equal,
];
#[must_use]
pub const fn converse(self) -> Self {
match self {
Self::Before => Self::After,
Self::After => Self::Before,
Self::Contains => Self::Within,
Self::Within => Self::Contains,
Self::Overlaps | Self::Equal => self,
}
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Before => "before",
Self::After => "after",
Self::Overlaps => "overlaps",
Self::Contains => "contains",
Self::Within => "within",
Self::Equal => "equal",
}
}
const fn idx(self) -> usize {
match self {
Self::Before => 0,
Self::After => 1,
Self::Overlaps => 2,
Self::Contains => 3,
Self::Within => 4,
Self::Equal => 5,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Modality {
Impossible,
Possible,
Definite,
}
impl Modality {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Impossible => "impossible",
Self::Possible => "possible",
Self::Definite => "definite",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Relations {
modalities: [Modality; 6],
}
impl Relations {
const ALL_POSSIBLE: Self = Self {
modalities: [Modality::Possible; 6],
};
fn from_possible(possible: [bool; 6]) -> Self {
let count = possible.iter().filter(|p| **p).count();
let mut modalities = [Modality::Impossible; 6];
for (m, p) in modalities.iter_mut().zip(possible) {
if p {
*m = if count == 1 {
Modality::Definite
} else {
Modality::Possible
};
}
}
Self { modalities }
}
#[must_use]
pub const fn modality(self, r: Relation) -> Modality {
self.modalities[r.idx()]
}
#[must_use]
pub fn is_possible(self, r: Relation) -> bool {
self.modality(r) != Modality::Impossible
}
#[must_use]
pub fn is_definite(self, r: Relation) -> bool {
self.modality(r) == Modality::Definite
}
#[must_use]
pub fn is_impossible(self, r: Relation) -> bool {
self.modality(r) == Modality::Impossible
}
#[must_use]
pub fn definite(self) -> Option<Relation> {
Relation::ALL.into_iter().find(|r| self.is_definite(*r))
}
pub fn possible(self) -> impl Iterator<Item = Relation> {
Relation::ALL
.into_iter()
.filter(move |r| self.is_possible(*r))
}
}
impl core::fmt::Display for Relations {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let mut first = true;
for r in self.possible() {
if !first {
write!(f, ", ")?;
}
first = false;
let adverb = match self.modality(r) {
Modality::Definite => "definitely",
_ => "possibly",
};
write!(f, "{adverb} {}", r.as_str())?;
}
Ok(())
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum Ext {
NegInf,
Day(BoundDate),
PosInf,
}
const fn ext(b: Bound) -> Option<Ext> {
match b {
Bound::NegativeInfinity => Some(Ext::NegInf),
Bound::Date(d) => Some(Ext::Day(d)),
Bound::PositiveInfinity => Some(Ext::PosInf),
Bound::Unknown => None,
}
}
fn succ(e: Ext) -> Ext {
let Ext::Day(d) = e else { return e };
if d.day < last_day(d.month, is_leap(d.year)) {
Ext::Day(BoundDate {
day: d.day + 1,
..d
})
} else if d.month < 12 {
Ext::Day(BoundDate {
year: d.year,
month: d.month + 1,
day: 1,
})
} else {
d.year.checked_add(1).map_or(Ext::PosInf, |year| {
Ext::Day(BoundDate {
year,
month: 1,
day: 1,
})
})
}
}
fn pred(e: Ext) -> Ext {
let Ext::Day(d) = e else { return e };
if d.day > 1 {
Ext::Day(BoundDate {
day: d.day - 1,
..d
})
} else if d.month > 1 {
let month = d.month - 1;
Ext::Day(BoundDate {
year: d.year,
month,
day: last_day(month, is_leap(d.year)),
})
} else {
d.year.checked_sub(1).map_or(Ext::NegInf, |year| {
Ext::Day(BoundDate {
year,
month: 12,
day: 31,
})
})
}
}
fn half_overlap(lo_a: Ext, hi_a: Ext, lo_b: Ext, hi_b: Ext) -> bool {
succ(lo_a).max(lo_b) <= hi_a.min(pred(hi_b))
}
impl Edtf {
#[must_use]
pub fn relation(&self, other: &Self) -> Relations {
let a = self.bounds();
let b = other.bounds();
let (Some(a1), Some(a2), Some(b1), Some(b2)) = (
ext(a.earliest),
ext(a.latest),
ext(b.earliest),
ext(b.latest),
) else {
return Relations::ALL_POSSIBLE;
};
let intersects = a1 <= b2 && b1 <= a2;
Relations::from_possible([
a1 < b2, b1 < a2, half_overlap(a1, a2, b1, b2) || half_overlap(b1, b2, a1, a2), intersects && a1 < a2, intersects && b1 < b2, intersects, ])
}
}