use chrono::{DateTime, Datelike as _, DurationRound as _, TimeDelta, TimeZone as _, Utc, Weekday};
use strum_macros::{Display, EnumIter};
use crate::core::{CoreError, CoreResult};
#[derive(
Debug,
Display,
serde::Serialize,
serde::Deserialize,
Clone,
Copy,
PartialEq,
EnumIter,
Eq,
PartialOrd,
Ord,
Hash,
)]
pub enum Interval {
OneMinute,
FiveMinutes,
FifteenMinutes,
ThirtyMinutes,
OneHour,
FourHours,
OneDay,
OneWeek,
OneMonth,
}
impl Interval {
pub fn next_interval_to_zoom_out(&self) -> Option<Interval> {
match self {
Interval::OneMinute => Some(Interval::FiveMinutes),
Interval::FiveMinutes => Some(Interval::FifteenMinutes),
Interval::FifteenMinutes => Some(Interval::OneHour),
Interval::ThirtyMinutes => Some(Interval::OneHour),
Interval::OneHour => Some(Interval::OneDay),
Interval::FourHours => Some(Interval::OneDay),
Interval::OneDay => Some(Interval::OneWeek),
Interval::OneWeek => None,
Interval::OneMonth => None,
}
}
pub fn time_delta(&self) -> TimeDelta {
match self {
Interval::OneMinute => TimeDelta::minutes(1),
Interval::FiveMinutes => TimeDelta::minutes(5),
Interval::FifteenMinutes => TimeDelta::minutes(15),
Interval::ThirtyMinutes => TimeDelta::minutes(30),
Interval::OneHour => TimeDelta::hours(1),
Interval::FourHours => TimeDelta::hours(4),
Interval::OneDay => TimeDelta::days(1),
Interval::OneWeek => TimeDelta::weeks(1),
Interval::OneMonth => TimeDelta::days(30),
}
}
pub fn get_time_bounds(
&self,
target: DateTime<Utc>,
) -> CoreResult<(DateTime<Utc>, DateTime<Utc>)> {
match self {
Interval::OneWeek => {
let mut start = target.duration_trunc(Interval::OneDay.time_delta())?;
let mut end = target.duration_round_up(Interval::OneDay.time_delta())?;
while start.weekday() != Weekday::Mon {
start -= TimeDelta::days(1);
}
while end.weekday() != Weekday::Mon && end <= start {
end += TimeDelta::days(1);
}
Ok((end, start))
}
Interval::OneMonth => {
let start = Utc
.with_ymd_and_hms(target.year(), target.month(), 1, 0, 0, 0)
.single()
.ok_or(CoreError::comput_error("Error creating start date"))?;
let end = if target.month() == 12 {
Utc.with_ymd_and_hms(target.year() + 1, 1, 1, 0, 0, 0)
} else {
Utc.with_ymd_and_hms(target.year(), target.month() + 1, 1, 0, 0, 0)
}
.single()
.ok_or(CoreError::comput_error("Error creating start date"))?;
Ok((start, end))
}
_ => {
let start = target.duration_trunc(self.time_delta())?;
let end = target.duration_round_up(self.time_delta())?;
if start != end {
Ok((start, end))
} else {
Ok((start, end + self.time_delta()))
}
}
}
}
pub fn short_string(&self) -> String {
match self {
Interval::OneMinute => "1m",
Interval::FiveMinutes => "5m",
Interval::FifteenMinutes => "15m",
Interval::ThirtyMinutes => "30m",
Interval::OneHour => "1h",
Interval::FourHours => "4h",
Interval::OneDay => "1d",
Interval::OneWeek => "1w",
Interval::OneMonth => "1M",
}
.to_string()
}
}