import type { AgeResult, I18nStrings } from './types.js'; /** Number of milliseconds in a day */ const MS_PER_DAY = 86_400_000; /** Weeks of a full-term pregnancy */ const FULL_TERM_WEEKS = 40; /** Prematurity correction cutoff: 2 years in days (730.5, rounded to 731) */ const CORRECTION_CUTOFF_DAYS = 731; /** * Calculate post-menstrual age in days. * PMA = gestational age at birth (in days) + chronological age (in days) */ export function postMenstrualAgeDays( chronologicalDays: number, gestationalAgeWeeks: number ): number { return gestationalAgeWeeks * 7 + chronologicalDays; } /** * Calculate age in days between two dates (ignoring time-of-day). * Uses UTC to avoid timezone/DST issues. */ export function ageInDays(dateOfBirth: Date, dateOfMeasurement: Date): number { const birthUTC = Date.UTC( dateOfBirth.getFullYear(), dateOfBirth.getMonth(), dateOfBirth.getDate() ); const measureUTC = Date.UTC( dateOfMeasurement.getFullYear(), dateOfMeasurement.getMonth(), dateOfMeasurement.getDate() ); return Math.floor((measureUTC - birthUTC) / MS_PER_DAY); } /** * Calculate corrected age for premature infants. * Correction = (40 - gestationalAgeWeeks) * 7 days * Applied only when chronological age < 2 years (731 days). */ export function correctedAgeInDays( chronologicalDays: number, gestationalAgeWeeks?: number ): number { if ( gestationalAgeWeeks == null || gestationalAgeWeeks >= 37 || chronologicalDays >= CORRECTION_CUTOFF_DAYS ) { return chronologicalDays; } const correctionDays = (FULL_TERM_WEEKS - gestationalAgeWeeks) * 7; return Math.max(0, chronologicalDays - correctionDays); } /** * Format age as a human-readable string. * - < 1 month: days only * - < 2 years: months and days * - >= 2 years: years and months */ export function formatAge(days: number, strings: I18nStrings['age']): string { if (days < 0) return strings.days(0); const totalMonths = Math.floor(days / 30.4375); const years = Math.floor(totalMonths / 12); const months = totalMonths % 12; const remainingDays = Math.floor(days - totalMonths * 30.4375); if (days < 31) { return strings.days(days); } if (totalMonths < 24) { if (remainingDays > 0) { return `${strings.months(totalMonths)} ${strings.and} ${strings.days(remainingDays)}`; } return strings.months(totalMonths); } if (months > 0) { return `${strings.years(years)} ${strings.and} ${strings.months(months)}`; } return strings.years(years); } /** * Calculate full age information including prematurity correction. */ export function calculateAge( dateOfBirth: Date, dateOfMeasurement: Date, gestationalAgeWeeks: number | undefined, strings: I18nStrings['age'] ): AgeResult { const chronologicalDays = ageInDays(dateOfBirth, dateOfMeasurement); const correctedDays = correctedAgeInDays(chronologicalDays, gestationalAgeWeeks); const isCorrected = correctedDays !== chronologicalDays; return { chronologicalDays, correctedDays, isCorrected, formatted: formatAge(chronologicalDays, strings), formattedCorrected: isCorrected ? formatAge(correctedDays, strings) : undefined, }; }