health.age-in-months Unreviewed
Age in completed months and days on a given date, for paediatric charts and doses; month ends handled like birthdays.
1.0.1 · published 2026-10-03 by charlie · Anterra
Pinned by 19 tests, run in TypeScript, Python and Rust.
Unreviewed. This capability’s implementations agree in every language and pass its published test vectors, which were worked out from the official sources cited. But no qualified clinician has yet checked those vectors, or confirmed that the capability covers the cases it claims. Treat it as a draft. Do not use it for real people, money or decisions without your own expert review. Once a qualified reviewer signs off, this notice is replaced with their name, qualification and the date. Each new version needs fresh sign-off.
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
ageInMonths(2026-03-10, 2026-03-10)→ months 0, days 0, total days 0 on the day of birth everything is zeroageInMonths(2026-03-10, 2026-03-11)→ months 0, days 1, total days 1 one day oldageInMonths(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.
export function ageInMonths(birthDate: string, onDate: string): AgeInMonths
| birthDate | date | date of birth, ISO |
| onDate | date | the date to measure on; never today implicitly |
| returns | AgeInMonths |
The type it declares, generated into your project
/** A child's age as completed months plus days, and the days since birth. */
export interface AgeInMonths {
/** completed calendar months since birth */
readonly months: number;
/** days since the last completed month, 0 or more */
readonly days: number;
/** days since birth; 0 on the day of birth */
readonly totalDays: number;
}
Your code names it in one line, in the file that uses it
import { ageInMonths } from "#fune/health.age-in-months@^1";
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { addDays, daysBetween, parseIsoDate } from "./dates_add_days.ts"; ← from dates.add-days ^1.0.0 · built alongside by fune
import { addMonths } from "./dates_add_months.ts"; ← from dates.add-months ^1.0.0 · built alongside by fune
import { type AgeInMonths } from "./health_age_in_months_types.ts";
/**
* The day the given number of months is completed. addMonths 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.
*/
function monthAnniversary(birthDate: string, birthDay: number, months: number): string {
const shifted = addMonths(birthDate, months);
return parseIsoDate(shifted).day < birthDay ? addDays(shifted, 1) : shifted;
}
/** Completed months and remaining days from birthDate to onDate. */
export function ageInMonths(birthDate: string, onDate: string): AgeInMonths {
const birth = parseIsoDate(birthDate);
const on = parseIsoDate(onDate);
if (onDate < birthDate) {
throw new RangeError(`onDate ${onDate} is before birthDate ${birthDate}`);
}
let months = (on.year - birth.year) * 12 + (on.month - birth.month);
let anniversary = monthAnniversary(birthDate, birth.day, months);
if (anniversary > onDate) {
months -= 1;
anniversary = monthAnniversary(birthDate, birth.day, months);
}
return { months, days: daysBetween(anniversary, onDate), totalDays: daysBetween(birthDate, onDate) };
}Install
fune build
With that line in your source, in a TypeScript project (language typescript in fune.project), fune build resolves it and its 2 dependencies, pins them in fune.lock, downloads only the TypeScript 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
The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./health.age-in-months-1.0.1-typescript.fune, or fetch it from a terminal with fune pull health.age-in-months@1.0.1:typescript.
The whole function, every language, is one file too: health.age-in-months-1.0.1.fune, 13,308 bytes, sha256 6ba2f856fa499229596115645b913970f63d353863d146ba2c24df0dcfff919e. 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.
| Case | Arguments | Expected | |
|---|---|---|---|
| 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
| Case | Arguments | Expected | |
|---|---|---|---|
| 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.
## Before you rely on this
**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 above, 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.
**Unreviewed.** This capability's implementations agree in every language and pass its published test vectors, which were worked out from the official sources cited. But no qualified clinician has yet checked those vectors, or confirmed that the capability covers the cases it claims. Treat it as a draft. Do not use it for real people, money or decisions without your own expert review. Once a qualified reviewer signs off, this notice is replaced with their name, qualification and the date. Each new version needs fresh sign-off.
1.0.1 marks it unreviewed. The code and the tests are unchanged.
Files
| Path | Bytes |
|---|---|
| README.md | 2,905 |
| impl/python.py | 1,373 |
| impl/rust.rs | 2,062 |
| impl/typescript.ts | 1,389 |
| vectors.json | 2,914 |