Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
from .dates_add_days import civil_from_days, days_in_month, epoch_day_from_iso ← from dates.add-days ^1.0.0 · built alongside by fune
from .math_round_div import round_div ← from math.round-div ^1.0.0 · built alongside by fune
from .money_amount import Money, money ← from money.amount ^1.0.0 · built alongside by fune
from .property_rent_proration_types import RentProrationMethod
_MAX_MINOR = 1_000_000_000
# lcm(28, 29, 30, 31): every month's daily rate is a whole number of these parts.
_MONTH_PARTS = 377_580
def prorate_rent(monthly_rent: Money, from_date: str, to_date: str, method: RentProrationMethod) -> Money:
"""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."""
if abs(monthly_rent.minor) > _MAX_MINOR:
raise ValueError(
"monthlyRent must be at most %d minor units either way, received %r" % (_MAX_MINOR, monthly_rent.minor)
)
start = epoch_day_from_iso(from_date)
end = epoch_day_from_iso(to_date)
if end < start:
raise ValueError("toDate %s is before fromDate %s" % (to_date, from_date))
days = end - start + 1
if days > 366:
raise ValueError("the period must be at most 366 days, received %d" % (days,))
if method == "annual-daily":
return money(round_div(monthly_rent.minor * 12 * days, 365, "half-up"), monthly_rent.currency)
if method != "calendar-month":
raise ValueError('unknown method "%s": use annual-daily or calendar-month' % (method,))
# Each day is worth _MONTH_PARTS / (days in its month) parts of a month.
parts = 0
day = start
while day <= end:
date = civil_from_days(day)
length = days_in_month(date.year, date.month)
segment_end = min(end, day + (length - date.day))
parts += (segment_end - day + 1) * (_MONTH_PARTS // length)
day = segment_end + 1
return money(round_div(monthly_rent.minor * parts, _MONTH_PARTS, "half-up"), monthly_rent.currency)