property.rent-proration
Rent for part of a month from the monthly rent, by the annual-daily or the calendar-month method.
1.0.0 (not the latest) · published 2026-10-03 by charlie · Anterra
Pinned by 21 tests, run in TypeScript, Python and Rust.
Not professional advice. This capability calculates property figures from published rules. It is a software component for developers, not legal or financial advice. Rules change and every rate here has an effective date. Check that the dates cover your case. Verify results against the official sources listed in its README, and have a conveyancer or tax adviser review how you use it, before anyone relies on the output. Provided “as is” under its licence, without warranty.
What it does
Rent for a part month: the first month of a tenancy that starts mid-month, the last month of one that ends mid-month, or a refund of rent paid in advance. Both dates are inclusive, so 1 to 15 January is 15 days.
There are two conventions in use, and they give different answers, so the method is an argument and never a guess:
For example
prorate_rent(£1,000.00, 2025-01-01, 2025-01-15, annual-daily)→ £493.15 £1,000 a month, 1-15 January, annual-daily: 15 × £1,000 × 12 ÷ 365 = £493.15prorate_rent(£1,000.00, 2025-01-01, 2025-01-15, calendar-month)→ £483.87 the same days by calendar month: 15/31 of £1,000 = £483.87prorate_rent(£1,000.00, 2025-03-10, 2025-03-10, annual-daily)→ £32.88 one day, annual-daily: £32.88
The function
The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.
pub fn prorate_rent(monthly_rent: &Money, from_date: &str, to_date: &str, method: &str) -> Money
| monthly_rent | Money | the rent for a full month; negative for a refund or credit |
| from_date | date | first day charged |
| to_date | date | last day charged, inclusive; at most 366 days after fromDate counted inclusively |
| method | RentProrationMethod | annual-daily: monthly × 12 ÷ 365 a day; calendar-month: monthly ÷ days in that month a day |
| returns | Money | the rent for the days, rounded half-up to the penny once |
The type it declares, generated into your project
// RentProrationMethod is a string in Rust, one of: "annual-daily", "calendar-month".
// Parameters take it as &str and results hold it as String.
Your code names it in one line, in the file that uses it
fune!(property.rent-proration@^1); // then call prorate_rent(…)
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::dates_add_days::{civil_from_days, days_in_month, epoch_day_from_iso}; ← from dates.add-days ^1.0.0 · built alongside by fune
use super::math_round_div::round_div; ← from math.round-div ^1.0.0 · built alongside by fune
use super::money_amount::{money, money_from_value, money_to_value, Money}; ← from money.amount ^1.0.0 · built alongside by fune
const MAX_MINOR: i64 = 1_000_000_000;
// lcm(28, 29, 30, 31): every month's daily rate is a whole number of these parts.
const MONTH_PARTS: i64 = 377_580;
/// Rent for the days from_date to to_date inclusive, from a monthly rent, as
/// one exact fraction rounded half-up once. The daily rate is never rounded
/// on its own, which is the usual source of a pound's error on a part-month
/// invoice.
///
/// # Panics
/// Panics on an out-of-range rent, a malformed date, a period that runs
/// backwards or is longer than 366 days, or an unknown method.
pub fn prorate_rent(monthly_rent: &Money, from_date: &str, to_date: &str, method: &str) -> Money {
if monthly_rent.minor.abs() > MAX_MINOR {
panic!(
"monthlyRent must be at most {} minor units either way, received {}",
MAX_MINOR, monthly_rent.minor
);
}
let start = epoch_day_from_iso(from_date);
let end = epoch_day_from_iso(to_date);
if end < start {
panic!("toDate {} is before fromDate {}", to_date, from_date);
}
let days = end - start + 1;
if days > 366 {
panic!("the period must be at most 366 days, received {}", days);
}
match method {
"annual-daily" => money(round_div(monthly_rent.minor * 12 * days, 365, "half-up"), &monthly_rent.currency),
"calendar-month" => {
// Each day is worth MONTH_PARTS / (days in its month) parts of a month.
let mut parts = 0i64;
let mut day = start;
while day <= end {
let date = civil_from_days(day);
let length = days_in_month(date.year, date.month);
let segment_end = end.min(day + (length - date.day));
parts += (segment_end - day + 1) * (MONTH_PARTS / length);
day = segment_end + 1;
}
money(round_div(monthly_rent.minor * parts, MONTH_PARTS, "half-up"), &monthly_rent.currency)
}
other => panic!("unknown method \"{}\": use annual-daily or calendar-month", other),
}
}
pub fn fune_vector(args: &[Value]) -> Value {
money_to_value(&prorate_rent(
&money_from_value(&args[0]),
args[1].as_str(),
args[2].as_str(),
args[3].as_str(),
))
}Install
fune build
With that line in your source, in a Rust project (language rust in fune.project), fune build resolves it and its 3 dependencies, pins them in fune.lock, downloads only the Rust package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. A crate’s build.rs runs it before every compile. Or pin a range in fune.project and build in one step:
fune add property.rent-proration
The manifest, vectors and README with only the Rust implementation. Install it without the registry with fune add ./property.rent-proration-1.0.0-rust.fune, or fetch it from a terminal with fune pull property.rent-proration@1.0.0:rust.
The whole function, every language, is one file too: property.rent-proration-1.0.0.fune, 16,876 bytes, sha256 5b6be9048d5ecd7b61cb256ee32daf2cbe2c93ff7cf3d62ddc83b0a2e3665b5c. It installs into a project of any language.
Customise it in your app
The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.
before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.
// fune: before property.rent-proration
after — your function gets the result and the arguments, and returns the final result.
// fune: after property.rent-proration
replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.
// fune: replace dates.add-days in property.rent-proration
// fune: replace math.round-div in property.rent-proration
// fune: replace money.amount in property.rent-proration
step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show property.rent-proration --steps.
// fune: step property.rent-proration after <n|label>
Tests
A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.
| Case | Arguments | Expected | |
|---|---|---|---|
| £1,000 a month, 1-15 January, annual-daily: 15 × £1,000 × 12 ÷ 365 = £493.15 | £1,000.00, 2025-01-01, 2025-01-15, annual-daily | → | £493.15 |
| the same days by calendar month: 15/31 of £1,000 = £483.87 | £1,000.00, 2025-01-01, 2025-01-15, calendar-month | → | £483.87 |
| one day, annual-daily: £32.88 | £1,000.00, 2025-03-10, 2025-03-10, annual-daily | → | £32.88 |
| 14 days of a leap February by calendar month: 14/29 | £1,000.00, 2024-02-01, 2024-02-14, calendar-month | → | £482.76 |
| 14 days of a common February by calendar month: exactly half | £1,000.00, 2023-02-01, 2023-02-14, calendar-month | → | £500.00 |
| a whole calendar month is exactly the monthly rent | £1,000.00, 2023-02-01, 2023-02-28, calendar-month | → | £1,000.00 |
| a whole February annual-daily is 28/365 of a year, not a month | £1,000.00, 2023-02-01, 2023-02-28, annual-daily | → | £920.55 |
| 20 January to 10 February by calendar month: 12/31 + 10/28, rounded once | £1,000.00, 2023-01-20, 2023-02-10, calendar-month | → | £744.24 |
| 20 January to 10 February annual-daily: 22 days | £1,000.00, 2023-01-20, 2023-02-10, annual-daily | → | £723.29 |
| across a year end by calendar month: 1/31 + 1/31 | £1,000.00, 2024-12-31, 2025-01-01, calendar-month | → | £64.52 |
Show the other 11 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| an exact half penny rounds up: £1,000.01 for 15 of April's 30 days | £1,000.01, 2025-04-01, 2025-04-15, calendar-month | → | £500.01 |
| a credit rounds symmetrically | -£1,000.00, 2025-01-01, 2025-01-15, calendar-month | → | -£483.87 |
| a whole leap year by calendar month is twelve months' rent | £1,000.00, 2024-01-01, 2024-12-31, calendar-month | → | £12,000.00 |
| a whole leap year annual-daily is 366/365 of a year | £1,000.00, 2024-01-01, 2024-12-31, annual-daily | → | £12,032.88 |
| zero rent | £0.00, 2025-01-01, 2025-01-15, annual-daily | → | £0.00 |
| the currency is kept | €1,000.00, 2025-01-01, 2025-01-15, calendar-month | → | €483.87 |
| an end before the start is refused | £1,000.00, 2025-01-15, 2025-01-14, annual-daily | → | error: toDate 2025-01-14 is before fromDate 2025-01-15 |
| more than 366 days is refused | £1,000.00, 2024-01-01, 2025-01-01, calendar-month | → | error: the period must be at most 366 days |
| an unknown method is refused | £1,000.00, 2025-01-01, 2025-01-15, 30-360 | → | error: unknown method |
| a date that does not exist is refused | £1,000.00, 2023-02-01, 2023-02-30, calendar-month | → | error: is not a real calendar date |
| a rent beyond the exact range is refused | £10,000,000.01, 2025-01-01, 2025-01-15, annual-daily | → | error: monthlyRent must be at most |
More from the author
- **annual-daily**: a daily rate of monthly rent × 12 ÷ 365, times the days. This is what most letting agents and tenancy agreements in England use, because every day costs the same whatever the month. £1,000 a month for 15 days is £493.15. - **calendar-month**: a daily rate of monthly rent ÷ the number of days in the month the day falls in. A full calendar month always comes to exactly one month's rent. £1,000 a month for 1 to 15 January is 15/31 of £1,000, £483.87. A period that crosses a month end is charged at each month's own daily rate: 20 January to 10 February is 12/31 + 10/28 of a month.
## Rounding happens once
Each method is computed as an exact fraction and rounded half-up (away from zero) to the penny only at the end. The daily rate is never rounded first: £1,000 × 12 ÷ 365 is £32.8767…, and rounding that to £32.88 before multiplying by 15 days would give £493.20 instead of £493.15.
## Edge cases and limits
- annual-daily does not know about leap years: 29 February is one more day at the same rate, and a full 28-day February is 28/365 of a year, not one month's rent. That is the convention, not a bug; use calendar-month if a whole month must be exactly the monthly rent. - `toDate` before `fromDate` is an error, as is a period longer than 366 days: this is for part months, and a longer span is whole months plus a part month. - A negative monthly rent gives a negative answer, rounded symmetrically. - The monthly rent may be at most £10,000,000 (1,000,000,000 minor units), which keeps every intermediate exact in all three languages.
Files
| Path | Bytes |
|---|---|
| README.md | 1,973 |
| impl/python.py | 1,971 |
| impl/rust.rs | 2,483 |
| impl/typescript.ts | 2,069 |
| vectors.json | 5,186 |