Functional Weave
Code in Rust

dates.month-boundaries@1.0.0

impl/python.py

1,382 bytes · the Python implementation · view raw

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 days_in_month, format_iso_date, parse_iso_date, CivilDate  ← from dates.add-days ^1.0.0 · built alongside by fune
from .dates_month_boundaries_types import MonthBoundaries


def month_boundaries(iso: str) -> MonthBoundaries:
    """The first and last day of the month containing a date, and its length.

    All three are returned together because a caller needs them together - a
    billing period, a statement range, a pro-rata fraction - and because
    deriving ``end`` from ``start`` in caller code is where "31 January plus one
    month" bugs come from.

    ``end`` is the last day of the month, inclusive, not the 1st of the next
    month. Inclusive is what a statement prints and what a human checks against.
    """
    date = parse_iso_date(iso)
    # February is the only month whose length is not a constant, and it follows
    # the full Gregorian rule: 2024 had 29 days, 1900 had 28 (a century year),
    # 2000 had 29 (divisible by 400).
    days = days_in_month(date.year, date.month)
    return MonthBoundaries(
        start=format_iso_date(CivilDate(date.year, date.month, 1)),
        end=format_iso_date(CivilDate(date.year, date.month, days)),
        days=days,
    )


def is_month_end(iso: str) -> bool:
    """True when the date is the last day of its month - a common billing trigger."""
    date = parse_iso_date(iso)
    return date.day == days_in_month(date.year, date.month)