{
  "schemaVersion": 1,
  "slug": "retirement-countdown-calculator",
  "category": "date-time",
  "kind": "implementation",
  "independentValidation": false,
  "implementationHash": "f63474725e6ace3721021e76b40e70b493c1b3be55c7f0e3955cd34c791f2813",
  "provenance": "Generated from the calculation contract used by the published page. Publication is not external validation.",
  "sourceFiles": [
    "data/calculators/retirement-countdown-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": "D = (D_R − D_now) ÷ 86400000; W = Σ_{d: D_now≤d<D_R, w(d)∈{1,…,5}} 1 − ⌈D÷365.2425×h⌉; Y = D÷365.2425",
    "vars": [
      [
        "D_R",
        "Retirement date"
      ],
      [
        "D",
        "Days until retirement"
      ],
      [
        "W",
        "Working days left"
      ],
      [
        "h",
        "Holidays per year"
      ],
      [
        "Y",
        "Years until retirement"
      ],
      [
        "D_now",
        "Current date"
      ],
      [
        "d",
        "Day index"
      ],
      [
        "w(d)",
        "Day of week (1 = Mon … 7 = Sun)"
      ]
    ],
    "note": "D_R = birth date plus retirementAge whole years. The loop steps day by day, excluding Saturday and Sunday, and stops at D_R; months = round(Y×12)."
  },
  "externalEvidence": null,
  "contract": {
    "compute": "const DAY=86400000; const b=new Date(birthDate+'T00:00:00Z'); const match=/^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(String(birthDate));const valid=!!match&&Number.isFinite(b.getTime())&&b.getUTCFullYear()===Number(match[1])&&b.getUTCMonth()+1===Number(match[2])&&b.getUTCDate()===Number(match[3]);const now=new Date(); now.setUTCHours(0,0,0,0);if(!valid||b>now)return{daysUntil:null,workingDaysUntil:null,yearsUntil:null,monthsUntil:null,retirementDate:null,currentAge:null,__error:'inputError'};const target=new Date(0);target.setUTCHours(0,0,0,0);target.setUTCFullYear(b.getUTCFullYear()+Math.floor(retirementAge),b.getUTCMonth(),b.getUTCDate());const days=Math.max(0,Math.round((target-now)/DAY));let wd=0;const cur=new Date(now.getTime());while(cur<target){const day=cur.getUTCDay();if(day!==0&&day!==6)wd++;cur.setUTCDate(cur.getUTCDate()+1);}const yearsToGo=days/365.2425;const holidays=Math.max(yearsToGo,0)*holidaysPerYear;return{daysUntil:days,workingDaysUntil:Math.max(wd-Math.round(holidays),0),yearsUntil:yearsToGo,monthsUntil:Math.round(yearsToGo*12),retirementDate:target.toISOString().slice(0,10),currentAge:Math.max((now-b)/(365.2425*DAY),0)};",
    "inputs": [
      {
        "name": "birthDate",
        "type": "date",
        "default": "1980-05-20",
        "step": 1
      },
      {
        "name": "retirementAge",
        "type": "number",
        "default": 65,
        "min": 40,
        "max": 90,
        "step": 1,
        "slider": true
      },
      {
        "name": "holidaysPerYear",
        "type": "number",
        "default": 10,
        "min": 0,
        "max": 60,
        "step": 1,
        "slider": true
      }
    ],
    "outputs": [
      {
        "name": "daysUntil",
        "precision": 0,
        "unit": "days"
      },
      {
        "name": "workingDaysUntil",
        "precision": 0,
        "unit": "days"
      },
      {
        "name": "yearsUntil",
        "precision": 1,
        "unit": "years"
      },
      {
        "name": "monthsUntil",
        "precision": 0,
        "unit": "months"
      },
      {
        "name": "retirementDate",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "currentAge",
        "precision": 1,
        "unit": "years"
      }
    ],
    "interpret": null
  }
}
