use chrono::TimeZone;
use chrono::DateTime;
use chrono::TimeDelta;
use chrono::offset::Utc;
use serde::{Serialize,Deserialize};
use std::mem::swap;
use std::ops::Sub;
use std::time::Duration;
#[derive(Clone,Debug,Default,PartialEq,Eq,Serialize,Deserialize)]
#[serde(try_from="UncheckedUtcTimespan")]
pub struct UtcTimespan {
start: Option<DateTime<Utc>>,
end: Option<DateTime<Utc>>,
}
impl UtcTimespan {
pub fn infinite() -> Self {
UtcTimespan { start: None, end: None }
}
pub fn new(a: Option<DateTime<Utc>>, b: Option<DateTime<Utc>>) -> Self {
let mut ts = UtcTimespan { start: a, end: b };
ts.fix_if_reversed();
return ts;
}
pub fn new_with_loud_error(
start: Option<DateTime<Utc>>, end: Option<DateTime<Utc>>
) -> Result<Self,String> {
let ts = UtcTimespan { start, end };
if ts.is_reversed() {
return Err("Timespan: End can't be before Start if both are defined.".to_string());
} else {
return Ok(ts);
}
}
pub fn set_start(&mut self, new_start: Option<DateTime<Utc>>) {
self.start = new_start;
if self.is_reversed() {
self.end = self.start;
}
}
pub fn set_end(&mut self, new_end: Option<DateTime<Utc>>) {
self.end = new_end;
if self.is_reversed() {
self.start = self.end;
}
}
}
impl UtcTimespan {
pub fn start(&self) -> &Option<DateTime<Utc>> {
&self.start
}
pub fn end(&self) -> &Option<DateTime<Utc>> {
&self.end
}
pub fn delta(&self) -> Option<TimeDelta> {
match self {
UtcTimespan { start: Some(start), end: Some(end) } =>
Some((*end).sub(start)),
_ => None
}
}
pub fn duration(&self) -> Option<Duration> {
self.delta().map(|d| d.abs().to_std().expect("time delta must be absolute"))
}
pub fn is_infinite(&self) -> bool {
return self.start.is_none() || self.end.is_none();
}
pub fn is_all_of_time(&self) -> bool {
return self.start.is_none() && self.end.is_none();
}
pub fn is_point(&self) -> bool {
return self.start == self.end && self.start.is_some();
}
pub fn contains_point(&self, time: &DateTime<Utc>) -> bool {
if self.is_point_before_start(time) {
return false;
}
if self.is_point_after_end(time) {
return false;
}
return true;
}
pub fn is_point_before_start(&self, time: &DateTime<Utc>) -> bool {
if let Some(ref start) = self.start {
return time < start;
}
return false;
}
pub fn is_point_after_start(&self, time: &DateTime<Utc>) -> bool {
if let Some(ref start) = self.start {
return start < time;
}
return true;
}
pub fn is_point_before_end(&self, time: &DateTime<Utc>) -> bool {
if let Some(ref end) = self.end {
return time < end;
}
return true;
}
pub fn is_point_after_end(&self, time: &DateTime<Utc>) -> bool {
if let Some(ref end) = self.end {
return end < time;
}
return false;
}
pub fn contains_span(&self, other: &UtcTimespan) -> bool {
match other {
UtcTimespan { start: None, end: None } => {
return self.is_all_of_time();
},
UtcTimespan { start: None, end: Some(end) } => {
return self.start.is_none() && !self.is_point_after_end(end);
},
UtcTimespan { start: Some(start), end: None } => {
return !self.is_point_before_start(start) && self.end.is_none();
},
UtcTimespan { start: Some(start), end: Some(end) } => {
return !self.is_point_before_start(start) && !self.is_point_after_end(end);
},
}
}
pub fn calculate_overlap(&self, other: &UtcTimespan) -> Option<UtcTimespan> {
let span = UtcTimespan {
start: max_of(self.start.as_ref(), other.start.as_ref()),
end: min_of(self.end.as_ref(), other.end.as_ref()),
};
if span.is_reversed() {
return None;
} else {
return Some(span);
}
}
}
fn min_of<Tz: TimeZone>(
a: Option<&DateTime<Tz>>,
b: Option<&DateTime<Tz>>
) -> Option<DateTime<Tz>> {
match (a,b) {
(Some(a), Some(b)) => if a < b { Some(a) } else { Some(b) },
(Some(a), None) => Some(a),
(_, b) => b,
}.cloned()
}
fn max_of<Tz: TimeZone>(
a: Option<&DateTime<Tz>>,
b: Option<&DateTime<Tz>>
) -> Option<DateTime<Tz>> {
match (a,b) {
(Some(a), Some(b)) => if a > b { Some(a) } else { Some(b) },
(Some(a), None) => Some(a),
(_, b) => b,
}.cloned()
}
impl UtcTimespan {
fn is_reversed(&self) -> bool {
if let Some(ref start) = self.start {
if let Some(ref end) = self.end {
return start > end;
}
}
return false;
}
fn fix_if_reversed(&mut self) {
if self.is_reversed() {
swap(&mut self.start, &mut self.end);
}
}
}
#[derive(Clone,Debug,Default,PartialEq,Eq,Serialize,Deserialize)]
pub struct UncheckedUtcTimespan {
pub start: Option<DateTime<Utc>>,
pub end: Option<DateTime<Utc>>,
}
impl UncheckedUtcTimespan{
pub fn to_checked_with_silent_swap(self) -> UtcTimespan {
UtcTimespan::new(self.start, self.end)
}
pub fn to_checked_with_loud_error(self) -> Result<UtcTimespan,String> {
UtcTimespan::new_with_loud_error(self.start, self.end)
}
}
impl TryFrom<UncheckedUtcTimespan> for UtcTimespan {
type Error = String;
fn try_from(value: UncheckedUtcTimespan) -> Result<Self, Self::Error> {
value.to_checked_with_loud_error()
}
}