Functional Weave
Code in Python

health.age-in-months

Age in completed months and days on a given date, for paediatric charts and doses; month ends handled like birthdays.

1.0.0 (not the latest) · published 2026-10-03 by charlie · Anterra

Pinned by 19 tests, run in TypeScript, Python and Rust.

Not professional advice. This capability calculates health figures from published rules. It is a software component for developers, not medical 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 clinician review how you use it, before anyone relies on the output. Provided “as is” under its licence, without warranty.

Not a medical device. It is not intended to diagnose, treat or support clinical decisions about any individual. Anyone building it into clinical software is responsible for that software’s regulatory status, and must validate it under their own clinical governance.

What it does

A child's age as completed months plus days on a given date: born 10 March 2026, on 25 May 2026 the child is 2 months 15 days. Paediatric growth charts, immunisation schedules and many dosing tables are read in months, and the date to measure on is an argument, never the clock. `totalDays` is the age in days as well, for neonates.

**When a month is completed.** A month is completed on the same day of the month as the birth. When that day does not exist in the month (born 31 January; born 29 February in a common year), the month is completed on the first day of the following month, not on the last day of the short month. Born 31 January 2026, the child is 0 months 28 days on 28 February and 1 month 0 days on 1 March. This is the same rule `dates.age` applies to a 29 February birthday (the anniversary is 1 March), so a child born 29 February 2024 is 11 months 30 days on 28 February 2025 and 12 months, and one year old, on 1 March 2025. A naive "add a month and clamp" says the child born on 31 January is a month old on 28 February, three days early.

For example

  • age_in_months(2026-03-10, 2026-03-10) → months 0, days 0, total days 0 on the day of birth everything is zero
  • age_in_months(2026-03-10, 2026-03-11) → months 0, days 1, total days 1 one day old
  • age_in_months(2026-03-10, 2026-04-09) → months 0, days 30, total days 30 the day before the first monthly anniversary

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.

def age_in_months(birth_date: str, on_date: str) -> AgeInMonths
birth_datedatedate of birth, ISO
on_datedatethe date to measure on; never today implicitly
returnsAgeInMonths

The type it declares, generated into your project

@dataclass(frozen=True)
class AgeInMonths:
    """A child's age as completed months plus days, and the days since birth."""

    #: completed calendar months since birth
    months: int
    #: days since the last completed month, 0 or more
    days: int
    #: days since birth; 0 on the day of birth
    total_days: int

Your code names it in one line, in the file that uses it

from fune.health.age_in_months import age_in_months  # health.age-in-months@^1
impl/python.py · 26 lines · open · 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 add_days, days_between, parse_iso_date  ← from dates.add-days ^1.0.0 · built alongside by fune
from .dates_add_months import add_months  ← from dates.add-months ^1.0.0 · built alongside by fune
from .health_age_in_months_types import AgeInMonths


def _month_anniversary(birth_date: str, birth_day: int, months: int) -> str:
    """The day the given number of months is completed. add_months clamps 31
    January + 1 month to 28 February; a clamped day has not yet reached the
    birth day, so the month completes the day after it (1 March), the same rule
    dates.age uses for a 29 February birthday."""
    shifted = add_months(birth_date, months)
    return add_days(shifted, 1) if parse_iso_date(shifted).day < birth_day else shifted


def age_in_months(birth_date: str, on_date: str) -> AgeInMonths:
    """Completed months and remaining days from ``birth_date`` to ``on_date``."""
    birth = parse_iso_date(birth_date)
    on = parse_iso_date(on_date)
    if on_date < birth_date:
        raise ValueError("onDate %s is before birthDate %s" % (on_date, birth_date))
    months = (on.year - birth.year) * 12 + (on.month - birth.month)
    anniversary = _month_anniversary(birth_date, birth.day, months)
    if anniversary > on_date:
        months -= 1
        anniversary = _month_anniversary(birth_date, birth.day, months)
    return AgeInMonths(months=months, days=days_between(anniversary, on_date), total_days=days_between(birth_date, on_date))

Install

fune build

With that line in your source, in a Python project (language python in fune.project), fune build resolves it and its 2 dependencies, pins them in fune.lock, downloads only the Python 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. Or pin a range in fune.project and build in one step:

fune add health.age-in-months
Download for Python health.age-in-months-1.0.0-python.fune · 8,341 bytes sha256 5feb2abe22af2c24e1f1684796f296cc9110a9355eede0ee5e66245fded40c5e

The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./health.age-in-months-1.0.0-python.fune, or fetch it from a terminal with fune pull health.age-in-months@1.0.0:python.

The whole function, every language, is one file too: health.age-in-months-1.0.0.fune, 11,926 bytes, sha256 63bf286b1e35ae77f10d05f89522d6523c430157b518aa3e793912bf46e3c03f. 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 health.age-in-months

after — your function gets the result and the arguments, and returns the final result.

# fune: after health.age-in-months

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 health.age-in-months
# fune: replace dates.add-months in health.age-in-months

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 health.age-in-months --steps.

# fune: step health.age-in-months 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.

CaseArgumentsExpected
on the day of birth everything is zero 2026-03-10, 2026-03-10 → months 0, days 0, total days 0
one day old 2026-03-10, 2026-03-11 → months 0, days 1, total days 1
the day before the first monthly anniversary 2026-03-10, 2026-04-09 → months 0, days 30, total days 30
on the first monthly anniversary 2026-03-10, 2026-04-10 → months 1, days 0, total days 31
2 months 15 days 2026-03-10, 2026-05-25 → months 2, days 15, total days 76
born 31 January is not a month old on 28 February: a naive clamp says it is 2026-01-31, 2026-02-28 → months 0, days 28, total days 28
born 31 January completes the first month on 1 March 2026-01-31, 2026-03-01 → months 1, days 0, total days 29
born 31 January, 30 March is 1 month 29 days 2026-01-31, 2026-03-30 → months 1, days 29, total days 58
born 31 January, 31 March is 2 months: the 31st does not decay after February 2026-01-31, 2026-03-31 → months 2, days 0, total days 59
born 31 January 2024, 29 February 2024 is still short of the 31st 2024-01-31, 2024-02-29 → months 0, days 29, total days 29
Show the other 9 tests
CaseArgumentsExpected
born 31 January 2024, 1 March 2024 completes the month 2024-01-31, 2024-03-01 → months 1, days 0, total days 30
born 29 February 2024 is 11 months 30 days on 28 February 2025 2024-02-29, 2025-02-28 → months 11, days 30, total days 365
born 29 February 2024 is 12 months on 1 March 2025, as dates.age turns 1 2024-02-29, 2025-03-01 → months 12, days 0, total days 366
born 30 April, 30 May is one month 2026-04-30, 2026-05-30 → months 1, days 0, total days 30
across the year end 2025-11-20, 2026-01-05 → months 1, days 16, total days 46
a two-year-old counted in months 2024-06-15, 2026-09-23 → months 27, days 8, total days 830
a measuring date before the birth is an error 2026-03-10, 2026-03-09 → error: is before birthDate
an impossible birth date is an error 2026-02-30, 2026-04-01 → error: is not a real calendar date
a UK-style date is an error 10/03/2026, 2026-04-01 → error: is not an ISO date

More from the author

Months are always counted from the date of birth, not stepped a month at a time, so a 31st-of-the-month birthday does not decay to the 28th after February: born 31 January, 2 months are completed on 31 March.

A measuring date before the birth date is an error, not a negative age. Dates are ISO `YYYY-MM-DD`; an impossible date (2026-02-30) or a UK-style `15/05/2026` is an error.

Built on `dates.add-months` and `dates.add-days`; no clinical thresholds are applied here.

Files

PathBytes
README.md1,563
impl/python.py1,373
impl/rust.rs2,062
impl/typescript.ts1,389
vectors.json2,914