use crate::date::Date;
#[must_use]
pub fn fraction(start: Date, end: Date) -> f64 {
let y1 = start.year();
let m1 = i32::from(start.month());
let mut d1 = i32::from(start.day());
let y2 = end.year();
let m2 = i32::from(end.month());
let mut d2 = i32::from(end.day());
if d1 == 31 {
d1 = 30;
}
if d2 == 31 {
d2 = 30;
}
let numerator = 360 * (y2 - y1) + 30 * (m2 - m1) + (d2 - d1);
f64::from(numerator) / 360.0
}
#[cfg(test)]
mod tests {
use super::*;
const TOL: f64 = 1e-12;
#[test]
fn eurobond_6m_mid_month() {
let start = Date::ymd(2026, 1, 15).unwrap();
let end = Date::ymd(2026, 7, 15).unwrap();
let f = fraction(start, end);
assert!((f - 180.0_f64 / 360.0).abs() < 1e-15);
assert!((f - 0.5).abs() < TOL);
}
#[test]
fn eurobond_d1_31_adjusts() {
let start = Date::ymd(2026, 1, 31).unwrap();
let end = Date::ymd(2026, 2, 28).unwrap();
let f = fraction(start, end);
assert!((f - 28.0_f64 / 360.0).abs() < TOL);
}
#[test]
fn eurobond_d2_31_unconditional_adjust() {
let start = Date::ymd(2026, 1, 15).unwrap();
let end = Date::ymd(2026, 7, 31).unwrap();
let f = fraction(start, end);
assert!((f - 195.0_f64 / 360.0).abs() < TOL);
}
#[test]
fn eurobond_d1_31_d2_31() {
let start = Date::ymd(2026, 1, 31).unwrap();
let end = Date::ymd(2026, 7, 31).unwrap();
let f = fraction(start, end);
assert!((f - 0.5).abs() < TOL);
}
#[test]
fn eurobond_worked_2026_01_31_to_07_31() {
let start = Date::ymd(2026, 1, 31).unwrap();
let end = Date::ymd(2026, 7, 31).unwrap();
let f = fraction(start, end);
assert!((f - 180.0_f64 / 360.0).abs() < 1e-15);
assert!((f - 0.5).abs() < TOL);
}
#[test]
fn eurobond_zero_length() {
let d = Date::ymd(2026, 5, 23).unwrap();
let f = fraction(d, d);
assert!(f.abs() < TOL);
}
#[test]
fn eurobond_inverted_negative() {
let start = Date::ymd(2026, 1, 15).unwrap();
let end = Date::ymd(2026, 7, 15).unwrap();
let forward = fraction(start, end);
let backward = fraction(end, start);
assert!((backward + forward).abs() < TOL);
assert!(backward < 0.0);
}
#[test]
fn eurobond_vs_bond_basis_d2_31_only_diff() {
let start = Date::ymd(2026, 1, 15).unwrap();
let end = Date::ymd(2026, 7, 31).unwrap();
let eurobond = fraction(start, end);
let bond_basis_hand = 196.0_f64 / 360.0;
assert!((eurobond - 195.0_f64 / 360.0).abs() < TOL);
assert!((bond_basis_hand - eurobond - 1.0_f64 / 360.0).abs() < TOL);
}
}