use crate::formatting::{format_duration, format_range, to_duration};
use crate::types::{BrightDateValue, BrightDuration};
use crate::BrightDate;
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct BrightDateInterval {
pub start: BrightDate,
pub end: BrightDate,
}
impl BrightDateInterval {
pub fn new(start: BrightDate, end: BrightDate) -> Self {
assert!(
start.value <= end.value,
"BrightDateInterval: start ({}) must be <= end ({})",
start.value,
end.value
);
Self { start, end }
}
pub fn from_values(start: BrightDateValue, end: BrightDateValue) -> Self {
Self::new(BrightDate::from_value(start), BrightDate::from_value(end))
}
pub fn from_dates(
start: chrono::DateTime<chrono::Utc>,
end: chrono::DateTime<chrono::Utc>,
) -> Result<Self, crate::types::BrightDateError> {
use crate::conversions::from_unix_ms;
let s = from_unix_ms(start.timestamp_millis() as f64)?;
let e = from_unix_ms(end.timestamp_millis() as f64)?;
Ok(Self::from_values(s, e))
}
pub fn from_iso(start: &str, end: &str) -> Result<Self, crate::types::BrightDateError> {
use crate::conversions::from_iso;
let s = from_iso(start)?;
let e = from_iso(end)?;
Ok(Self::from_values(s, e))
}
pub fn from_duration(start: BrightDate, duration_days: f64) -> Self {
let end = start.add_days(duration_days);
Self::new(start, end)
}
#[allow(clippy::misnamed_getters)]
pub fn duration(&self) -> f64 {
self.end.value - self.start.value
}
pub fn duration_metric(&self) -> BrightDuration {
to_duration(self.duration())
}
pub fn contains(&self, point: &BrightDate) -> bool {
point.value >= self.start.value && point.value <= self.end.value
}
pub fn contains_value(&self, value: f64) -> bool {
value >= self.start.value && value <= self.end.value
}
pub fn overlaps(&self, other: &Self) -> bool {
self.start.value <= other.end.value && self.end.value >= other.start.value
}
pub fn intersection(&self, other: &Self) -> Option<Self> {
let s = self.start.value.max(other.start.value);
let e = self.end.value.min(other.end.value);
if s <= e {
Some(Self::from_values(s, e))
} else {
None
}
}
pub fn union(&self, other: &Self) -> Option<Self> {
if self.overlaps(other) || self.adjacent_to(other) {
let s = self.start.value.min(other.start.value);
let e = self.end.value.max(other.end.value);
Some(Self::from_values(s, e))
} else {
None
}
}
pub fn adjacent_to(&self, other: &Self) -> bool {
self.end.value == other.start.value || other.end.value == self.start.value
}
pub fn encloses(&self, other: &Self) -> bool {
self.start.value <= other.start.value && self.end.value >= other.end.value
}
pub fn split(&self, count: usize) -> Vec<Self> {
assert!(count >= 1, "Count must be at least 1");
let step = self.duration() / count as f64;
(0..count)
.map(|i| {
let s = self.start.value + step * i as f64;
let e = s + step;
Self::from_values(s, e)
})
.collect()
}
pub fn expand(&self, amount: f64) -> Self {
Self::from_values(self.start.value - amount, self.end.value + amount)
}
pub fn shrink(&self, amount: f64) -> Option<Self> {
let s = self.start.value + amount;
let e = self.end.value - amount;
if s <= e { Some(Self::from_values(s, e)) } else { None }
}
pub fn shift(&self, amount: f64) -> Self {
Self::from_values(self.start.value + amount, self.end.value + amount)
}
pub fn iterate(&self, step: f64) -> Vec<BrightDate> {
assert!(step > 0.0, "Step must be positive");
let mut result = Vec::new();
let mut current = self.start.value;
while current <= self.end.value + f64::EPSILON {
result.push(BrightDate::from_value(current));
current += step;
}
result
}
pub fn sample(&self, count: usize) -> Vec<BrightDate> {
if count == 0 {
return vec![];
}
if count == 1 {
return vec![self.midpoint()];
}
let step = self.duration() / (count - 1) as f64;
(0..count)
.map(|i| BrightDate::from_value(self.start.value + step * i as f64))
.collect()
}
pub fn midpoint(&self) -> BrightDate {
self.start.lerp(&self.end, 0.5)
}
pub fn format_duration_str(&self) -> String {
format_duration(self.duration())
}
pub fn format_range(&self) -> String {
format_range(self.start.value, self.end.value, self.start.precision)
}
}
impl std::fmt::Display for BrightDateInterval {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.format_range())
}
}