use crate::bounds::{is_leap, last_day, Bound, BoundDate};
use crate::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: [Relation; 6] = [
Relation::Before,
Relation::After,
Relation::Overlaps,
Relation::Contains,
Relation::Within,
Relation::Equal,
];
pub fn converse(self) -> Relation {
match self {
Relation::Before => Relation::After,
Relation::After => Relation::Before,
Relation::Contains => Relation::Within,
Relation::Within => Relation::Contains,
Relation::Overlaps | Relation::Equal => self,
}
}
pub fn as_str(self) -> &'static str {
match self {
Relation::Before => "before",
Relation::After => "after",
Relation::Overlaps => "overlaps",
Relation::Contains => "contains",
Relation::Within => "within",
Relation::Equal => "equal",
}
}
fn idx(self) -> usize {
match self {
Relation::Before => 0,
Relation::After => 1,
Relation::Overlaps => 2,
Relation::Contains => 3,
Relation::Within => 4,
Relation::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 {
pub fn as_str(self) -> &'static str {
match self {
Modality::Impossible => "impossible",
Modality::Possible => "possible",
Modality::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: Relations = Relations {
modalities: [Modality::Possible; 6],
};
fn from_possible(possible: [bool; 6]) -> Relations {
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
};
}
}
Relations { modalities }
}
pub fn modality(self, r: Relation) -> Modality {
self.modalities[r.idx()]
}
pub fn is_possible(self, r: Relation) -> bool {
self.modality(r) != Modality::Impossible
}
pub fn is_definite(self, r: Relation) -> bool {
self.modality(r) == Modality::Definite
}
pub fn is_impossible(self, r: Relation) -> bool {
self.modality(r) == Modality::Impossible
}
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,
}
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 {
match d.year.checked_add(1) {
Some(year) => Ext::Day(BoundDate {
year,
month: 1,
day: 1,
}),
None => Ext::PosInf,
}
}
}
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 {
match d.year.checked_sub(1) {
Some(year) => Ext::Day(BoundDate {
year,
month: 12,
day: 31,
}),
None => Ext::NegInf,
}
}
}
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 {
pub fn relation(&self, other: &Edtf) -> 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, ])
}
}