From 15b67323edac3c819ea6bff9309a084d60babc72 Mon Sep 17 00:00:00 2001 From: Brian Hung Date: Thu, 31 Jul 2025 15:25:13 -0700 Subject: [PATCH] merge accrint, accrintm #58 --- .../src/expressions/parser/static_analysis.rs | 4 + base/src/functions/financial.rs | 239 ++++++++++++++++++ base/src/functions/mod.rs | 13 +- base/src/test/mod.rs | 2 + base/src/test/test_fn_accrint.rs | 134 ++++++++++ base/src/test/test_fn_accrintm.rs | 122 +++++++++ docs/src/functions/financial.md | 4 +- docs/src/functions/financial/accrint.md | 3 +- docs/src/functions/financial/accrintm.md | 3 +- 9 files changed, 517 insertions(+), 7 deletions(-) create mode 100644 base/src/test/test_fn_accrint.rs create mode 100644 base/src/test/test_fn_accrintm.rs diff --git a/base/src/expressions/parser/static_analysis.rs b/base/src/expressions/parser/static_analysis.rs index 2dc2fd2..e25b3d6 100644 --- a/base/src/expressions/parser/static_analysis.rs +++ b/base/src/expressions/parser/static_analysis.rs @@ -771,6 +771,8 @@ fn get_function_args_signature(kind: &Function, arg_count: usize) -> Vec args_signature_scalars(arg_count, 5, 1), Function::Mduration => args_signature_scalars(arg_count, 5, 1), Function::Pduration => args_signature_scalars(arg_count, 3, 0), + Function::Accrint => args_signature_scalars(arg_count, 6, 2), + Function::Accrintm => args_signature_scalars(arg_count, 4, 1), Function::Pmt => args_signature_scalars(arg_count, 3, 2), Function::Ppmt => args_signature_scalars(arg_count, 4, 2), Function::Price => args_signature_scalars(arg_count, 6, 1), @@ -1047,6 +1049,8 @@ fn static_analysis_on_function(kind: &Function, args: &[Node]) -> StaticResult { Function::Duration => not_implemented(args), Function::Mduration => not_implemented(args), Function::Pduration => not_implemented(args), + Function::Accrint => not_implemented(args), + Function::Accrintm => not_implemented(args), Function::Pmt => not_implemented(args), Function::Ppmt => not_implemented(args), Function::Price => not_implemented(args), diff --git a/base/src/functions/financial.rs b/base/src/functions/financial.rs index ca47640..4c8c21c 100644 --- a/base/src/functions/financial.rs +++ b/base/src/functions/financial.rs @@ -92,6 +92,38 @@ fn days360_eu(start: chrono::NaiveDate, end: chrono::NaiveDate) -> i32 { d2 + m2 * 30 + y2 * 360 - d1 - m1 * 30 - y1 * 360 } +fn days_30us_360(start: chrono::NaiveDate, end: chrono::NaiveDate) -> i32 { + let mut d1 = start.day() as i32; + let mut d2 = end.day() as i32; + let m1 = start.month() as i32; + let m2 = end.month() as i32; + let y1 = start.year(); + let y2 = end.year(); + if d1 == 31 { + d1 = 30; + } + if d2 == 31 && (d1 == 30 || d1 == 31) { + d2 = 30; + } + (y2 - y1) * 360 + (m2 - m1) * 30 + (d2 - d1) +} + +fn days_30e_360(start: chrono::NaiveDate, end: chrono::NaiveDate) -> i32 { + let mut d1 = start.day() as i32; + let mut d2 = end.day() as i32; + let m1 = start.month() as i32; + let m2 = end.month() as i32; + let y1 = start.year(); + let y2 = end.year(); + if d1 == 31 { + d1 = 30; + } + if d2 == 31 { + d2 = 30; + } + (y2 - y1) * 360 + (m2 - m1) * 30 + (d2 - d1) +} + fn days_between(start: i64, end: i64, basis: i32) -> Result { let start_date = from_excel_date(start)?; let end_date = from_excel_date(end)?; @@ -129,6 +161,22 @@ fn year_diff(start: i64, end: i64, basis: i32) -> Result { year_frac(start, end, basis) } +fn year_fraction( + start: chrono::NaiveDate, + end: chrono::NaiveDate, + basis: i32, +) -> Result { + let days = match basis { + 0 => days_30us_360(start, end) as f64 / 360.0, + 1 => (end - start).num_days() as f64 / 365.0, + 2 => (end - start).num_days() as f64 / 360.0, + 3 => (end - start).num_days() as f64 / 365.0, + 4 => days_30e_360(start, end) as f64 / 360.0, + _ => return Err("Invalid basis".to_string()), + }; + Ok(days) +} + fn compute_payment( rate: f64, nper: f64, @@ -542,6 +590,197 @@ impl Model { CalcResult::Number(result) } + // ACCRINT(issue, first_interest, settlement, rate, par, freq, [basis], [calc]) + pub(crate) fn fn_accrint(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { + let arg_count = args.len(); + if !(6..=8).contains(&arg_count) { + return CalcResult::new_args_number_error(cell); + } + let issue = match self.get_number_no_bools(&args[0], cell) { + Ok(f) => f, + Err(s) => return s, + }; + let first = match self.get_number_no_bools(&args[1], cell) { + Ok(f) => f, + Err(s) => return s, + }; + let settlement = match self.get_number_no_bools(&args[2], cell) { + Ok(f) => f, + Err(s) => return s, + }; + let rate = match self.get_number_no_bools(&args[3], cell) { + Ok(f) => f, + Err(s) => return s, + }; + let par = match self.get_number_no_bools(&args[4], cell) { + Ok(f) => f, + Err(s) => return s, + }; + let freq = match self.get_number_no_bools(&args[5], cell) { + Ok(f) => f as i32, + Err(s) => return s, + }; + let basis = if arg_count > 6 { + match self.get_number_no_bools(&args[6], cell) { + Ok(f) => f as i32, + Err(s) => return s, + } + } else { + 0 + }; + let calc = if arg_count > 7 { + match self.get_number(&args[7], cell) { + Ok(f) => f != 0.0, + Err(s) => return s, + } + } else { + true + }; + + if !(freq == 1 || freq == 2 || freq == 4) { + return CalcResult::new_error(Error::NUM, cell, "invalid frequency".to_string()); + } + if !(0..=4).contains(&basis) { + return CalcResult::new_error(Error::NUM, cell, "invalid basis".to_string()); + } + if par < 0.0 { + return CalcResult::new_error(Error::NUM, cell, "par cannot be negative".to_string()); + } + if rate < 0.0 { + return CalcResult::new_error(Error::NUM, cell, "rate cannot be negative".to_string()); + } + + let issue_d = match from_excel_date(issue as i64) { + Ok(d) => d, + Err(_) => return CalcResult::new_error(Error::NUM, cell, "Invalid date".to_string()), + }; + let first_d = match from_excel_date(first as i64) { + Ok(d) => d, + Err(_) => return CalcResult::new_error(Error::NUM, cell, "Invalid date".to_string()), + }; + let settle_d = match from_excel_date(settlement as i64) { + Ok(d) => d, + Err(_) => return CalcResult::new_error(Error::NUM, cell, "Invalid date".to_string()), + }; + + if settle_d < issue_d { + return CalcResult::new_error(Error::NUM, cell, "settlement < issue".to_string()); + } + if first_d < issue_d { + return CalcResult::new_error(Error::NUM, cell, "first_interest < issue".to_string()); + } + if settle_d < first_d { + return CalcResult::new_error( + Error::NUM, + cell, + "settlement < first_interest".to_string(), + ); + } + + let months = 12 / freq; + let mut prev = first_d; + if settle_d <= first_d { + prev = issue_d; + } else { + while prev <= settle_d { + let next = prev + chrono::Months::new(months as u32); + if next > settle_d { + break; + } + prev = next; + } + } + let next_coupon = prev + chrono::Months::new(months as u32); + + let mut result = 0.0; + if calc { + let mut next = first_d; + while next < prev { + result += rate * par / freq as f64; + next = next + chrono::Months::new(months as u32); + } + } + + let days_in_period = match year_fraction(prev, next_coupon, basis) { + Ok(f) => f, + Err(_) => return CalcResult::new_error(Error::NUM, cell, "invalid basis".to_string()), + }; + let days_elapsed = match year_fraction(prev, settle_d, basis) { + Ok(f) => f, + Err(_) => return CalcResult::new_error(Error::NUM, cell, "invalid basis".to_string()), + }; + + result += rate * par / freq as f64 + * if days_in_period == 0.0 { + 0.0 + } else { + days_elapsed / days_in_period + }; + CalcResult::Number(result) + } + + // ACCRINTM(issue, settlement, rate, par, [basis]) + pub(crate) fn fn_accrintm(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { + let arg_count = args.len(); + if !(4..=5).contains(&arg_count) { + return CalcResult::new_args_number_error(cell); + } + let issue = match self.get_number_no_bools(&args[0], cell) { + Ok(f) => f, + Err(s) => return s, + }; + let settlement = match self.get_number_no_bools(&args[1], cell) { + Ok(f) => f, + Err(s) => return s, + }; + let rate = match self.get_number_no_bools(&args[2], cell) { + Ok(f) => f, + Err(s) => return s, + }; + let par = match self.get_number_no_bools(&args[3], cell) { + Ok(f) => f, + Err(s) => return s, + }; + let basis = if arg_count > 4 { + match self.get_number_no_bools(&args[4], cell) { + Ok(f) => f as i32, + Err(s) => return s, + } + } else { + 0 + }; + + if !(0..=4).contains(&basis) { + return CalcResult::new_error(Error::NUM, cell, "invalid basis".to_string()); + } + if par < 0.0 { + return CalcResult::new_error(Error::NUM, cell, "par cannot be negative".to_string()); + } + if rate < 0.0 { + return CalcResult::new_error(Error::NUM, cell, "rate cannot be negative".to_string()); + } + + let issue_d = match from_excel_date(issue as i64) { + Ok(d) => d, + Err(_) => return CalcResult::new_error(Error::NUM, cell, "Invalid date".to_string()), + }; + let settle_d = match from_excel_date(settlement as i64) { + Ok(d) => d, + Err(_) => return CalcResult::new_error(Error::NUM, cell, "Invalid date".to_string()), + }; + + if settle_d < issue_d { + return CalcResult::new_error(Error::NUM, cell, "settlement < issue".to_string()); + } + + let frac = match year_fraction(issue_d, settle_d, basis) { + Ok(f) => f, + Err(_) => return CalcResult::new_error(Error::NUM, cell, "invalid basis".to_string()), + }; + + CalcResult::Number(par * rate * frac) + } + // RATE(nper, pmt, pv, [fv], [type], [guess]) pub(crate) fn fn_rate(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { let arg_count = args.len(); diff --git a/base/src/functions/mod.rs b/base/src/functions/mod.rs index 536acec..f4dc135 100644 --- a/base/src/functions/mod.rs +++ b/base/src/functions/mod.rs @@ -217,6 +217,8 @@ pub enum Function { Isoweeknum, // Financial + Accrint, + Accrintm, Cumipmt, Cumprinc, Db, @@ -325,7 +327,7 @@ pub enum Function { } impl Function { - pub fn into_iter() -> IntoIter { + pub fn into_iter() -> IntoIter { [ Function::And, Function::False, @@ -486,6 +488,8 @@ impl Function { Function::WorkdayIntl, Function::Yearfrac, Function::Isoweeknum, + Function::Accrint, + Function::Accrintm, Function::Pmt, Function::Pv, Function::Rate, @@ -837,6 +841,8 @@ impl Function { "YEARFRAC" => Some(Function::Yearfrac), "ISOWEEKNUM" | "_XLFN.ISOWEEKNUM" => Some(Function::Isoweeknum), // Financial + "ACCRINT" => Some(Function::Accrint), + "ACCRINTM" => Some(Function::Accrintm), "PMT" => Some(Function::Pmt), "PV" => Some(Function::Pv), "RATE" => Some(Function::Rate), @@ -1088,6 +1094,8 @@ impl fmt::Display for Function { Function::WorkdayIntl => write!(f, "WORKDAY.INTL"), Function::Yearfrac => write!(f, "YEARFRAC"), Function::Isoweeknum => write!(f, "ISOWEEKNUM"), + Function::Accrint => write!(f, "ACCRINT"), + Function::Accrintm => write!(f, "ACCRINTM"), Function::Pmt => write!(f, "PMT"), Function::Pv => write!(f, "PV"), Function::Rate => write!(f, "RATE"), @@ -1377,6 +1385,9 @@ impl Model { Function::WorkdayIntl => self.fn_workday_intl(args, cell), Function::Yearfrac => self.fn_yearfrac(args, cell), Function::Isoweeknum => self.fn_isoweeknum(args, cell), + // Financial + Function::Accrint => self.fn_accrint(args, cell), + Function::Accrintm => self.fn_accrintm(args, cell), Function::Pmt => self.fn_pmt(args, cell), Function::Pv => self.fn_pv(args, cell), Function::Rate => self.fn_rate(args, cell), diff --git a/base/src/test/mod.rs b/base/src/test/mod.rs index 21de93f..2e6c8fb 100644 --- a/base/src/test/mod.rs +++ b/base/src/test/mod.rs @@ -11,6 +11,8 @@ mod test_datedif_leap_month_end; mod test_days360_month_end; mod test_degrees_radians; mod test_error_propagation; +mod test_fn_accrint; +mod test_fn_accrintm; mod test_fn_average; mod test_fn_averageifs; mod test_fn_choose; diff --git a/base/src/test/test_fn_accrint.rs b/base/src/test/test_fn_accrint.rs new file mode 100644 index 0000000..2ffde00 --- /dev/null +++ b/base/src/test/test_fn_accrint.rs @@ -0,0 +1,134 @@ +#![allow(clippy::unwrap_used)] + +use crate::{cell::CellValue, test::util::new_empty_model}; + +#[test] +fn fn_accrint() { + let mut model = new_empty_model(); + model._set("A1", "=DATE(2020,1,1)"); + model._set("A2", "=DATE(2020,1,1)"); + model._set("A3", "=DATE(2020,1,31)"); + model._set("A4", "10%"); + model._set("A5", "$1,000"); + model._set("A6", "2"); + + model._set("B1", "=ACCRINT(A1,A2,A3,A4,A5,A6)"); + model._set("C1", "=ACCRINT(A1)"); + model._set("C2", "=ACCRINT(A1,A2,A3,A4,A5,3)"); + + model.evaluate(); + + match model.get_cell_value_by_ref("Sheet1!B1") { + Ok(CellValue::Number(v)) => { + assert!((v - 8.333333333333334).abs() < 1e-9); + } + other => unreachable!("Expected number for B1, got {:?}", other), + } + assert_eq!(model._get_text("C1"), *"#ERROR!"); + assert_eq!(model._get_text("C2"), *"#NUM!"); +} + +#[test] +fn fn_accrint_parameters() { + let mut model = new_empty_model(); + + model._set("A1", "=DATE(2020,1,1)"); + model._set("A2", "=DATE(2020,1,1)"); + model._set("A3", "=DATE(2020,7,1)"); + model._set("A4", "8%"); + model._set("A5", "1000"); + + model._set("B1", "=ACCRINT(A1,A2,A3,A4,A5,2,0,TRUE)"); + model._set("B2", "=ACCRINT(A1,A2,A3,A4,A5,2,1,TRUE)"); + model._set("B3", "=ACCRINT(A1,A2,A3,A4,A5,2,4,TRUE)"); + model._set("B4", "=ACCRINT(A1,A2,A3,A4,A5,1)"); + model._set("B5", "=ACCRINT(A1,A2,A3,A4,A5,4)"); + model._set("B6", "=ACCRINT(A1,A2,A3,A4,A5,2)"); + model._set("B7", "=ACCRINT(A1,A2,A3,A4,A5,2,0)"); + + model.evaluate(); + + match model.get_cell_value_by_ref("Sheet1!B1") { + Ok(CellValue::Number(v)) => { + assert!((v - 40.0).abs() < 1e-9); + } + other => unreachable!("Expected number for B1, got {:?}", other), + } + + match ( + model.get_cell_value_by_ref("Sheet1!B1"), + model.get_cell_value_by_ref("Sheet1!B6"), + ) { + (Ok(CellValue::Number(v1)), Ok(CellValue::Number(v2))) => { + assert!((v1 - v2).abs() < 1e-12); + } + other => unreachable!("Expected matching numbers, got {:?}", other), + } +} + +#[test] +fn fn_accrint_errors() { + let mut model = new_empty_model(); + + model._set("A1", "=DATE(2020,1,1)"); + model._set("A2", "=DATE(2020,1,1)"); + model._set("A3", "=DATE(2020,7,1)"); + model._set("A4", "8%"); + model._set("A5", "1000"); + + model._set("B1", "=ACCRINT()"); + model._set("B2", "=ACCRINT(A1,A2,A3,A4,A5)"); + model._set("B3", "=ACCRINT(A1,A2,A3,A4,A5,2,0,TRUE,1)"); + model._set("C1", "=ACCRINT(A1,A2,A3,A4,A5,0)"); + model._set("C2", "=ACCRINT(A1,A2,A3,A4,A5,3)"); + model._set("C3", "=ACCRINT(A1,A2,A3,A4,A5,-1)"); + model._set("D1", "=ACCRINT(A1,A2,A3,A4,A5,2,-1)"); + model._set("D2", "=ACCRINT(A1,A2,A3,A4,A5,2,5)"); + model._set("E1", "=ACCRINT(A3,A2,A1,A4,A5,2)"); + model._set("E2", "=ACCRINT(A1,A3,A1,A4,A5,2)"); + model._set("F1", "=ACCRINT(A1,A2,A3,A4,0,2)"); + model._set("F2", "=ACCRINT(A1,A2,A3,A4,-1000,2)"); + model._set("F3", "=ACCRINT(A1,A2,A3,-8%,A5,2)"); + + model.evaluate(); + + assert_eq!(model._get_text("B1"), *"#ERROR!"); + assert_eq!(model._get_text("B2"), *"#ERROR!"); + assert_eq!(model._get_text("B3"), *"#ERROR!"); + assert_eq!(model._get_text("C1"), *"#NUM!"); + assert_eq!(model._get_text("C2"), *"#NUM!"); + assert_eq!(model._get_text("C3"), *"#NUM!"); + assert_eq!(model._get_text("D1"), *"#NUM!"); + assert_eq!(model._get_text("D2"), *"#NUM!"); + assert_eq!(model._get_text("E1"), *"#NUM!"); + assert_eq!(model._get_text("E2"), *"#NUM!"); + assert_eq!(model._get_text("F2"), *"#NUM!"); + assert_eq!(model._get_text("F3"), *"#NUM!"); + + match model.get_cell_value_by_ref("Sheet1!F1") { + Ok(CellValue::Number(v)) => { + assert!((v - 0.0).abs() < 1e-9); + } + other => unreachable!("Expected 0 for F1, got {:?}", other), + } +} + +#[test] +fn fn_accrint_combined() { + let mut model = new_empty_model(); + model._set("A1", "=DATE(2018,10,15)"); + model._set("A2", "=DATE(2019,2,1)"); + model._set("A3", "5%"); + model._set("A4", "1000"); + + model._set("B1", "=ACCRINT(A1,A1,A2,A3,A4,2)"); + + model.evaluate(); + + match model.get_cell_value_by_ref("Sheet1!B1") { + Ok(CellValue::Number(v)) => { + assert!((v - 14.722222222222221).abs() < 1e-9); + } + other => unreachable!("Expected number for B1, got {:?}", other), + } +} diff --git a/base/src/test/test_fn_accrintm.rs b/base/src/test/test_fn_accrintm.rs new file mode 100644 index 0000000..a85a3a4 --- /dev/null +++ b/base/src/test/test_fn_accrintm.rs @@ -0,0 +1,122 @@ +#![allow(clippy::unwrap_used)] + +use crate::{cell::CellValue, test::util::new_empty_model}; + +#[test] +fn fn_accrintm() { + let mut model = new_empty_model(); + model._set("A1", "=DATE(2020,1,1)"); + model._set("A2", "=DATE(2020,7,1)"); + model._set("A3", "10%"); + model._set("A4", "$1,000"); + + model._set("B1", "=ACCRINTM(A1,A2,A3,A4)"); + model._set("C1", "=ACCRINTM(A1)"); + + model.evaluate(); + + match model.get_cell_value_by_ref("Sheet1!B1") { + Ok(CellValue::Number(v)) => { + assert!((v - 50.0).abs() < 1e-9); + } + other => unreachable!("Expected number for B1, got {:?}", other), + } + assert_eq!(model._get_text("C1"), *"#ERROR!"); +} + +#[test] +fn fn_accrintm_parameters() { + let mut model = new_empty_model(); + + model._set("A1", "=DATE(2020,1,1)"); + model._set("A2", "=DATE(2020,7,1)"); + model._set("A3", "8%"); + model._set("A4", "1000"); + + model._set("B1", "=ACCRINTM(A1,A2,A3,A4,0)"); + model._set("B2", "=ACCRINTM(A1,A2,A3,A4,1)"); + model._set("B3", "=ACCRINTM(A1,A2,A3,A4,4)"); + model._set("C1", "=ACCRINTM(A1,A2,A3,A4)"); + + model.evaluate(); + + match ( + model.get_cell_value_by_ref("Sheet1!B1"), + model.get_cell_value_by_ref("Sheet1!B2"), + ) { + (Ok(CellValue::Number(v1)), Ok(CellValue::Number(v2))) => { + assert!(v1 > 0.0 && v2 > 0.0); + } + other => unreachable!("Expected numbers for basis test, got {:?}", other), + } + + match ( + model.get_cell_value_by_ref("Sheet1!B1"), + model.get_cell_value_by_ref("Sheet1!C1"), + ) { + (Ok(CellValue::Number(v1)), Ok(CellValue::Number(v2))) => { + assert!((v1 - v2).abs() < 1e-12); + } + other => unreachable!( + "Expected matching numbers for default test, got {:?}", + other + ), + } +} + +#[test] +fn fn_accrintm_errors() { + let mut model = new_empty_model(); + + model._set("A1", "=DATE(2020,1,1)"); + model._set("A2", "=DATE(2020,7,1)"); + model._set("A3", "8%"); + model._set("A4", "1000"); + + model._set("B1", "=ACCRINTM()"); + model._set("B2", "=ACCRINTM(A1,A2,A3)"); + model._set("B3", "=ACCRINTM(A1,A2,A3,A4,0,1)"); + model._set("C1", "=ACCRINTM(A1,A2,A3,A4,-1)"); + model._set("C2", "=ACCRINTM(A1,A2,A3,A4,5)"); + model._set("D1", "=ACCRINTM(A2,A1,A3,A4)"); + model._set("E1", "=ACCRINTM(A1,A2,A3,0)"); + model._set("E2", "=ACCRINTM(A1,A2,A3,-1000)"); + model._set("E3", "=ACCRINTM(A1,A2,-8%,A4)"); + + model.evaluate(); + + assert_eq!(model._get_text("B1"), *"#ERROR!"); + assert_eq!(model._get_text("B2"), *"#ERROR!"); + assert_eq!(model._get_text("B3"), *"#ERROR!"); + assert_eq!(model._get_text("C1"), *"#NUM!"); + assert_eq!(model._get_text("C2"), *"#NUM!"); + assert_eq!(model._get_text("D1"), *"#NUM!"); + assert_eq!(model._get_text("E2"), *"#NUM!"); + assert_eq!(model._get_text("E3"), *"#NUM!"); + + match model.get_cell_value_by_ref("Sheet1!E1") { + Ok(CellValue::Number(v)) => { + assert!((v - 0.0).abs() < 1e-9); + } + other => unreachable!("Expected 0 for E1, got {:?}", other), + } +} + +#[test] +fn fn_accrintm_combined() { + let mut model = new_empty_model(); + model._set("C1", "=DATE(2016,4,5)"); + model._set("C2", "=DATE(2019,2,1)"); + model._set("A3", "5%"); + model._set("A4", "1000"); + model._set("B2", "=ACCRINTM(C1,C2,A3,A4)"); + + model.evaluate(); + + match model.get_cell_value_by_ref("Sheet1!B2") { + Ok(CellValue::Number(v)) => { + assert!((v - 141.11111111111111).abs() < 1e-9); + } + other => unreachable!("Expected number for B2, got {:?}", other), + } +} diff --git a/docs/src/functions/financial.md b/docs/src/functions/financial.md index 909abd7..c58f9b0 100644 --- a/docs/src/functions/financial.md +++ b/docs/src/functions/financial.md @@ -11,8 +11,8 @@ You can track the progress in this [GitHub issue](https://github.com/ironcalc/Ir | Function | Status | Documentation | | ---------- | ---------------------------------------------- | ------------------ | -| ACCRINT | | – | -| ACCRINTM | | – | +| ACCRINT | | [ACCRINT](financial/accrint) | +| ACCRINTM | | [ACCRINTM](financial/accrintm) | | AMORDEGRC | | – | | AMORLINC | | – | | COUPDAYBS | | – | diff --git a/docs/src/functions/financial/accrint.md b/docs/src/functions/financial/accrint.md index 453b66c..908b5a7 100644 --- a/docs/src/functions/financial/accrint.md +++ b/docs/src/functions/financial/accrint.md @@ -7,6 +7,5 @@ lang: en-US # ACCRINT ::: warning -🚧 This function is not yet available in IronCalc. -[Follow development here](https://github.com/ironcalc/IronCalc/labels/Functions) +🚧 This function is implemented but currently lacks detailed documentation. For guidance, you may refer to the equivalent functionality in [Microsoft Excel documentation](https://support.microsoft.com/en-us/office/excel-functions-by-category-5f91f4e9-7b42-46d2-9bd1-63f26a86c0eb). ::: \ No newline at end of file diff --git a/docs/src/functions/financial/accrintm.md b/docs/src/functions/financial/accrintm.md index 0de1e3f..e4150e2 100644 --- a/docs/src/functions/financial/accrintm.md +++ b/docs/src/functions/financial/accrintm.md @@ -7,6 +7,5 @@ lang: en-US # ACCRINTM ::: warning -🚧 This function is not yet available in IronCalc. -[Follow development here](https://github.com/ironcalc/IronCalc/labels/Functions) +🚧 This function is implemented but currently lacks detailed documentation. For guidance, you may refer to the equivalent functionality in [Microsoft Excel documentation](https://support.microsoft.com/en-us/office/excel-functions-by-category-5f91f4e9-7b42-46d2-9bd1-63f26a86c0eb). ::: \ No newline at end of file