Debugging Day-Count Mismatches in Lease Interest Calculations

Diagnose an ASC 842 / IFRS 16 schedule whose opening liability and first interest figure disagree because present value used Actual/365 while accrual used 30/360, and thread one basis through both.

Problem Statement Link to this section

A lease schedule lands on the accountant's desk with a discrepancy that resists explanation: the opening liability ties out to the present-value working paper, and the payment vector is correct, yet the first period's interest is off by a few dollars and nothing downstream reconciles. The recursion is right, the rate is right, the balance is right — and the interest is still wrong. The cause is almost always a day-count mismatch: the present value discounted the payments using one convention (say Actual/365) while the interest accrual measured the first period on a different one (say 30/360). Because the two calculations disagree about how many days a period contains, the opening balance and the first interest figure are struck on incompatible clocks, and the schedule cannot be internally consistent. This page shows how to diagnose that mismatch, why the three common conventions produce different year fractions on the same two dates, and how to thread a single basis through both the discounting and the accrual so the schedule reconciles. It is one failure mode in the broader amortization precision troubleshooting cluster.

Standard Anchor Link to this section

Both frameworks require internal consistency between the measurement of the liability and the interest that unwinds it, even though neither prescribes a single day-count convention by name. ASC 842-20-30-1 measures the lease liability at the present value of the unpaid payments, and ASC 842-20-35-3 accrues interest on that carrying amount using the interest method; IFRS 16.26 and 16.36 set the same two-step structure. The standards leave the day-count basis to the contract and the entity's accounting policy — a real-estate lease may specify 30/360, an equipment lease Actual/365 — but they implicitly require that the same basis govern both steps, because the interest that produces a constant periodic rate on the remaining balance (IFRS 16.36) must be measured on the same time scale that placed the payments in the present-value sum. Discounting on Actual/365 and accruing on 30/360 breaks that constant-rate property at the first period and leaves a schedule an auditor cannot re-derive. The rate itself, whether the implicit rate or the lessee's incremental borrowing rate, is locked at commencement; the day-count basis must be locked alongside it.

Algorithm Specification: Three Conventions, One Pair of Dates Link to this section

Each convention computes a year fraction but defines the numerator and denominator differently. For a period from to :

30/360 treats every month as 30 days and every year as 360:

where and are the day numbers after the standard end-of-month adjustment. Actual/365 counts real calendar days over a fixed 365-day year:

Actual/Actual counts real calendar days over the real length of the year, 365 or 366:

The first-period interest on an opening liability at annual rate is , so a different yields a different on the identical dates and balance. The diagnostic check is that must equal using the same that discounting used; when it does not, the two steps ran on different bases.

Three day-count conventions applied to one pair of lease dates A single commencement-to-first-payment date pair, the 15th of January to the 1st of February 2024, feeds three convention lanes. The 30/360 lane counts sixteen adjusted days over three hundred sixty and yields a year fraction of about 0.0444 and first-period interest of 666.67. The Actual/365 lane counts seventeen real days over three hundred sixty-five and yields about 0.0466 and interest of 698.63. The Actual/Actual lane counts seventeen real days over three hundred sixty-six, the length of the leap year, and yields about 0.0464 and interest of 696.72. The three lanes converge on a single resolution: choose the contract's basis and use it in both the present value and the accrual. Same dates 15 Jan 2024 → 1 Feb 2024 L₀ · r = 15,000 30/360 Δ = 16 adjusted days ÷ 360 τ ≈ 0.04444 · I₁ = 666.67 Actual/365 Δ = 17 real days ÷ 365 τ ≈ 0.04658 · I₁ = 698.63 Actual/Actual Δ = 17 real days ÷ 366 (leap year) τ ≈ 0.04645 · I₁ = 696.72 Resolution pick the contract's basis, then use it in both PV and accrual one τ, both steps
The identical dates and balance produce three different first-period interest figures under 30/360, Actual/365, and Actual/Actual. The fix is not to prefer one convention universally but to pick the contract's basis and thread that single year fraction through both the present value and the accrual.

Annotated Python Snippet: One Basis Through Both Steps Link to this section

The module below threads a single basis value through both the opening present value and the first-period accrual, then asserts that the first interest figure equals the opening balance times the periodic factor computed on that same basis. If a mismatch were reintroduced, the assertion would fail.

from decimal import Decimal, ROUND_HALF_UP
from datetime import date

CENT = Decimal("0.01")

def year_fraction(start: date, end: date, basis: str) -> Decimal:
    """One year-fraction function, shared by PV and accrual."""
    if basis == "30_360":
        d1 = min(start.day, 30)
        d2 = 30 if (end.day == 31 and d1 == 30) else end.day
        days = (end.year - start.year) * 360 + (end.month - start.month) * 30 + (d2 - d1)
        return Decimal(days) / Decimal("360")
    if basis == "ACTUAL_ACTUAL":
        denom = Decimal("366") if end.year % 4 == 0 else Decimal("365")
        return Decimal((end - start).days) / denom
    return Decimal((end - start).days) / Decimal("365")     # ACTUAL_365

def opening_and_first_interest(payments, annual_rate, commencement, basis):
    base = Decimal(1) + annual_rate
    pv = Decimal("0")
    for due, amount in payments:                            # PV on the shared basis
        pv += amount / (base ** year_fraction(commencement, due, basis))
    opening = pv.quantize(CENT, ROUND_HALF_UP)
    first_due = payments[0][0]                              # accrual on the SAME basis
    tau1 = year_fraction(commencement, first_due, basis)
    interest1 = (opening * (base ** tau1 - Decimal(1))).quantize(CENT, ROUND_HALF_UP)
    return opening, interest1, tau1

flows = [(date(2024, 2, 1), Decimal("15000.00")),
         (date(2024, 3, 1), Decimal("15000.00")),
         (date(2024, 4, 1), Decimal("15000.00"))]
opening, interest1, tau1 = opening_and_first_interest(
    flows, Decimal("0.06"), date(2024, 1, 15), "ACTUAL_365")

# Both steps used one basis, so first interest reconciles to opening x period factor.
check = (opening * ((Decimal(1) + Decimal("0.06")) ** tau1 - Decimal(1))).quantize(CENT, ROUND_HALF_UP)
assert interest1 == check, "day-count mismatch: PV and accrual used different bases"
print(f"opening={opening} first interest={interest1} on tau={tau1}")

The assertion is the diagnostic made permanent: it recomputes the first interest from the opening balance and the same-basis factor, so any future refactor that lets the PV service and the schedule service default to different conventions breaks the build rather than the audit.

Correct vs. Incorrect: Three Conventions on the Same Dates Link to this section

Commencement 15 January 2024, first payment 1 February 2024, opening balance times annual rate equal to 15,000 for the period. Only the convention differs.

Convention Day-count numerator Denominator Year fraction First interest
30/360 16 adjusted days 360 0.04444 666.67
Actual/365 17 real days 365 0.04658 698.63
Actual/Actual 17 real days 366 (leap year) 0.04645 696.72
Mismatch (PV Actual/365, accrual 30/360) conflicting conflicting inconsistent fails re-derivation

The three single-convention rows are each internally correct — the choice follows the contract. The final row is the defect: mixing Actual/365 discounting with 30/360 accrual produces an opening balance and a first interest figure that cannot both be right, and the schedule will not reconcile.

Gotcha: Two Services, Two Defaults Link to this section

The mismatch rarely comes from a single function using two conventions; it comes from two components each using its own default. A present-value microservice may default to Actual/365 while the schedule generator defaults to 30/360, and each is individually tested and individually correct. The bug only appears when they are wired together on a real lease.

pv = present_value(flows, rate, basis="ACTUAL_365")     # PV service default
sched = build_schedule(pv, rate, basis="30_360")        # schedule service default
# opening seeded on Actual/365, interest accrued on 30/360 -> irreconcilable

Guard against it by making basis a required, non-defaulted parameter that is set once from the lease record and passed explicitly into every step. Store the basis on the lease alongside the locked rate so both services read the same source of truth, and add the reconciliation assertion above to a shared test so a divergence fails in CI.

Frequently Asked Questions Link to this section

Which day-count convention should a lease use?

The one the contract specifies, or the entity's documented default where the contract is silent. Real-estate leases commonly use 30/360, which keeps every month's interest equal and simplifies reconciliation; equipment and many financing arrangements use an actual-days basis. There is no universally correct choice — the requirement is consistency, so whichever basis is selected must govern both the present value and the accrual and be locked to the lease alongside the discount rate.

How do I tell a day-count mismatch apart from a stub-period error?

Check what reconciles. In a day-count mismatch the opening liability and the first interest figure are struck on different conventions, so recomputing first interest as opening times the same-basis factor fails even though each component looks correct. A stub-period error uses one consistent basis but forgets that the first period is short, charging a full period of interest for a partial one. The mismatch shows two components disagreeing; the stub error shows one component ignoring elapsed days.

Does the day-count basis matter after the first period?

Yes, though it is most visible at commencement. When periods are regular full months the three conventions still differ slightly — 30/360 makes every month identical while an actual basis makes a 28-day February accrue less than a 31-day March — so a schedule that mixes bases drifts throughout, not only in period one. The first period is simply where a mid-month commencement makes the discrepancy largest and easiest to spot.