use chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, Utc};
use std::ops::{Add, Sub};
use crate::error::{PandRSError, Result};
use crate::na::NA;
use crate::temporal::frequency::Frequency;
pub trait Temporal:
Clone
+ std::fmt::Debug
+ PartialOrd
+ Add<Duration, Output = Self>
+ Sub<Duration, Output = Self>
+ 'static
{
fn now() -> Self;
fn duration_between(&self, other: &Self) -> Duration;
fn to_utc(&self) -> DateTime<Utc>;
fn from_str(s: &str) -> Result<Self>;
fn to_string(&self) -> String;
}
impl Temporal for DateTime<Utc> {
fn now() -> Self {
Utc::now()
}
fn duration_between(&self, other: &Self) -> Duration {
if self > other {
*self - *other
} else {
*other - *self
}
}
fn to_utc(&self) -> DateTime<Utc> {
*self
}
fn from_str(s: &str) -> Result<Self> {
match s.parse::<DateTime<Utc>>() {
Ok(dt) => Ok(dt),
Err(e) => Err(PandRSError::Format(format!(
"Date-time parsing error: {}",
e
))),
}
}
fn to_string(&self) -> String {
self.to_rfc3339()
}
}
impl Temporal for DateTime<Local> {
fn now() -> Self {
Local::now()
}
fn duration_between(&self, other: &Self) -> Duration {
if self > other {
*self - *other
} else {
*other - *self
}
}
fn to_utc(&self) -> DateTime<Utc> {
self.with_timezone(&Utc)
}
fn from_str(s: &str) -> Result<Self> {
match s.parse::<DateTime<Local>>() {
Ok(dt) => Ok(dt),
Err(e) => Err(PandRSError::Format(format!(
"Date-time parsing error: {}",
e
))),
}
}
fn to_string(&self) -> String {
self.to_rfc3339()
}
}
impl Temporal for NaiveDateTime {
fn now() -> Self {
Local::now().naive_local()
}
fn duration_between(&self, other: &Self) -> Duration {
if self > other {
*self - *other
} else {
*other - *self
}
}
fn to_utc(&self) -> DateTime<Utc> {
DateTime::<Utc>::from_naive_utc_and_offset(*self, Utc)
}
fn from_str(s: &str) -> Result<Self> {
match NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S") {
Ok(dt) => Ok(dt),
Err(_) => match NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S") {
Ok(dt) => Ok(dt),
Err(e) => Err(PandRSError::Format(format!(
"Date-time parsing error: {}",
e
))),
},
}
}
fn to_string(&self) -> String {
self.format("%Y-%m-%d %H:%M:%S").to_string()
}
}
impl Temporal for NaiveDate {
fn now() -> Self {
Local::now().date_naive()
}
fn duration_between(&self, other: &Self) -> Duration {
let days = if self > other {
(*self - *other).num_days()
} else {
(*other - *self).num_days()
};
Duration::days(days)
}
fn to_utc(&self) -> DateTime<Utc> {
let naive_dt =
self.and_time(NaiveTime::from_hms_opt(0, 0, 0).expect("00:00:00 is always valid"));
DateTime::<Utc>::from_naive_utc_and_offset(naive_dt, Utc)
}
fn from_str(s: &str) -> Result<Self> {
match NaiveDate::parse_from_str(s, "%Y-%m-%d") {
Ok(dt) => Ok(dt),
Err(_) => {
match chrono::DateTime::parse_from_rfc3339(s) {
Ok(dt) => Ok(dt.date_naive()),
Err(e) => Err(PandRSError::Format(format!("Date parsing error: {}", e))),
}
}
}
}
fn to_string(&self) -> String {
self.format("%Y-%m-%d").to_string()
}
}
#[derive(Debug, Clone)]
pub struct TimeSeries<T: Temporal> {
values: Vec<NA<f64>>,
timestamps: Vec<T>,
name: Option<String>,
frequency: Option<Frequency>,
}
impl<T: Temporal> TimeSeries<T> {
pub fn new(values: Vec<NA<f64>>, timestamps: Vec<T>, name: Option<String>) -> Result<Self> {
if values.len() != timestamps.len() {
return Err(PandRSError::Consistency(format!(
"Length of values ({}) does not match length of time index ({})",
values.len(),
timestamps.len()
)));
}
Ok(TimeSeries {
values,
timestamps,
name,
frequency: None,
})
}
pub fn len(&self) -> usize {
self.values.len()
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
pub fn name(&self) -> Option<&String> {
self.name.as_ref()
}
pub fn timestamps(&self) -> &[T] {
&self.timestamps
}
pub fn values(&self) -> &[NA<f64>] {
&self.values
}
pub fn frequency(&self) -> Option<&Frequency> {
self.frequency.as_ref()
}
pub fn with_frequency(mut self, freq: Frequency) -> Self {
self.frequency = Some(freq);
self
}
pub fn filter_by_time(&self, start: &T, end: &T) -> Result<Self> {
let mut filtered_values = Vec::new();
let mut filtered_timestamps = Vec::new();
for (i, ts) in self.timestamps.iter().enumerate() {
if ts >= start && ts <= end {
filtered_values.push(self.values[i].clone());
filtered_timestamps.push(ts.clone());
}
}
Self::new(filtered_values, filtered_timestamps, self.name.clone())
}
pub fn resample(&self, freq: Frequency) -> crate::temporal::resample::Resample<T> {
crate::temporal::resample::Resample::new(self, freq)
}
pub fn rolling_mean(&self, window: usize) -> Result<Self> {
if window > self.len() || window == 0 {
return Err(PandRSError::Consistency(format!(
"Invalid window size ({}). Must be greater than 0 and less than or equal to the data length ({}).",
window, self.len()
)));
}
self.rolling(window)?.mean()
}
}
pub fn days_in_month(year: i32, month: u32) -> u32 {
match month {
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
4 | 6 | 9 | 11 => 30,
2 => {
if is_leap_year(year) {
29
} else {
28
}
}
_ => panic!("Invalid month: {}", month),
}
}
pub fn is_leap_year(year: i32) -> bool {
(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
}