Shared Rust kernels

ported/day_count_common

All modules · quoted at commit 9e1884369a67 of DnaCalc/DnaCalc. Comments are the source's own.

days_in_month · 11 lines · calc-func/src/ported/day_count_common.rs on GitHub
fn 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 13 functions.

us_30_360 · 34 lines · calc-func/src/ported/day_count_common.rs on GitHub
/// NASD US (30/360) day count between two Excel serial dates.
///
/// Excel's basis-0 rule rolls the end date to the 1st of the following month
/// when the end day is 31 and the (already adjusted) start day is below 30,
/// rather than collapsing the end day to 30 in place. The month roll keeps the
/// count non-negative for forward ranges; the in-place form produces short and
/// even negative counts for same-month spans.
pub fn us_30_360(start: i64, end: i64) -> Result<f64, WorksheetErrorCode> {
    let (sy, sm, mut sd) = ymd_from_excel_serial(WorkbookDateSystem::System1900, start as f64)
        .ok_or(WorksheetErrorCode::Value)?;
    let (ey, em, mut ed) = ymd_from_excel_serial(WorkbookDateSystem::System1900, end as f64)
        .ok_or(WorksheetErrorCode::Value)?;

    // Excel's 1900 calendar has a Feb 29 (serial 60), so in 1900 the 29th is the month end.
    let feb_end = |y: i64, m: i64| if y == 1900 { 29 } else { days_in_month(y, m) };
    let start_last_feb = sm == 2 && sd == feb_end(sy, sm);
    let end_last_feb = em == 2 && ed == feb_end(ey, em);

    if sd == 31 || start_last_feb {
        sd = 30;
    }
    if ed == 31 {
        if sd < 30 {
            let (ny, nm) = if em == 12 { (ey + 1, 1) } else { (ey, em + 1) };
            return Ok((ny - sy) as f64 * 360.0 + (nm - sm) as f64 * 30.0 + (1 - sd) as f64);
        }
        ed = 30;
    }
    if end_last_feb && start_last_feb {
        ed = 30;
    }

    Ok(((ey - sy) * 360 + (em - sm) * 30 + (ed - sd)) as f64)
}

Used by 13 functions.

real_days_in_year · 7 lines · calc-func/src/ported/day_count_common.rs on GitHub
fn real_days_in_year(year: i64) -> f64 {
    if (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 {
        366.0
    } else {
        365.0
    }
}

Used by 6 functions.

excel_actual_actual_year · 44 lines · calc-func/src/ported/day_count_common.rs on GitHub
/// Excel's actual/actual day count (YEARFRAC basis 1, and ACCRINTM basis 1) for `start <= end`
/// serials, as the pair (days, year length) whose quotient is the year fraction. Not the ISDA
/// per-year split:
///
/// * if the dates are at most a year apart (same year, or the next year on an earlier or
///   equal month/day), the year length is 366 when both dates are in one leap year or a Feb 29
///   lies in the span (start before 1 March of a leap start year, or end on/after 1 March of a
///   leap end year, or the end is a real Feb 29), else 365, and the fraction is days / year length;
/// * otherwise days / (days in the whole years start..=end / number of years), the year total
///   on the real calendar while `days` is the serial difference (so it includes Excel's
///   fictitious 1900-02-29).
///
/// The rule is David Wheeler's YEARFRAC analysis (used by Gnumeric and LibreOffice);
/// reproduced on live Excel 20430 on two fresh corpora (W111-5 G8-05, G8-06).
pub fn excel_actual_actual_year(start: i64, end: i64) -> Result<(f64, f64), WorksheetErrorCode> {
    let ymd = |serial: i64| {
        ymd_from_excel_serial(WorkbookDateSystem::System1900, serial as f64)
            .ok_or(WorksheetErrorCode::Value)
    };
    let serial_of = |y: i64, m: i64, d: i64| {
        excel_serial_from_ymd(WorkbookDateSystem::System1900, y, m, d)
            .map(|v| v as i64)
            .ok_or(WorksheetErrorCode::Value)
    };
    let is_leap = |y: i64| real_days_in_year(y) == 366.0;
    let (sy, sm, sd) = ymd(start)?;
    let (ey, em, ed) = ymd(end)?;
    let days = (end - start) as f64;
    let within_a_year = sy == ey || (ey == sy + 1 && (sm > em || (sm == em && sd >= ed)));
    if within_a_year {
        let feb29_in_span = (is_leap(sy) && start < serial_of(sy, 3, 1)?)
            || (is_leap(ey) && end >= serial_of(ey, 3, 1)?)
            || (em == 2 && ed == 29 && is_leap(ey));
        let year_length = if (sy == ey && is_leap(sy)) || feb29_in_span {
            366.0
        } else {
            365.0
        };
        return Ok((days, year_length));
    }
    let years = (ey - sy + 1) as f64;
    let days_in_years: f64 = (sy..=ey).map(real_days_in_year).sum();
    Ok((days, days_in_years / years))
}

Used by 6 functions.

excel_actual_actual_fraction · 5 lines · calc-func/src/ported/day_count_common.rs on GitHub
/// [`excel_actual_actual_year`]'s days divided by its year length.
pub fn excel_actual_actual_fraction(start: i64, end: i64) -> Result<f64, WorksheetErrorCode> {
    let (days, year_length) = excel_actual_actual_year(start, end)?;
    Ok(days / year_length)
}

Used by 2 functions.