TD7KX2PIGP2TCZ2Q7XXNBBDJIQ2KXEIFWLBMWXTJE3OEXVV4L7MAC
YMV7RPQ5TFBETNHRMS26MGHJXEAYRMIF4F7Z6ITGRCG65TOGSNDAC
P6XNTWNXKWQ7APCAHAP635VIWCOX5NH5P4DS3C4APOE6OKJGEJOQC
QYLGEDIVYSUHAYU7ZLUZDA6OULFDDZYTQN264V3473MEXLFZ4U3AC
IYW574EKVRH2QJ7GFNX4FMCNI7EMLNYYIC6NGHVIJVDEWSDL42GQC
XIWTRGR6SRVSX3TA6YZVMZIETMZNLJBQSJ3BAL3O6ZXURJPTHQZAC
XCCNAGMZBU6N3YNH3ILES46HV6HBSSCSO52CK5ZQNYIYNSDCHXIAC
/// A duration between two `MonthlyDates`. The value can be positive or negative.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Duration(isize);
impl Duration {
fn months(n: isize) -> Self {
Duration(n)
}
fn quarters(n: isize) -> Self {
Duration(3 * n)
}
fn year(n: isize) -> Self {
Duration(12 * n)
}
/// Return the duration between two dates.
fn between(min: MonthlyDate, max: MonthlyDate) -> Self {
Duration(max.as_isize() - min.as_isize())
}
fn is_not_positive(&self) -> bool {
self.0 <= 0
}
}
/// Return true if the durations between Points are all equal.
pub fn is_regular(&self, duration: &MonthlyDate) -> bool {
self.0.as_slice().windows(2).all(|datapoint_pair| {
datapoint_pair[1].date() - datapoint_pair[0].date() == *duration
})
}
// /// Return true if the durations between Points are all equal.
// pub fn is_regular(&self, duration: &MonthlyDate) -> bool {
// if self.len < 2 {
// return false
// } else {
// self.0.as_slice().windows(2).all(|datapoint_pair| {
// datapoint_pair[1].date() - datapoint_pair[0].date() == *duration
// })
// }
// }
Ok((self.0)[1].date() - (self.0)[0].date())
let first_date = self.0[0].date();
let second_date = self.0[1].date();
let duration = Duration::between(second_date, first_date);
if duration.is_not_positive() {
return Err(
expected_positive_duration(
&format!("{}-{}-01", first_date.year(), first_date.month()),
&format!("{}-{}-01", second_date.year(), second_date.month()),
)
)
};
Ok(duration)
/// A `RegularTimeSeries` has an additional requirements over `TimeSeries` in
/// the time interval between successive `DatePoints` has the same `Duration`.
/// This also ensures ordering.
/// A `RegularTimeSeries` has an additional requirements over `TimeSeries` in the time interval
/// between successive `DatePoints` has the same `Duration`. This also ensures ordering. A
/// `RegularTimeSeries` is guaranteed to have two or more data points.
/// Return the datapoint for the given date or error if that date is not in `Self`.
pub fn datepoint_from_date(&self, date: MonthlyDate) -> Result<DatePoint::<N>, Error> {
if date < self.first_date() || date > self.last_date(){
return Err(
date_not_in_timeseries(
&format!(
"{}-{:02}-01",
date.year(),
date.month()
)
)
)};
let months_delta = date.as_isize() - self.first_date().as_isize();
if months_delta % self.duration.0 != 0 {
return Err(
date_not_in_timeseries(
&format!(
"{}-{:02}-01",
date.year(),
date.month()
)
)
)};
let index = date.as_isize() - self.first_date().as_isize() / self.duration.0;
/// Transform a `RegularTimeSeries` into quarterly data.
pub fn to_quarterly(&self, n: usize) -> RegularTimeSeries<1> {
let x = self.ts.0.iter().map(|dp| dp.date().as_isize() as f64).collect::<Vec<f64>>();
let y = self.ts.0.iter().map(|dp| dp.value(n) as f64).collect::<Vec<f64>>();
let spline = CubicSpline::from_nodes(x, y);
let (add_year, month) = match self.first_date().month() {
1 => (0, 1),
2 | 3 | 4 => (0, 4),
5 | 6 | 7 => (0, 7),
8 | 9 | 10 => (0, 10),
11 | 12 => (1, 1),
_ => panic!(),
};
let mut date = MonthlyDate::ym(self.first_date().year() + add_year, month);
let mut v = Vec::new();
while date <= self.last_date() {
let dp = DatePoint::<1>::new(date, [spline.eval(date.as_isize() as f64) as f32]);
v.push(dp);
date = MonthlyDate(date.0 + 3);
};
TimeSeries::new(v).try_into().unwrap()
}
/// Transform a `RegularTimeSeries` into year-on-year percentage change over the previous year.
pub fn to_year_on_year(&self, n: usize) -> Result<RegularTimeSeries<1>, Error> {
let mut v = Vec::new();
let mut date = self.first_date();
while let Ok(dp2) = self.datepoint_from_date(date + Duration::year(1)) {
let dp1 = self.datepoint_from_date(date).unwrap();
let yoy = (dp2.value(0) - dp1.value(n)) * 100.0 / dp1.value(n);
let dp = DatePoint::<1>::new(date + Duration::year(1), [yoy]);
v.push(dp);
date = date + self.duration;
}
TimeSeries::new(v).try_into()
}
pub fn new(start_date: Option<MonthlyDate>, end_date: Option<MonthlyDate>) -> Self {
DateRange { start_date, end_date }
/// Return a new `DateRange`.
pub fn new(start_date: &Option<MonthlyDate>, end_date: &Option<MonthlyDate>) -> Self {
DateRange {
start_date: start_date.clone(),
end_date: end_date.clone()
}
match ts.is_regular(&duration) {
true => {
Ok(RegularTimeSeries::<N> {
duration: duration.clone(),
ts: ts,
})
},
false => Err(expected_regular_time_series()),
}
Ok(RegularTimeSeries::<N> { duration, ts })