[ { "name": "\u00a310,000 at 3.65% for 30 days on ACT/365 is exactly \u00a330", "args": [ [ { "date": "2026-01-01", "balance": { "minor": 1000000, "currency": "GBP" } } ], "2026-01-01", "2026-01-31", 365, "act-365f", "half-up" ], "expect": { "interest": { "minor": 3000, "currency": "GBP" }, "exact": { "numerator": 3000, "denominator": 1 }, "days": 30 } }, { "name": "a deposit mid-month: the new balance earns from its date", "args": [ [ { "date": "2026-03-01", "balance": { "minor": 100000, "currency": "GBP" } }, { "date": "2026-03-16", "balance": { "minor": 600000, "currency": "GBP" } } ], "2026-03-01", "2026-04-01", 450, "act-365f", "half-up" ], "expect": { "interest": { "minor": 1368, "currency": "GBP" }, "exact": { "numerator": 99900, "denominator": 73 }, "days": 31 } }, { "name": "a balance before the period starts is carried in", "args": [ [ { "date": "2025-12-10", "balance": { "minor": 250000, "currency": "GBP" } }, { "date": "2026-01-20", "balance": { "minor": 0, "currency": "GBP" } } ], "2026-01-01", "2026-02-01", 500, "act-365f", "half-up" ], "expect": { "interest": { "minor": 651, "currency": "GBP" }, "exact": { "numerator": 47500, "denominator": 73 }, "days": 31 } }, { "name": "changes after the period end are ignored", "args": [ [ { "date": "2026-01-01", "balance": { "minor": 100000, "currency": "GBP" } }, { "date": "2026-02-10", "balance": { "minor": 900000, "currency": "GBP" } } ], "2026-01-01", "2026-02-01", 500, "act-365f", "half-up" ], "expect": { "interest": { "minor": 425, "currency": "GBP" }, "exact": { "numerator": 31000, "denominator": 73 }, "days": 31 } }, { "name": "ACT/360 charges more than ACT/365 for the same days", "args": [ [ { "date": "2026-01-01", "balance": { "minor": 1000000, "currency": "GBP" } } ], "2026-01-01", "2026-01-31", 365, "act-360", "half-up" ], "expect": { "interest": { "minor": 3042, "currency": "GBP" }, "exact": { "numerator": 9125, "denominator": 3 }, "days": 30 } }, { "name": "30/360: February counts as 30 days", "args": [ [ { "date": "2026-02-01", "balance": { "minor": 1200000, "currency": "GBP" } } ], "2026-02-01", "2026-03-01", 600, "30-360", "half-up" ], "expect": { "interest": { "minor": 6000, "currency": "GBP" }, "exact": { "numerator": 6000, "denominator": 1 }, "days": 28 } }, { "name": "ACT/ACT ISDA across a leap-year boundary", "args": [ [ { "date": "2027-12-01", "balance": { "minor": 5000000, "currency": "GBP" } } ], "2027-12-01", "2028-02-01", 400, "act-act-isda", "half-up" ], "expect": { "interest": { "minor": 33926, "currency": "GBP" }, "exact": { "numerator": 453220000, "denominator": 13359 }, "days": 62 } }, { "name": "an overdrawn balance accrues negative interest", "args": [ [ { "date": "2026-05-01", "balance": { "minor": -50000, "currency": "GBP" } } ], "2026-05-01", "2026-06-01", 1990, "act-365f", "half-up" ], "expect": { "interest": { "minor": -845, "currency": "GBP" }, "exact": { "numerator": -61690, "denominator": 73 }, "days": 31 } }, { "name": "the same debt with \"up\" rounds away from zero", "args": [ [ { "date": "2026-05-01", "balance": { "minor": -50000, "currency": "GBP" } } ], "2026-05-01", "2026-06-01", 1990, "act-365f", "up" ], "expect": { "interest": { "minor": -846, "currency": "GBP" }, "exact": { "numerator": -61690, "denominator": 73 }, "days": 31 } }, { "name": "rounding once: many small changes, one rounding", "args": [ [ { "date": "2026-01-01", "balance": { "minor": 333, "currency": "GBP" } }, { "date": "2026-01-02", "balance": { "minor": 334, "currency": "GBP" } }, { "date": "2026-01-03", "balance": { "minor": 333, "currency": "GBP" } }, { "date": "2026-01-04", "balance": { "minor": 0, "currency": "GBP" } } ], "2026-01-01", "2026-01-05", 2500, "act-365f", "half-up" ], "expect": { "interest": { "minor": 1, "currency": "GBP" }, "exact": { "numerator": 50, "denominator": 73 }, "days": 4 } }, { "name": "a zero-length period accrues nothing", "args": [ [ { "date": "2026-01-01", "balance": { "minor": 100000, "currency": "GBP" } } ], "2026-01-10", "2026-01-10", 500, "act-365f", "half-up" ], "expect": { "interest": { "minor": 0, "currency": "GBP" }, "exact": { "numerator": 0, "denominator": 1 }, "days": 0 } }, { "name": "a negative rate charges a positive balance", "args": [ [ { "date": "2026-01-01", "balance": { "minor": 10000000, "currency": "GBP" } } ], "2026-01-01", "2026-04-01", -50, "act-365f", "half-even" ], "expect": { "interest": { "minor": -12329, "currency": "GBP" }, "exact": { "numerator": -900000, "denominator": 73 }, "days": 90 } }, { "name": "a whole leap year on ACT/365F is 366/365 of a year's interest", "args": [ [ { "date": "2028-01-01", "balance": { "minor": 100000, "currency": "GBP" } } ], "2028-01-01", "2029-01-01", 1000, "act-365f", "half-up" ], "expect": { "interest": { "minor": 10027, "currency": "GBP" }, "exact": { "numerator": 732000, "denominator": 73 }, "days": 366 } }, { "name": "an empty balance list is refused", "args": [ [], "2026-01-01", "2026-02-01", 500, "act-365f", "half-up" ], "expectError": "balances must not be empty" }, { "name": "a first balance after the start is refused", "args": [ [ { "date": "2026-01-05", "balance": { "minor": 100, "currency": "GBP" } } ], "2026-01-01", "2026-02-01", 500, "act-365f", "half-up" ], "expectError": "after fromIso" }, { "name": "balances out of order are refused", "args": [ [ { "date": "2026-01-05", "balance": { "minor": 100, "currency": "GBP" } }, { "date": "2026-01-01", "balance": { "minor": 200, "currency": "GBP" } } ], "2026-01-01", "2026-02-01", 500, "act-365f", "half-up" ], "expectError": "strictly ascending date order" }, { "name": "an end before the start is refused", "args": [ [ { "date": "2026-01-01", "balance": { "minor": 100, "currency": "GBP" } } ], "2026-02-01", "2026-01-01", 500, "act-365f", "half-up" ], "expectError": "must not be after toIso" }, { "name": "mixed currencies are refused", "args": [ [ { "date": "2026-01-01", "balance": { "minor": 100, "currency": "GBP" } }, { "date": "2026-01-10", "balance": { "minor": 100, "currency": "EUR" } } ], "2026-01-01", "2026-02-01", 500, "act-365f", "half-up" ], "expectError": "currency mismatch" }, { "name": "an impossible date is refused", "args": [ [ { "date": "2026-02-30", "balance": { "minor": 100, "currency": "GBP" } } ], "2026-01-01", "2026-02-01", 500, "act-365f", "half-up" ], "expectError": "is not a real calendar date" }, { "name": "an unknown convention is refused", "args": [ [ { "date": "2026-01-01", "balance": { "minor": 100, "currency": "GBP" } } ], "2026-01-01", "2026-02-01", 500, "act-365", "half-up" ], "expectError": "unknown day count convention" }, { "name": "a rate out of range is refused", "args": [ [ { "date": "2026-01-01", "balance": { "minor": 100, "currency": "GBP" } } ], "2026-01-01", "2026-02-01", 100001, "act-365f", "half-up" ], "expectError": "annualRateBasisPoints must be between -100000 and 100000" } ]