health.gestational-age
Gestational age (weeks+days) and estimated due date from the last menstrual period by Naegele's rule, cycle-adjusted.
1.0.0 (not the latest) · published 2026-10-03 by charlie · Anterra
Pinned by 20 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 midwife or obstetrician 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
Status: needs review and sign-off by a qualified clinician before it is published. Not a medical device; for decision support only; always follow local clinical guidelines.
Gestational age on a date, as completed weeks plus days (12+3), and the estimated due date (EDD), from the first day of the last menstrual period (LMP). This is the LMP estimate only: NICE recommends dating a pregnancy by ultrasound between 11+2 and 14+1 weeks, and once a dating scan has been done its dates supersede these.
For example
gestationalAge(2026-03-15, 2026-06-10, 28)→ weeks 12, days 3, total days 87, due date 2026-12-20 NHS due date calculator: LMP 15 March 2026, 28-day cycle, due 20 December 2026; 12+3 on 10 JunegestationalAge(2026-03-15, 2026-06-10, 35)→ weeks 11, days 3, total days 80, due date 2026-12-27 NHS calculator: a 35-day cycle moves the due date a week later, to 27 December 2026gestationalAge(2026-03-15, 2026-06-10, 21)→ weeks 13, days 3, total days 94, due date 2026-12-13 NHS calculator: a 21-day cycle moves it a week earlier, to 13 December 2026
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 gestationalAge(lmpDate: string, onDate: string, cycleLengthDays: number): GestationalAge
| lmpDate | date | first day of the last menstrual period, ISO |
| onDate | date | the date to measure on; never today implicitly |
| cycleLengthDays | int | usual cycle length, 20 to 44; 28 for the unadjusted rule |
| returns | GestationalAge |
The type it declares, generated into your project
/** Gestational age as weeks+days, and the estimated due date. */
export interface GestationalAge {
/** completed weeks */
readonly weeks: number;
/** days beyond the completed weeks, 0 to 6 */
readonly days: number;
/** gestational age in days */
readonly totalDays: number;
/** estimated due date, 40+0 weeks */
readonly dueDate: string;
}
Your code names it in one line, in the file that uses it
import { gestationalAge } from "#fune/health.gestational-age@^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 { type GestationalAge } from "./health_gestational_age_types.ts";
/** Naegele's rule: 280 days (40+0 weeks) from the LMP for a 28-day cycle. */
const TERM_DAYS = 280;
/** 44+0 weeks: beyond this the dates are almost certainly wrong. */
const MAX_DAYS = 308;
/**
* Gestational age and estimated due date from the last menstrual period. A
* cycle longer or shorter than 28 days moves ovulation, so both are shifted by
* the difference, as the NHS due date calculator does.
*/
export function gestationalAge(lmpDate: string, onDate: string, cycleLengthDays: number): GestationalAge {
if (!Number.isInteger(cycleLengthDays) || cycleLengthDays < 20 || cycleLengthDays > 44) {
throw new RangeError(`cycleLengthDays must be a whole number from 20 to 44, received ${cycleLengthDays}`);
}
parseIsoDate(lmpDate);
parseIsoDate(onDate);
const shift = cycleLengthDays - 28;
const totalDays = daysBetween(lmpDate, onDate) - shift;
if (totalDays < 0) {
throw new RangeError(`onDate ${onDate} is before the adjusted start of pregnancy`);
}
if (totalDays > MAX_DAYS) {
throw new RangeError(`gestational age of ${totalDays} days is more than 44+0 weeks: check the dates`);
}
return {
weeks: Math.floor(totalDays / 7),
days: totalDays % 7,
totalDays,
dueDate: addDays(lmpDate, TERM_DAYS + shift),
};
}Install
fune build
With that line in your source, in a TypeScript project (language typescript in fune.project), fune build resolves it and its 1 dependency, 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.gestational-age
The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./health.gestational-age-1.0.0-typescript.fune, or fetch it from a terminal with fune pull health.gestational-age@1.0.0:typescript.
The whole function, every language, is one file too: health.gestational-age-1.0.0.fune, 14,669 bytes, sha256 eece582e3c18dfa4333d0a6782552ad603158f9d4cd53dcbddbfcbc2caec00d3. 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.gestational-age
after — your function gets the result and the arguments, and returns the final result.
// fune: after health.gestational-age
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.gestational-age
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.gestational-age --steps.
// fune: step health.gestational-age 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 | |
|---|---|---|---|
| NHS due date calculator: LMP 15 March 2026, 28-day cycle, due 20 December 2026; 12+3 on 10 June | 2026-03-15, 2026-06-10, 28 | → | weeks 12, days 3, total days 87, due date 2026-12-20 |
| NHS calculator: a 35-day cycle moves the due date a week later, to 27 December 2026 | 2026-03-15, 2026-06-10, 35 | → | weeks 11, days 3, total days 80, due date 2026-12-27 |
| NHS calculator: a 21-day cycle moves it a week earlier, to 13 December 2026 | 2026-03-15, 2026-06-10, 21 | → | weeks 13, days 3, total days 94, due date 2026-12-13 |
| NHS calculator: the shortest cycle it accepts, 20 days, due 12 December 2026 | 2026-03-15, 2026-06-10, 20 | → | weeks 13, days 4, total days 95, due date 2026-12-12 |
| NHS calculator: a 22-day cycle, due 14 December 2026 | 2026-03-15, 2026-06-10, 22 | → | weeks 13, days 2, total days 93, due date 2026-12-14 |
| NHS calculator: the longest cycle it accepts, 44 days, due 5 January 2027 | 2026-03-15, 2026-06-10, 44 | → | weeks 10, days 1, total days 71, due date 2027-01-05 |
| NHS calculator: LMP 31 May 2026, due 7 March 2027 | 2026-05-31, 2026-09-23, 28 | → | weeks 16, days 3, total days 115, due date 2027-03-07 |
| NHS calculator: LMP 1 January 2026, due 8 October 2026 | 2026-01-01, 2026-09-23, 28 | → | weeks 37, days 6, total days 265, due date 2026-10-08 |
| NHS calculator: LMP 10 June 2026, due 17 March 2027, on the due date itself 40+0 | 2026-06-10, 2027-03-17, 28 | → | weeks 40, days 0, total days 280, due date 2027-03-17 |
| NHS calculator: LMP 1 September 2026 with a 30-day cycle, due 10 June 2027 | 2026-09-01, 2026-12-01, 30 | → | weeks 12, days 5, total days 89, due date 2027-06-10 |
Show the other 10 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| on the LMP itself with a 28-day cycle the gestational age is 0+0 | 2026-03-15, 2026-03-15, 28 | → | weeks 0, days 0, total days 0, due date 2026-12-20 |
| a pregnancy spanning 29 February 2028 counts the leap day | 2027-06-01, 2028-03-01, 28 | → | weeks 39, days 1, total days 274, due date 2028-03-07 |
| 44+0 weeks is still accepted | 2026-01-01, 2026-11-05, 28 | → | weeks 44, days 0, total days 308, due date 2026-10-08 |
| 44+1 weeks is an error: check the dates | 2026-01-01, 2026-11-06, 28 | → | error: more than 44+0 weeks |
| a 35-day cycle puts the adjusted start 7 days after the LMP, so 3 days after the LMP is an error | 2026-03-15, 2026-03-18, 35 | → | error: is before the adjusted start of pregnancy |
| onDate before the LMP is an error | 2026-03-15, 2026-03-14, 28 | → | error: is before the adjusted start of pregnancy |
| a 19-day cycle is outside the NHS calculator's 20 to 44 days | 2026-03-15, 2026-06-10, 19 | → | error: cycleLengthDays must be a whole number from 20 to 44 |
| a 45-day cycle is outside 20 to 44 days | 2026-03-15, 2026-06-10, 45 | → | error: cycleLengthDays must be a whole number from 20 to 44 |
| a fractional cycle length is an error | 2026-03-15, 2026-06-10, 28.5 | → | error: cycleLengthDays must be a whole number from 20 to 44 |
| an impossible LMP date is an error | 2026-02-30, 2026-06-10, 28 | → | error: is not a real calendar date |
More from the author
**The rule.** Naegele's rule puts the due date 280 days (40+0 weeks) after the LMP, which assumes a 28-day cycle with ovulation on day 14. A longer or shorter cycle moves ovulation, so both the due date and the gestational age are shifted by the difference from 28 days:
dueDate = LMP + 280 + (cycleLengthDays - 28) totalDays = (onDate - LMP) - (cycleLengthDays - 28) weeks = totalDays div 7, days = totalDays mod 7
With a 35-day cycle the due date is a week later and the pregnancy is a week less advanced on any date. Pass 28 for the unadjusted rule. This is exactly what the NHS due date calculator does, and its published answers are among the vectors (LMP 15 March 2026: due 20 December 2026 on a 28-day cycle, 27 December on 35 days, 12 December on 20 days, 5 January 2027 on 44 days).
**Limits, and why they are errors.**
- `cycleLengthDays` must be a whole number from 20 to 44, the range the NHS calculator accepts. - A measuring date before the adjusted start of pregnancy (the LMP plus the cycle shift) is an error, not a negative age: with a 35-day cycle, three days after the LMP is not yet 0+0. - A gestational age above 44+0 weeks (308 days) is an error asking for the dates to be checked. Pregnancy normally lasts 37 to 42 weeks from the LMP; 44 weeks is a guard rail against a wrong year or a transposed date, not a clinical threshold, and a clinician should confirm it.
Dates are ISO `YYYY-MM-DD`; the measuring date is always an argument, never today's date. Leap days are counted like any other day.
## Sources
- NHS, "Due date calculator" (nhs.uk): takes the first day of the last period and a cycle length that "must be between 20 and 44 days"; pregnancy "normally lasts from 37 weeks to 42 weeks from the first day of your last period". Its outputs for the dates above were checked on 23 September 2026. https://www.nhs.uk/pregnancy/finding-out/due-date-calculator/ - NICE guideline NG201, "Antenatal care" (2021), recommendation 1.2.14: offer an ultrasound scan between 11+2 and 14+1 weeks to determine gestational age. https://www.nice.org.uk/guidance/ng201/chapter/Recommendations
Files
| Path | Bytes |
|---|---|
| README.md | 2,684 |
| impl/python.py | 1,392 |
| impl/rust.rs | 2,395 |
| impl/typescript.ts | 1,422 |
| vectors.json | 3,798 |