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 && d1 == 30 {
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 bond_basis_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 - 0.5).abs() < TOL);
}
#[test]
fn bond_basis_d1_31_adjusts_to_30() {
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 bond_basis_d2_31_with_d1_30_adjusts() {
let start = Date::ymd(2026, 1, 30).unwrap();
let end = Date::ymd(2026, 7, 31).unwrap();
let f = fraction(start, end);
assert!((f - 0.5).abs() < TOL);
}
#[test]
fn bond_basis_d2_31_with_d1_15_no_adjust() {
let start = Date::ymd(2026, 1, 15).unwrap();
let end = Date::ymd(2026, 7, 31).unwrap();
let f = fraction(start, end);
assert!((f - 196.0_f64 / 360.0).abs() < TOL);
}
#[test]
fn bond_basis_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 bond_basis_zero_length() {
let d = Date::ymd(2026, 5, 23).unwrap();
let f = fraction(d, d);
assert!(f.abs() < TOL);
}
#[test]
fn bond_basis_inverted_is_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 bond_basis_does_not_adjust_feb_28() {
let start = Date::ymd(2026, 2, 28).unwrap();
let end = Date::ymd(2026, 8, 28).unwrap();
let f = fraction(start, end);
assert!((f - 0.5).abs() < TOL);
}
}