use chrono::Duration;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use std::str::FromStr;
use thiserror::Error;
#[derive(Debug, Error, PartialEq, Eq)]
pub enum IntervalError {
#[error("unknown interval: {0}")]
Unknown(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Interval {
Minute,
Hour,
Day,
Week,
}
impl Interval {
pub fn as_str(&self) -> &'static str {
match self {
Interval::Minute => "1m",
Interval::Hour => "1h",
Interval::Day => "1d",
Interval::Week => "1w",
}
}
pub fn periods_per_year(&self, is_crypto: bool) -> u32 {
match (self, is_crypto) {
(Interval::Minute, true) => 525_600, (Interval::Minute, false) => 98_280, (Interval::Hour, true) => 8_760, (Interval::Hour, false) => 1_638, (Interval::Day, true) => 365,
(Interval::Day, false) => 252,
(Interval::Week, _) => 52,
}
}
pub fn lower_intervals(&self) -> &'static [Interval] {
match self {
Interval::Minute => &[],
Interval::Hour => &[Interval::Minute],
Interval::Day => &[Interval::Hour, Interval::Minute],
Interval::Week => &[Interval::Day, Interval::Hour, Interval::Minute],
}
}
}
impl fmt::Display for Interval {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl Serialize for Interval {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for Interval {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
s.parse().map_err(serde::de::Error::custom)
}
}
impl FromStr for Interval {
type Err = IntervalError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"1m" | "m" | "min" | "minute" => Ok(Interval::Minute),
"1h" | "h" | "hour" | "hourly" => Ok(Interval::Hour),
"1d" | "d" | "day" | "daily" => Ok(Interval::Day),
"1w" | "w" | "week" | "weekly" => Ok(Interval::Week),
_ => Err(IntervalError::Unknown(s.to_string())),
}
}
}
pub fn interval_duration(interval: Interval) -> Duration {
match interval {
Interval::Minute => Duration::minutes(1),
Interval::Hour => Duration::hours(1),
Interval::Day => Duration::days(1),
Interval::Week => Duration::weeks(1),
}
}
#[cfg(test)]
#[path = "interval_tests.rs"]
mod tests;