functions: ACCRINTM accrues zero when issue equals settlement (#4096) (#4102)

ACCRINTM returned #NUM! whenever issue >= settlement. Excel rejects only
an issue date after settlement: when the two dates are equal after
truncation to whole days (including distinct times on the same day) it
returns 0 in every basis and both date systems. Relax the guard to
issue > settle; zero rate, zero par and an invalid basis stay #NUM!.
This commit is contained in:
Zinadin Zidan authored and GitHub committed 2026-11-14 15:22:59 +01:00
1 parent c0ee120ac2
commit 40783ab184
2 files changed
+27 -1

No files matched your search

+2 -1
View File
@@ -71,7 +71,8 @@ pub(super) fn accrintm(a: &[Arg], c: &mut dyn Ctx) -> R<Value> {
let (issue, settle, rate) = (date(a, 0)?, date(a, 1)?, num(a, 2)?);
let par = if has(a, 3) { num(a, 3)? } else { 1000.0 };
let basis = basis_arg(a, 4)?;
if issue >= settle || rate <= 0.0 || par <= 0.0 {
// Excel accepts equal (whole-day) dates and accrues nothing; only issue after settlement is #NUM!.
if issue > settle || rate <= 0.0 || par <= 0.0 {
return Err(CellError::Num);
}
num_val(par * rate * year_frac(c.date_system(), issue, settle, basis)?)
+25
View File
@@ -317,3 +317,28 @@ fn securities_read_dates_in_the_1904_system() {
let odd = vec![d4(2008, 11, 11), d4(2021, 3, 1), d4(2008, 10, 15), d4(2009, 3, 1), n(0.0785), n(0.0625), n(100.0), n(2.0), n(1.0)];
close_tol(ev4("ODDFPRICE", odd), 113.597717474079, 1e-9);
}
/// Excel truncates ACCRINTM's dates to whole days and accrues nothing when issue equals
/// settlement, in every basis and both date systems; only issue after settlement is #NUM! (#4096).
#[test]
fn accrintm_equal_dates_accrue_zero() {
let day = serial_from_ymd(DateSystem::D1900, 2025, 1, 15).unwrap();
for basis in 0..=4 {
close(ev("ACCRINTM", vec![n(day), n(day), n(0.05), n(1000.0), n(f64::from(basis))]), 0.0);
}
// Distinct times on the same day truncate to equal dates.
close(ev("ACCRINTM", vec![n(day + 0.1), n(day + 0.9), n(0.05), n(1000.0), n(2.0)]), 0.0);
close_tol(ev("ACCRINTM", vec![n(day), n(day + 1.0), n(0.05), n(1000.0), n(2.0)]), 0.1388888888888889, 1e-15);
is_err(ev("ACCRINTM", vec![n(day + 1.0), n(day), n(0.05), n(1000.0), n(2.0)]), CellError::Num);
// Equal dates do not excuse a zero rate or par, or an invalid basis.
is_err(ev("ACCRINTM", vec![n(day), n(day), n(0.0), n(1000.0), n(2.0)]), CellError::Num);
is_err(ev("ACCRINTM", vec![n(day), n(day), n(0.05), n(0.0), n(2.0)]), CellError::Num);
is_err(ev("ACCRINTM", vec![n(day), n(day), n(0.05), n(1000.0), n(5.0)]), CellError::Num);
let day4 = serial_from_ymd(DateSystem::D1904, 2025, 1, 15).unwrap();
for basis in 0..=4 {
let mut ctx = TestCtx { sys: DateSystem::D1904, ..TestCtx::default() };
let args = vec![n(day4), n(day4), n(0.05), n(1000.0), n(f64::from(basis))];
close(crate::call(crate::lookup("ACCRINTM").unwrap(), &args, &mut ctx), 0.0);
}
}