{
  "schemaVersion": 1,
  "slug": "age-calculator",
  "category": "everyday",
  "kind": "implementation",
  "independentValidation": false,
  "implementationHash": "a913c6c14a411feaad73e26bbafdfd1a7decbead36c61bb6c4b55ba0fbab5472",
  "provenance": "Generated from the calculation contract used by the published page. Publication is not external validation.",
  "sourceFiles": [
    "data/calculators/age-calculator.json",
    "src/engine.js"
  ],
  "runtimeSource": "/sources/engine.txt",
  "runtimeHash": "5c888d8898afbdee36267bb9977829e1e2ac568f6677c8ee87469c139864645c",
  "scope": "The compute function records implemented equations, constants, lookup tables and custom rules. Inputs record defaults and allowed values; outputs record precision and units. Defaults are examples, not recommendations. General background references are not evidence for this specific algorithm.",
  "publishedFormula": {
    "formula": "A = D_ref − D_birth    (Y → M → D)",
    "vars": [
      [
        "A",
        "Calendar age: completed years, then months, then remaining days"
      ],
      [
        "D_ref",
        "Reference date, on or after the date of birth"
      ],
      [
        "D_birth",
        "Date of birth"
      ]
    ],
    "note": "Years count completed birthdays; months are counted from the last birthday and clamped to month end when needed. This tool uses March 1 for February 29 birthdays in non-leap years. Total calendar days is a separate UTC date difference, not a fixed-length conversion of years and months."
  },
  "externalEvidence": {
    "reviewedAt": "2026-10-28",
    "sources": [
      {
        "name": "MDN — Date and UTC calendar operations",
        "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date"
      }
    ],
    "referenceRole": "definition",
    "implementationHash": "a913c6c14a411feaad73e26bbafdfd1a7decbead36c61bb6c4b55ba0fbab5472",
    "basis": "Count completed birthdays first. Starting at the most recent birthday, count complete calendar months; if the original day does not exist in a shorter month, use that month’s last day. The remaining calendar days form the last part of the age. Result units are yr (years), mo (months) and d (days). Total calendar days is a separate date difference, not years multiplied by 365. This calculator treats February 29 birthdays as March 1 in non-leap years. Dates are compared in UTC without time-of-day inputs; a birthday on the reference date has a countdown of zero. P1 consolidation retains the core model and adds input modes or derived outputs. The migration report records the input/output union and known-answer checks.",
    "limitations": "MDN documents the JavaScript Date and UTC operations used here. The March 1 leap-day birthday rule and month-end handling are this calculator’s calendar convention; the reference does not independently validate these choices. Added display totals and migration adapters are implementation behavior, not independently validated claims."
  },
  "contract": {
    "compute": "const mergedResult=(()=>{\nconst bad=()=>({age:null,exactAge:null,totalDays:null,daysToNext:null,nextBirthday:null,__error:'inputError'});\nconst make=(y,m,d)=>{const x=new Date(0);x.setUTCFullYear(y,m-1,d);x.setUTCHours(0,0,0,0);return x;};\nconst parse=s=>{const m=/^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(String(s));if(!m)return null;const y=+m[1],mo=+m[2],d=+m[3],x=make(y,mo,d);return y>=1&&y<=9998&&x.getUTCFullYear()===y&&x.getUTCMonth()===mo-1&&x.getUTCDate()===d?x:null;};\nconst b=parse(birthdate),today=parse(asOf);if(!b||!today||b>today)return bad();\nconst y=b.getUTCFullYear(),mo=b.getUTCMonth()+1,d=b.getUTCDate(),year=today.getUTCFullYear();\nconst leap=y=>y%4===0&&(y%100!==0||y%400===0);\nconst birthdayIn=y=>mo===2&&d===29&&!leap(y)?make(y,3,1):make(y,mo,d);\nlet age=year-y;if(birthdayIn(year)>today)age--;\nconst anchor=birthdayIn(y+age);\nconst addMonths=n=>{const base=anchor.getUTCMonth()+n,yy=anchor.getUTCFullYear()+Math.floor(base/12),mm=base%12+1;const last=make(yy,mm+1,0).getUTCDate();return make(yy,mm,Math.min(anchor.getUTCDate(),last));};\nlet months=0;while(months<11&&addMonths(months+1)<=today)months++;\nconst days=Math.round((today-addMonths(months))/86400000);\nlet next=birthdayIn(year);if(next<today)next=birthdayIn(year+1);\nconst iso=x=>String(x.getUTCFullYear()).padStart(4,'0')+'-'+String(x.getUTCMonth()+1).padStart(2,'0')+'-'+String(x.getUTCDate()).padStart(2,'0');\nconst elapsed=Math.round((today-b)/86400000);return{elapsedYears:elapsed/365.2425,calendarMonths:age*12+months,totalWeeks:elapsed/7,totalHours:elapsed*24,bornOnWeekday:['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'][b.getUTCDay()],age,exactAge:age+' / '+months+' / '+days,totalDays:Math.round((today-b)/86400000),daysToNext:Math.round((next-today)/86400000),nextBirthday:iso(next),__ageParts:[age,months,days]};\n})();return mergedResult.__error?{\"calendarMonths\":null,\"elapsedYears\":null,\"exactAge\":null,\"age\":null,\"daysToNext\":null,\"totalDays\":null,\"nextBirthday\":null,\"totalWeeks\":null,\"totalHours\":null,\"bornOnWeekday\":null,...mergedResult}:mergedResult;",
    "inputs": [
      {
        "name": "birthdate",
        "type": "date",
        "default": "1990-01-01",
        "min": "0001-01-01",
        "max": "9998-12-31",
        "requiredValue": true,
        "preserveEmpty": true
      },
      {
        "name": "asOf",
        "type": "date",
        "default": "@today",
        "min": "0001-01-01",
        "max": "9998-12-31",
        "requiredValue": true,
        "preserveEmpty": true
      }
    ],
    "outputs": [
      {
        "name": "exactAge",
        "type": "text",
        "unit": "",
        "components": [
          "ageYearsPart",
          "ageMonthsPart",
          "ageDaysPart"
        ],
        "display": "tokens",
        "separator": " · "
      },
      {
        "name": "age",
        "precision": 0
      },
      {
        "name": "daysToNext",
        "precision": 0
      },
      {
        "name": "totalDays",
        "precision": 0
      },
      {
        "name": "nextBirthday",
        "type": "text",
        "unit": ""
      },
      {
        "name": "totalWeeks",
        "precision": 2,
        "unit": "",
        "type": "number"
      },
      {
        "name": "totalHours",
        "precision": 0,
        "unit": "",
        "type": "number"
      },
      {
        "name": "bornOnWeekday",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "elapsedYears",
        "precision": 4,
        "unit": ""
      },
      {
        "name": "calendarMonths",
        "precision": 0,
        "unit": ""
      }
    ],
    "interpret": null
  }
}
