Esc

    The API is under testing and may change before release.

    Day counts

    This page lists everything you need to turn an accrual period into a year fraction: the twelve conventions, the three readings (two dates alone, a coupon in its schedule, or a stated calendar), their arguments, and the cases where two conventions or two readings part.

    Quick example

    This measures one period, 15 February to 31 August 2026, under all twelve conventions.

    Rust
    use chrono::{Duration, NaiveDate};
    use helixrs::DayCountFraction;
    
    fn date(year: i32, month: u32, day: u32) -> NaiveDate {
        NaiveDate::from_ymd_opt(year, month, day).expect("a real calendar date")
    }
    
    fn main() -> Result<(), Box<dyn std::error::Error>> {
        let start = date(2026, 2, 15);
        let end = date(2026, 8, 31);
        let actual_days = (end - start).num_days();
    
        // One period, twelve conventions. The convention and its code string name the
        // same convention, so either may be passed wherever a day count is taken.
        let members = [
            DayCountFraction::Act360,
            DayCountFraction::Act365Fixed,
            DayCountFraction::Nl365,
            DayCountFraction::Thirty360,
            DayCountFraction::Thirty360Us,
            DayCountFraction::ThirtyE360,
            DayCountFraction::ThirtyE360Isda,
            DayCountFraction::ActActIsda,
            DayCountFraction::ActActIcma,
            DayCountFraction::ActActAfb,
            DayCountFraction::Bus252,
            DayCountFraction::One,
        ];
        println!("{start} to {end}: {actual_days} actual days");
        println!("{:<14} year fraction", "code");
        for member in members {
            let fraction = member.year_fraction(start, end);
            println!("{:<14} {fraction:.10}", member.code());
        }
    
        // The two fixed-denominator counts are actual days over their denominator.
    
        Ok(())
    }
    Python
    import datetime as dt
    
    import helixio as hlx
    
    DayCountFraction = hlx.dates.DayCountFraction
    START = dt.date(2026, 2, 15)
    END = dt.date(2026, 8, 31)
    ACTUAL_DAYS = (END - START).days
    
    # One period, twelve conventions. The typed member and its code string name the
    # same convention, so either may be passed wherever a day count is taken.
    members = [
        DayCountFraction.Act360,
        DayCountFraction.Act365Fixed,
        DayCountFraction.Nl365,
        DayCountFraction.Thirty360,
        DayCountFraction.Thirty360Us,
        DayCountFraction.ThirtyE360,
        DayCountFraction.ThirtyE360Isda,
        DayCountFraction.ActActIsda,
        DayCountFraction.ActActIcma,
        DayCountFraction.ActActAfb,
        DayCountFraction.Bus252,
        DayCountFraction.One,
    ]
    print(f"{START} to {END}: {ACTUAL_DAYS} actual days")
    print(f"{'code':<14} year fraction")
    fractions = {}
    for member in members:
        fraction = member.year_fraction(START, END)
        fractions[member.code()] = fraction
        print(f"{member.code():<14} {fraction:.10f}")
    
    # The two fixed-denominator counts are actual days over their denominator.
    2026-02-15 to 2026-08-31: 197 actual days
    code           year fraction
    ACT/360        0.5472222222
    ACT/365.FIXED  0.5397260274
    NL/365         0.5397260274
    30/360         0.5444444444
    30U/360        0.5444444444
    30E/360        0.5416666667
    30E/360.ISDA   0.5416666667
    ACT/ACT.ISDA   0.5397260274
    ACT/ACT.ICMA   0.5000000000
    ACT/ACT.AFB    0.5397260274
    BUS/252        0.5317460317
    1/1            1.0000000000

    The same period comes out a few days' interest apart from one convention to the next, which is why a coupon on the wrong convention is wrong.

    How it's priced

    With DD the actual days from t0t_0 to t1t_1, the actual conventions are

    τACT/360=D360,τACT/365.FIXED=D365.\tau_{\text{ACT/360}} = \frac{D}{360}, \qquad \tau_{\text{ACT/365.FIXED}} = \frac{D}{365}.

    ACT/ACT.ISDA splits the period at each year boundary and divides each part by its own year's length; NL/365 is ACT/365.FIXED without 29 February. The 30/360 family writes each date as (y,m,d)(y, m, d) with adjusted days d0′,d1′d_0', d_1':

    τ=360 (y1−y0)+30 (m1−m0)+(d1′−d0′)360.\tau = \frac{360\,(y_1 - y_0) + 30\,(m_1 - m_0) + (d_1' - d_0')}{360}.

    A coupon t0,t1)t_0, t_1) inside a regular period [s0,s1)[s_0, s_1) of a schedule paying ff times a year accrues, under ACT/ACT.ICMA,

    τ=t1−t0f (s1−s0),\tau = \frac{t_1 - t_0}{f\,(s_1 - s_0)},

    BUS/252 is τ=Nb/252\tau = N_b / 252, with NbN_b the business days in [t0,t1)[t_0, t_1), and 1/1 is τ=1\tau = 1 for any period, so a zero-coupon period accrues its whole rate once. Which market uses which convention is covered in [how dates and conventions work.

    API

    Symbol Meaning Rust Python
    τ(t0,t1)\tau(t_0, t_1) the reading from two dates alone DayCountFraction::year_fraction DayCountFraction.year_fraction
    τ\tau with [s0,s1)[s_0, s_1), ff the schedule-aware reading DayCountFraction::year_fraction_with_context with a PeriodContext DayCountFraction.year_fraction_with_context
    Nb/252N_b / 252 on a stated calendar DayCountFraction::year_fraction_with_calendar DayCountFraction.year_fraction_with_calendar
    DD, d0′d_0', d1′d_1', yy, mm the dates' parts the two dates you pass the two dates you pass
    a convention one of twelve, by member or code DayCountFraction, parsed from its code dates.DayCountFraction
    Argument Type Default Meaning
    DayCountFraction(code) str required "ACT/360", "ACT/365.FIXED", "NL/365", "30/360", "30U/360", "30E/360", "30E/360.ISDA", "ACT/ACT.ISDA", "ACT/ACT.ICMA", "ACT/ACT.AFB", "BUS/252", "1/1"; case is ignored
    year_fraction(start, end) date, date required the context-free reading
    year_fraction_with_context(start, end, period_end, frequency, schedule_end, ...) date, date, date, Frequency or str, date required the reading inside a schedule: the coupon's period end, the schedule's frequency and its last date
    year_fraction_with_context(..., end_of_month=) bool False whether the schedule rolls on month end, which turns on the 30U/360 February rule
    year_fraction_with_context(..., reference_days=) int or None None states the regular period's length in days instead of deriving it
    year_fraction_with_calendar(start, end, calendar) date, date, Calendar required BUS/252 counts this calendar; every other convention ignores it

    Defaults and limits

    • Leap years. Across 29 February, ACT/ACT.ISDA divides each year's part by its own length, ACT/365.FIXED divides the whole by 365, and NL/365 doesn't count 29 February at all.
    • ACT/ACT.ICMA needs its schedule. A full regular period accrues exactly 1/f1/f. From two dates alone, the convention takes the dates as their own reference period, so a short first coupon reads as a whole number of months, not its accrual. Use year_fraction_with_context.
    • The 30U/360 February rule applies only in a schedule that rolls on month end: the last day of February then counts as the 30th. From two dates alone, 30U/360 agrees with 30/360.
    • BUS/252 defaults to Brazil. With no calendar it counts BRBD, Brazil's national calendar; a city calendar such as BRSP can only make the count smaller. São Paulo closes on 24 and 31 December, so a period across the year end counts two fewer days there.
    • Codes are matched ignoring case, and a spelling that isn't a code is refused with the list of codes that are. ACT/365 is not ACT/365.FIXED.

    !!! warning "A member is not equal to its string" DayCountFraction.Act360 != "ACT/360". Compare member.code() with the string, or parse the string first.

    More examples

    The three readings in one program: a leap year, the ICMA reference period of a short first coupon, 30U/360 and February, BUS/252 on two calendars, and naming a day count. The example reads no shared data.

    Rust
    use chrono::{Duration, NaiveDate};
    use helixrs::PeriodContext;
    use helixrs::{DayCountFraction, Frequency, Period, add_period, calendar};
    
    fn date(year: i32, month: u32, day: u32) -> NaiveDate {
        NaiveDate::from_ymd_opt(year, month, day).expect("a real calendar date")
    }
    
    fn days(start: NaiveDate, end: NaiveDate) -> f64 {
        (end - start).num_days() as f64
    }
    
    fn main() -> Result<(), Box<dyn std::error::Error>> {
    
        // A period across the end of a leap year. ACT/ACT.ISDA splits it at the year
        // boundary and divides each part by its own year's length; ACT/365.FIXED
        // divides the whole by 365; NL/365 does not count 29 February at all.
        let leap_start = date(2027, 11, 15);
        let leap_end = date(2028, 5, 15);
        let isda = DayCountFraction::ActActIsda.year_fraction(leap_start, leap_end);
        let fixed = DayCountFraction::Act365Fixed.year_fraction(leap_start, leap_end);
        let no_leap = DayCountFraction::Nl365.year_fraction(leap_start, leap_end);
    
        let in_2027 = days(leap_start, date(2028, 1, 1));
        let in_2028 = days(date(2028, 1, 1), leap_end);
    
        println!("{leap_start} to {leap_end}: {in_2027} days in 2027, {in_2028} in 2028");
        println!("ACT/ACT.ISDA   {isda:.10}");
        println!("ACT/365.FIXED  {fixed:.10}");
        println!("NL/365         {no_leap:.10}");
    
        // ACT/ACT.ICMA measures a period against the regular coupon period it sits
        // in. A semiannual bond's short first coupon runs from issue to 15 June; the
        // regular period it belongs to is the six months before 15 June.
        let issue = date(2026, 3, 10);
        let first_coupon = date(2026, 6, 15);
        let maturity = date(2031, 6, 15);
        let context = |period_end: NaiveDate| PeriodContext::Scheduled {
            period_end,
            frequency: Frequency::SemiAnnual,
            schedule_end: maturity,
            end_of_month: false,
            reference_days: None,
        };
        let icma = DayCountFraction::ActActIcma;
        let short_first =
            icma.year_fraction_with_context(issue, first_coupon, context(first_coupon));
        // A full regular period is exactly one over the frequency.
        let next_coupon = date(2026, 12, 15);
        let regular =
            icma.year_fraction_with_context(first_coupon, next_coupon, context(next_coupon));
        let six_months_back: Period = "-6M".parse().expect("a tenor");
        let notional_start = add_period(first_coupon, six_months_back);
    
        // Given two dates alone, the convention takes the dates as their own
        // reference period: the nearest whole number of months, as a share of a
        // year. The short first coupon then reads as three months, which is not
        // its accrual. A context that states no schedule gives the same answer.
        let alone = icma.year_fraction(issue, first_coupon);
    
        println!("notional period      {notional_start} to {first_coupon}");
        println!("short first coupon   {short_first:.10}");
        println!("regular coupon       {regular:.10}");
        println!("two dates alone      {alone:.10}");
    
        // 30U/360 is the US 30/360 with the February rule: in a schedule that rolls on
        // month end, the last day of February counts as the 30th. Read from two dates
        // alone it applies no February rule and agrees with 30/360.
        let (feb_end, aug_end) = (date(2026, 2, 28), date(2026, 8, 31));
        let us = DayCountFraction::Thirty360Us;
        let alone_us = us.year_fraction(feb_end, aug_end);
        let month_end = us.year_fraction_with_context(
            feb_end,
            aug_end,
            PeriodContext::Scheduled {
                period_end: aug_end,
                frequency: Frequency::SemiAnnual,
                schedule_end: date(2031, 8, 31),
                end_of_month: true,
                reference_days: None,
            },
        );
        // Without the rule: 28 February is day 28, so 31 August stays day 31.
        // With it: 28 February is day 30, which makes 31 August day 30 too.
        println!("30U/360, two dates alone   {alone_us:.10}");
        println!("30U/360, month-end roll    {month_end:.10}");
    
        // Across a year end, where São Paulo closes on 24 and 31 December and the
        // national calendar does not.
        let (ye_start, ye_end) = (date(2027, 12, 1), date(2028, 1, 31));
        let sao_paulo = calendar("BRSP").expect("a registered business centre");
        let bus = DayCountFraction::Bus252;
        let national = bus.year_fraction(ye_start, ye_end);
        let with_calendar = bus.year_fraction_with_calendar(ye_start, ye_end, sao_paulo.as_ref());
        let business_days = sao_paulo.business_days_between(ye_start, ye_end);
    
        println!("BUS/252, two dates alone {national:.10}");
        println!("BUS/252, BRSP calendar   {with_calendar:.10} ({business_days} business days)");
    
        // A code is matched ignoring case, and a spelling that is not a code is
        // refused with the list of codes that are. ACT/365 is not ACT/365.FIXED.
        let folded: DayCountFraction = "act/360".parse().expect("a code in any case");
        let refused = if "ACT/365".parse::<DayCountFraction>().is_err() { "refused" } else { "" };
    
        println!("'act/360'  -> {}", folded.code());
        println!("'ACT/365'  -> {refused}");
    
        Ok(())
    }
    Python
    import datetime as dt
    
    import helixio as hlx
    
    DayCountFraction = hlx.dates.DayCountFraction
    
    # A period across the end of a leap year. ACT/ACT.ISDA splits it at the year
    # boundary and divides each part by its own year's length; ACT/365.FIXED divides
    # the whole by 365; NL/365 does not count 29 February at all.
    LEAP_START = dt.date(2027, 11, 15)
    LEAP_END = dt.date(2028, 5, 15)
    isda = DayCountFraction.ActActIsda.year_fraction(LEAP_START, LEAP_END)
    fixed = DayCountFraction.Act365Fixed.year_fraction(LEAP_START, LEAP_END)
    no_leap = DayCountFraction.Nl365.year_fraction(LEAP_START, LEAP_END)
    
    in_2027 = (dt.date(2028, 1, 1) - LEAP_START).days
    in_2028 = (LEAP_END - dt.date(2028, 1, 1)).days
    
    print(f"{LEAP_START} to {LEAP_END}: {in_2027} days in 2027, {in_2028} in 2028")
    print(f"ACT/ACT.ISDA   {isda:.10f}")
    print(f"ACT/365.FIXED  {fixed:.10f}")
    print(f"NL/365         {no_leap:.10f}")
    
    # ACT/ACT.ICMA measures a period against the regular coupon period it sits in.
    # A semiannual bond's short first coupon runs from issue to 15 June; the regular
    # period it belongs to is the six months before 15 June.
    ISSUE = dt.date(2026, 3, 10)
    FIRST_COUPON = dt.date(2026, 6, 15)
    MATURITY = dt.date(2031, 6, 15)
    short_first = DayCountFraction.ActActIcma.year_fraction_with_context(
        ISSUE, FIRST_COUPON, FIRST_COUPON, hlx.dates.Frequency.SemiAnnual, MATURITY
    )
    # A full regular period is exactly one over the frequency.
    regular = DayCountFraction.ActActIcma.year_fraction_with_context(
        FIRST_COUPON, dt.date(2026, 12, 15), dt.date(2026, 12, 15), "6M", MATURITY
    )
    notional_start = hlx.dates.add_period(FIRST_COUPON, "-6M")
    
    # Given two dates alone, the convention takes the dates as their own reference
    # period: the nearest whole number of months, as a share of a year. The short
    # first coupon then reads as three months, which is not its accrual.
    alone = DayCountFraction.ActActIcma.year_fraction(ISSUE, FIRST_COUPON)
    
    print(f"notional period      {notional_start} to {FIRST_COUPON}")
    print(f"short first coupon   {short_first:.10f}")
    print(f"regular coupon       {regular:.10f}")
    print(f"two dates alone      {alone:.10f}")
    
    # 30U/360 is the US 30/360 with the February rule: in a schedule that rolls on
    # month end, the last day of February counts as the 30th. Read from two dates
    # alone it applies no February rule and agrees with 30/360.
    FEB_END, AUG_END = dt.date(2026, 2, 28), dt.date(2026, 8, 31)
    US = DayCountFraction.Thirty360Us
    alone_us = US.year_fraction(FEB_END, AUG_END)
    month_end = US.year_fraction_with_context(FEB_END, AUG_END, AUG_END, "6M", dt.date(2031, 8, 31), end_of_month=True)
    # Without the rule: 28 February is day 28, so 31 August stays day 31.
    # With it: 28 February is day 30, which makes 31 August day 30 too.
    print(f"30U/360, two dates alone   {alone_us:.10f}")
    print(f"30U/360, month-end roll    {month_end:.10f}")
    
    # Across a year end, where São Paulo closes on 24 and 31 December and the
    # national calendar does not.
    YEAR_END = (dt.date(2027, 12, 1), dt.date(2028, 1, 31))
    SAO_PAULO = hlx.Calendar("BRSP")
    national = DayCountFraction.Bus252.year_fraction(*YEAR_END)
    with_calendar = DayCountFraction.Bus252.year_fraction_with_calendar(*YEAR_END, SAO_PAULO)
    business_days = SAO_PAULO.business_days_between(*YEAR_END)
    
    print(f"BUS/252, two dates alone {national:.10f}")
    print(f"BUS/252, BRSP calendar   {with_calendar:.10f} ({business_days} business days)")
    
    # A code is matched ignoring case, and a spelling that is not a code is refused
    # with the list of codes that are. ACT/365 is not ACT/365.FIXED.
    refused = ""
    try:
        DayCountFraction("ACT/365")
    except ValueError:
        refused = "refused"
    
    print(f"'act/360'  -> {DayCountFraction('act/360').code()}")
    print(f"'ACT/365'  -> {refused}")
    2027-11-15 to 2028-05-15: 47 days in 2027, 135 in 2028
    ACT/ACT.ISDA   0.4976195823
    ACT/365.FIXED  0.4986301370
    NL/365         0.4958904110
    notional period      2025-12-15 to 2026-06-15
    short first coupon   0.2664835165
    regular coupon       0.5000000000
    two dates alone      0.2500000000
    30U/360, two dates alone   0.5083333333
    30U/360, month-end roll    0.5000000000
    BUS/252, two dates alone 0.1706349206
    BUS/252, BRSP calendar   0.1626984127 (41 business days)
    'act/360'  -> ACT/360
    'ACT/365'  -> refused

    See also

    • Source: crates/helixrs/src/statics/daycounts.rs
    • Related pages:
      • How dates and conventions work, for which market uses which convention, and each convention rebuilt from its formula.
      • Calendars, for the business days BUS/252 counts.
      • Schedules, for the periods a day count measures.
      • Legs and coupons, for the day count a coupon accrues on.

    From the 0.0 line at revision bf7f2e2. Every printed number is that revision's own output.