use crate::calc_engine::calculator::calc_vertex::{CalcValue, CalcVertex, Value};
use crate::calc_engine::calculator::funcs::utils::get_nums_from_value;
use crate::calc_engine::calculator::math::day_count::days_diff_360_eu;
use crate::calc_engine::connector::Connector;
use logisheets_base::datetime::EasyDate;
use logisheets_parser::ast;
fn is_leap(y: u32) -> bool {
y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)
}
fn days_in_month(year: u32, month: u8) -> u8 {
EasyDate {
year,
month,
day: 1,
}
.last_day_of_this_month()
.day
}
fn safe_serial(year: u32, month: u8, day: u8) -> u32 {
let last = days_in_month(year, month);
let dt = EasyDate {
year,
month,
day: day.min(last),
};
u32::from(dt)
}
fn days360_us(s: EasyDate, e: EasyDate) -> i32 {
let mut d1 = s.day as i32;
let mut d2 = e.day as i32;
if d1 == 31 {
d1 = 30;
}
if d2 == 31 && d1 == 30 {
d2 = 30;
}
(e.year as i32 - s.year as i32) * 360 + (e.month as i32 - s.month as i32) * 30 + (d2 - d1)
}
pub fn calc_days360<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
C: Connector,
{
assert_or_return!(args.len() == 2 || args.len() == 3, ast::Error::Unspecified);
let mut iter = args.into_iter();
let a = fetcher.get_calc_value(iter.next().unwrap());
assert_f64_from_calc_value!(s_ser, a);
let b = fetcher.get_calc_value(iter.next().unwrap());
assert_f64_from_calc_value!(e_ser, b);
let european = optional_f64!(iter, fetcher, 0.) != 0.;
if s_ser < 0. || e_ser < 0. {
return CalcVertex::from_error(ast::Error::Num);
}
let s = EasyDate::from(s_ser.trunc() as u32);
let e = EasyDate::from(e_ser.trunc() as u32);
let days = if european {
days_diff_360_eu(s, e)
} else {
days360_us(s, e)
};
CalcVertex::from_number(days as f64)
}
pub fn calc_yearfrac<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
C: Connector,
{
assert_or_return!(args.len() == 2 || args.len() == 3, ast::Error::Unspecified);
let mut iter = args.into_iter();
let a = fetcher.get_calc_value(iter.next().unwrap());
assert_f64_from_calc_value!(x, a);
let b = fetcher.get_calc_value(iter.next().unwrap());
assert_f64_from_calc_value!(y, b);
let basis = optional_f64!(iter, fetcher, 0.).trunc() as i64;
if x < 0. || y < 0. {
return CalcVertex::from_error(ast::Error::Num);
}
let (lo, hi) = if x <= y { (x, y) } else { (y, x) };
let lo_ser = lo.trunc() as u32;
let hi_ser = hi.trunc() as u32;
let s = EasyDate::from(lo_ser);
let e = EasyDate::from(hi_ser);
let actual = (hi_ser - lo_ser) as f64;
let frac = match basis {
0 => days360_us(s, e) as f64 / 360.,
4 => days_diff_360_eu(s, e) as f64 / 360.,
2 => actual / 360.,
3 => actual / 365.,
1 => {
if s.year == e.year {
actual / if is_leap(s.year) { 366. } else { 365. }
} else {
let sy_first = safe_serial(s.year, 1, 1) as f64;
let ey_next = safe_serial(e.year + 1, 1, 1) as f64;
let years = (e.year - s.year + 1) as f64;
let avg = (ey_next - sy_first) / years;
actual / avg
}
}
_ => return CalcVertex::from_error(ast::Error::Num),
};
CalcVertex::from_number(frac)
}
pub fn calc_datedif<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
C: Connector,
{
assert_or_return!(args.len() == 3, ast::Error::Unspecified);
let mut iter = args.into_iter();
let a = fetcher.get_calc_value(iter.next().unwrap());
assert_f64_from_calc_value!(s_ser, a);
let b = fetcher.get_calc_value(iter.next().unwrap());
assert_f64_from_calc_value!(e_ser, b);
let c = fetcher.get_calc_value(iter.next().unwrap());
assert_text_from_calc_value!(unit, c);
let s_ser = s_ser.trunc() as u32;
let e_ser = e_ser.trunc() as u32;
if e_ser < s_ser {
return CalcVertex::from_error(ast::Error::Num);
}
let s = EasyDate::from(s_ser);
let e = EasyDate::from(e_ser);
let complete_months = |s: EasyDate, e: EasyDate| -> i64 {
let mut m = (e.year as i64 - s.year as i64) * 12 + (e.month as i64 - s.month as i64);
if e.day < s.day {
m -= 1;
}
m
};
let res: i64 = match unit.to_uppercase().as_str() {
"Y" => {
let mut yy = e.year as i64 - s.year as i64;
if (e.month, e.day) < (s.month, s.day) {
yy -= 1;
}
yy
}
"M" => complete_months(s, e),
"D" => (e_ser - s_ser) as i64,
"YM" => complete_months(s, e).rem_euclid(12),
"MD" => {
if e.day >= s.day {
(e.day - s.day) as i64
} else {
let (py, pm) = if e.month == 1 {
(e.year - 1, 12)
} else {
(e.year, e.month - 1)
};
days_in_month(py, pm) as i64 - s.day as i64 + e.day as i64
}
}
"YD" => {
let mut anchor = safe_serial(e.year, s.month, s.day);
if anchor > e_ser {
anchor = safe_serial(e.year - 1, s.month, s.day);
}
(e_ser - anchor) as i64
}
_ => return CalcVertex::from_error(ast::Error::Value),
};
CalcVertex::from_number(res as f64)
}
pub fn calc_networkdays<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
C: Connector,
{
assert_or_return!(args.len() == 2 || args.len() == 3, ast::Error::Unspecified);
let mut iter = args.into_iter();
let a = fetcher.get_calc_value(iter.next().unwrap());
assert_f64_from_calc_value!(s_ser, a);
let b = fetcher.get_calc_value(iter.next().unwrap());
assert_f64_from_calc_value!(e_ser, b);
let mut holidays: Vec<i64> = Vec::new();
if let Some(h) = iter.next() {
let v = fetcher.get_calc_value(h);
match get_nums_from_value(v) {
Ok(nums) => holidays = nums.into_iter().map(|n| n.trunc() as i64).collect(),
Err(e) => return CalcVertex::from_error(e),
}
}
let s = s_ser.trunc() as i64;
let e = e_ser.trunc() as i64;
let (lo, hi, sign) = if s <= e { (s, e, 1i64) } else { (e, s, -1i64) };
let mut count = 0i64;
for d in lo..=hi {
if d % 7 == 0 || d % 7 == 1 {
continue;
}
if holidays.contains(&d) {
continue;
}
count += 1;
}
CalcVertex::from_number((count * sign) as f64)
}