use chrono::{NaiveDate, Datelike};
use std::str::FromStr;
use thiserror::Error;
pub fn is_leap_year(year: i32) -> bool {
if year%4 > 0 {
false
} else if year%100 > 0 {
true
} else if year%400 == 0 {
true
}
else {
false
}
}
pub fn is_end_of_month (day: u32, month: u32, year: i32) -> bool {
if [1, 3, 5, 7, 8, 10, 12].contains(&month) {
day == 31
} else if [4, 6, 9, 11].contains(&month) {
day == 30
} else {
if is_leap_year(year) {
day == 29
} else {
day == 28
}
}
}
#[derive(Hash, Clone, Copy, Debug)]
pub enum DayCountConvention {
US30360,
ActAct,
Act360,
Act365,
EU30360
}
impl DayCountConvention {
pub fn from_int(day_count_convention: u8) -> Result<Self, DayCountConventionError> {
match day_count_convention {
0 => Ok(DayCountConvention::US30360),
1 => Ok(DayCountConvention::ActAct),
2 => Ok(DayCountConvention::Act360),
3 => Ok(DayCountConvention::Act365),
4 => Ok(DayCountConvention::EU30360),
other => Err(DayCountConventionError::InvalidValue{val: other.to_string()})
}
}
pub fn from_str(day_count_convention: &str) -> Result<Self, DayCountConventionError> {
<Self as FromStr>::from_str(day_count_convention)
}
pub fn yearfrac(&self, start: NaiveDate, end: NaiveDate) -> f64 {
assert!(end >= start);
if start == end {
return 0.0 }
let numerator = self.diff_dts(start, end);
let denom = self.basis(start, end);
numerator/denom
}
fn basis(&self, start: NaiveDate, end: NaiveDate) -> f64{
match self {
DayCountConvention::US30360 |
DayCountConvention::Act360 |
DayCountConvention::EU30360
=> 360.0,
DayCountConvention::Act365 =>
365.0,
DayCountConvention::ActAct => {
let (start_day, start_month, start_year) = (start.day(), start.month(), start.year());
let (end_day, end_month, end_year) = (end.day(), end.month(), end.year());
if start_year == end_year {
if is_leap_year(start_year) {
366.0
} else {
365.0
}
} else if (end_year-1 == start_year) &
( (start_month>end_month) | ( (start_month==end_month) & (start_day>end_day) ) )
{
if is_leap_year(start_year) {
if (start_month < 2) | ( (start_month==2) & (start_day<=29)) {
366.0
} else {
365.0
}
} else if is_leap_year(end_year) {
if (end_month > 2) | ( (end_month==2) & (end_day==29)) {
366.0
}
else {
365.0
}
} else {
365.0
}
}
else{
let mut tmp = 0.0;
for i_y in start_year..end_year+1 {
if is_leap_year(i_y) {
tmp += 366.0
} else {
tmp += 365.0
}
}
tmp / (end_year as f64 - start_year as f64 + 1.0)
}
}
}
}
fn diff_dts(&self, start: NaiveDate, end: NaiveDate) -> f64{
match self {
DayCountConvention::ActAct |
DayCountConvention::Act360 |
DayCountConvention::Act365
=> (start-end).num_days().abs() as f64,
DayCountConvention::US30360 => {
self.nasd360(start, end, 0, true)
}
DayCountConvention::EU30360=> {
self.euro360(start, end)
}
}
}
fn euro360(&self, start: NaiveDate, end: NaiveDate) -> f64 {
let (mut start_day, start_month, start_year) = (start.day(), start.month(), start.year());
let (mut end_day, end_month, end_year) = (end.day(), end.month(), end.year());
if start_day == 31 {
start_day = 30;
};
if end_day == 31 {
end_day = 30;
};
self.days360(start_day, start_month, start_year, end_day, end_month, end_year)
}
fn nasd360 (&self, start: NaiveDate, end: NaiveDate, method: u8, use_eom: bool) -> f64{
let (mut start_day, start_month, start_year) = (start.day(), start.month(), start.year());
let (mut end_day, end_month, end_year) = (end.day(), end.month(), end.year());
if ((end_month == 2) & is_end_of_month(end_day, end_month, end_year)) &
( ((start_month == 2) & is_end_of_month(start_day, start_month, start_year)) | (method==3)) {
end_day = 30;
};
if (end_day==31) & ( (start_day>=30) | (method==3)){
end_day = 30;
};
if start_day==31 {
start_day = 30;
}
if use_eom & (start_month==2) & is_end_of_month(start_day, start_month, start_year) {
start_day = 30;
}
self.days360(start_day, start_month, start_year, end_day, end_month, end_year)
}
fn days360(&self, start_day: u32, start_month: u32, start_year: i32, end_day: u32, end_month: u32, end_year: i32)->f64{
((end_year - start_year)*360 + (end_month as i32-start_month as i32)*30 + (end_day as i32-start_day as i32)).into()
}
}
impl FromStr for DayCountConvention {
type Err = DayCountConventionError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s{
"nasd30/360" => Ok(DayCountConvention::US30360),
"act/act" => Ok(DayCountConvention::ActAct),
"act360" => Ok(DayCountConvention::Act360),
"act365" => Ok(DayCountConvention::Act365),
"eur30/360" => Ok(DayCountConvention::EU30360),
other => Err(DayCountConventionError::InvalidValue{val: other.to_owned()})
}
}
}
#[derive(Debug, Error)]
pub enum DayCountConventionError{
#[error("Yearfrac: Invalid Value: {}. Has to be one of: nasd30/360, act/act, act360, act365, eur30/360 (from_str)
or in the range 0-4 (from_int).", val)]
InvalidValue{val: String}
}
#[cfg(test)]
mod tests {
}