Shared Rust kernels
ported/coupon_family
All modules · quoted at commit 9e1884369a67 of DnaCalc/DnaCalc. Comments are the source's own.
max_excel_serial · 3 lines · calc-func/src/ported/coupon_family.rs on GitHubfn max_excel_serial() -> i64 {
excel_serial_from_ymd(WorkbookDateSystem::System1900, 9999, 12, 31).unwrap() as i64
}
Used by 6 functions.
days_in_month · 11 lines · calc-func/src/ported/coupon_family.rs on GitHubfn days_in_month(year: i64, month: i64) -> i64 {
match month {
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
4 | 6 | 9 | 11 => 30,
2 => {
let leap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
if leap { 29 } else { 28 }
}
_ => 30,
}
}
Used by 6 functions.
days_from_civil · 9 lines · calc-func/src/ported/coupon_family.rs on GitHubfn days_from_civil(year: i64, month: i64, day: i64) -> i64 {
let year = year - i64::from(month <= 2);
let era = if year >= 0 { year } else { year - 399 } / 400;
let yoe = year - era * 400;
let mp = month + if month > 2 { -3 } else { 9 };
let doy = (153 * mp + 2) / 5 + day - 1;
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
era * 146097 + doe - 719468
}
Used by 6 functions.
excel_serial_from_ymd_unbounded_1900 · 8 lines · calc-func/src/ported/coupon_family.rs on GitHubfn excel_serial_from_ymd_unbounded_1900(year: i64, month: i64, day: i64) -> i64 {
if year == 1900 && month == 2 && day == 29 {
return 60;
}
let base = days_from_civil(1899, 12, 31);
let days = days_from_civil(year, month, 1) - base + (day - 1);
if days >= 60 { days + 1 } else { days }
}
Used by 6 functions.
calendar_days360 · 45 lines · calc-func/src/ported/coupon_family.rs on GitHubfn calendar_days360(
start: i64,
end: i64,
adjustment: CalendarDayAdjustment,
) -> Result<f64, WorksheetErrorCode> {
let (sy, sm, mut sd) = ymd_from_excel_serial(WorkbookDateSystem::System1900, start as f64)
.ok_or(WorksheetErrorCode::Value)?;
let (mut ey, mut em, mut ed) = ymd_from_excel_serial(WorkbookDateSystem::System1900, end as f64)
.ok_or(WorksheetErrorCode::Value)?;
if matches!(adjustment, CalendarDayAdjustment::CouponAccrued) {
let start_last_feb = sm == 2 && sd == days_in_month(sy, sm);
let end_last_feb = em == 2 && ed == days_in_month(ey, em);
// Coupon accrual adjusts a day-31 end using the original start day, before
// normalizing a February-end start. The shared DAYS360 convention differs.
// Fn7 paired COUPDAYBS/COUPDAYSNC controls cover real and fictitious February ends.
if ed == 31 {
if sd < 30 {
(ey, em) = if em == 12 { (ey + 1, 1) } else { (ey, em + 1) };
ed = 1;
} else {
ed = 30;
}
}
if sd == 31 || start_last_feb {
sd = 30;
}
if start_last_feb && end_last_feb {
ed = 30;
}
return Ok(((ey - sy) * 360 + (em - sm) * 30 + (ed - sd)) as f64);
}
if sd == 31
|| (matches!(adjustment, CalendarDayAdjustment::CouponPeriod)
&& sm == 2 && sd == days_in_month(sy, sm))
{
sd = 30;
}
if ed == 31
|| (matches!(adjustment, CalendarDayAdjustment::CouponPeriod)
&& em == 2 && ed == days_in_month(ey, em))
{
ed = 30;
}
Ok(((ey - sy) * 360 + (em - sm) * 30 + (ed - sd)) as f64)
}
Used by 2 functions.
actual_days · 3 lines · calc-func/src/ported/coupon_family.rs on GitHubfn actual_days(start: i64, end: i64) -> f64 {
(end - start) as f64
}
Used by 3 functions.
accrued_or_remaining_days · 17 lines · calc-func/src/ported/coupon_family.rs on GitHubfn accrued_or_remaining_days(
start: i64,
end: i64,
basis: CouponBasis,
) -> Result<f64, WorksheetErrorCode> {
match basis {
CouponBasis::Us30_360 => {
calendar_days360(start, end, CalendarDayAdjustment::CouponAccrued)
}
CouponBasis::ActualActual | CouponBasis::Actual360 | CouponBasis::Actual365 => {
Ok(actual_days(start, end))
}
CouponBasis::European30_360 => {
calendar_days360(start, end, CalendarDayAdjustment::European)
}
}
}
Used by 2 functions.
parse_basis · 13 lines · calc-func/src/ported/coupon_family.rs on GitHubfn parse_basis(value: f64) -> Result<CouponBasis, WorksheetErrorCode> {
if !value.is_finite() {
return Err(WorksheetErrorCode::Num);
}
match value.trunc() as i64 {
0 => Ok(CouponBasis::Us30_360),
1 => Ok(CouponBasis::ActualActual),
2 => Ok(CouponBasis::Actual360),
3 => Ok(CouponBasis::Actual365),
4 => Ok(CouponBasis::European30_360),
_ => Err(WorksheetErrorCode::Num),
}
}
Used by 6 functions.
parse_frequency · 9 lines · calc-func/src/ported/coupon_family.rs on GitHubfn parse_frequency(value: f64) -> Result<i64, WorksheetErrorCode> {
if !value.is_finite() {
return Err(WorksheetErrorCode::Num);
}
match value.trunc() as i64 {
1 | 2 | 4 => Ok(value.trunc() as i64),
_ => Err(WorksheetErrorCode::Num),
}
}
Used by 6 functions.
check_coupon_dates · 10 lines · calc-func/src/ported/coupon_family.rs on GitHub/// The check of a COUP function's dates on their own: each a date, and settlement before
/// maturity; #NUM! otherwise.
pub(crate) fn check_coupon_dates(settlement: f64, maturity: f64) -> Result<(), WorksheetErrorCode> {
let settlement = parse_coupon_date(settlement)?;
let maturity = parse_coupon_date(maturity)?;
if settlement >= maturity {
return Err(WorksheetErrorCode::Num);
}
Ok(())
}
Used by 6 functions.
parse_coupon_date · 12 lines · calc-func/src/ported/coupon_family.rs on GitHubfn parse_coupon_date(value: f64) -> Result<i64, WorksheetErrorCode> {
if !value.is_finite() {
return Err(WorksheetErrorCode::Num);
}
let serial = value.trunc() as i64;
if serial < 0 || serial > max_excel_serial() {
return Err(WorksheetErrorCode::Num);
}
ymd_from_excel_serial(WorkbookDateSystem::System1900, serial as f64)
.ok_or(WorksheetErrorCode::Num)?;
Ok(serial)
}
Used by 6 functions.
parse_coupon_context · 20 lines · calc-func/src/ported/coupon_family.rs on GitHubfn parse_coupon_context(
settlement: f64,
maturity: f64,
frequency: f64,
basis: Option<f64>,
) -> Result<CouponContext, WorksheetErrorCode> {
let settlement = parse_coupon_date(settlement)?;
let maturity = parse_coupon_date(maturity)?;
let frequency = parse_frequency(frequency)?;
let basis = parse_basis(basis.unwrap_or(0.0))?;
if settlement >= maturity {
return Err(WorksheetErrorCode::Num);
}
Ok(CouponContext {
settlement,
maturity,
frequency,
basis,
})
}
Used by 6 functions.
coupon_date_before_maturity · 29 lines · calc-func/src/ported/coupon_family.rs on GitHub/// Coupon date `k` periods before maturity, computed from maturity itself rather than by
/// chaining one period at a time: a maturity on the 28th-30th keeps that day of month in every
/// month long enough for it (clamped only in shorter months), and only a month-end maturity
/// gives month-end coupons. Chaining off an already-clamped date drifted to month ends after
/// the first short month (maturity 2046-08-28 quarterly gave a next coupon of 2045-05-31
/// where Excel gives 2045-05-28). Live Excel 20430, W111-5 G8-08.
fn coupon_date_before_maturity(ctx: CouponContext, periods: i64, preserve_month_end: bool) -> Option<i64> {
let months_per_coupon = 12 / ctx.frequency;
let (year, month, day) =
ymd_from_excel_serial(WorkbookDateSystem::System1900, ctx.maturity as f64)?;
let month_index = year
.checked_mul(12)?
.checked_add(month - 1)?
.checked_sub(periods.checked_mul(months_per_coupon)?)?;
let target_year = month_index.div_euclid(12);
let target_month = month_index.rem_euclid(12) + 1;
let month_end = days_in_month(target_year, target_month);
let maturity_is_month_end = preserve_month_end && day == days_in_month(year, month);
let target_day = if maturity_is_month_end {
month_end
} else {
day.min(month_end)
};
Some(excel_serial_from_ymd_unbounded_1900(
target_year,
target_month,
target_day,
))
}
Used by 6 functions.
locate_coupon_period · 17 lines · calc-func/src/ported/coupon_family.rs on GitHubfn locate_coupon_period(ctx: CouponContext) -> Result<CouponPeriod, WorksheetErrorCode> {
let mut next = ctx.maturity;
let mut coupons = 1i64;
loop {
let previous =
coupon_date_before_maturity(ctx, coupons, true).ok_or(WorksheetErrorCode::Num)?;
if previous <= ctx.settlement {
return Ok(CouponPeriod {
previous: previous.max(0),
next,
remaining_coupons: coupons,
});
}
next = previous;
coupons += 1;
}
}
Used by 6 functions.