{
  "schemaVersion": 1,
  "slug": "date-calculator",
  "category": "household",
  "kind": "implementation",
  "independentValidation": false,
  "implementationHash": "c6022adc0f75dbeb175d62deac674ab4eb53b09c7b7b159110f9cffcab3f7664",
  "provenance": "Generated from the calculation contract used by the published page. Publication is not external validation.",
  "sourceFiles": [
    "data/calculators/date-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": "R = addDays(clampMonth(S, ±(12Y + M)), ±(7W + D))",
    "vars": [],
    "note": "The reference date changes only the result-distance note, not the result date. No holidays or time zones are applied."
  },
  "externalEvidence": {
    "reviewedAt": "2026-10-31",
    "sources": [
      {
        "name": "MDN — JavaScript Date and UTC methods",
        "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date"
      }
    ],
    "implementationHash": "c6022adc0f75dbeb175d62deac674ab4eb53b09c7b7b159110f9cffcab3f7664",
    "basis": "Use strict Gregorian dates from year 0001 to 9999. A combined offset adds years and months together, clamps the day once to the target month end, then applies weeks and days. Subtraction reverses the offset. Complete months follow that same clamp rule. The reference date changes only the result-distance note, not the result date. No holidays or time zones are applied.",
    "limitations": "The reference date changes only the result-distance note, not the result date. No holidays or time zones are applied."
  },
  "contract": {
    "compute": "try{if((([\"single\"].includes(String(inputMode)))&&(!Number.isFinite(amount)||amount<0||amount>100000))||(([\"combined\"].includes(String(inputMode)))&&(!Number.isFinite(days)||days<-100000||days>100000))||(([\"combined\"].includes(String(inputMode)))&&(!Number.isFinite(weeks)||weeks<-10000||weeks>10000))||(([\"combined\"].includes(String(inputMode)))&&(!Number.isFinite(months)||months<-1200||months>1200))||(([\"combined\"].includes(String(inputMode)))&&(!Number.isFinite(years)||years<-100||years>100)))throw new Error('inputError');const calculate=function(){const h=(function context() {\n  const DAY=86400000;\n  const own=(o,k)=>Object.prototype.hasOwnProperty.call(o,k);\n  const fail=()=>{throw new Error('inputError');};\n  const num=(v,min=-Infinity,max=Infinity,integer=false)=>{\n    if(!Number.isFinite(v)||v<min||v>max||(integer&&!Number.isInteger(v)))fail();return v;\n  };\n  const choice=(v,values)=>{if(!values.includes(v))fail();return v;};\n  const date=s=>{if(typeof s!=='string'||!/^\\d{4}-\\d{2}-\\d{2}$/.test(s)||s.startsWith('0000'))fail();const d=new Date(s+'T00:00:00Z');if(!Number.isFinite(+d)||d.toISOString().slice(0,10)!==s)fail();return d;};\n  const iso=d=>{if(!Number.isFinite(+d)||d.getUTCFullYear()<1||d.getUTCFullYear()>9999)fail();return d.toISOString().slice(0,10);};\n  const shift=(d,n)=>{const r=new Date(+d+n*DAY);iso(r);return r;};\n  const month=(d,n)=>{const r=new Date(+d);r.setUTCDate(1);r.setUTCMonth(r.getUTCMonth()+n);const end=new Date(+r);end.setUTCMonth(end.getUTCMonth()+1,0);r.setUTCDate(Math.min(d.getUTCDate(),end.getUTCDate()));iso(r);return r;};\n  const weekdays=['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];\n  const work=(d,count)=>{let n=Math.floor(count/7)*5;for(let k=0;k<count%7;k++){const w=(d.getUTCDay()+k)%7;if(w!==0&&w!==6)n++;}return n;};\n  const wholeMonths=(a,b)=>{let n=(b.getUTCFullYear()-a.getUTCFullYear())*12+b.getUTCMonth()-a.getUTCMonth();if(n>=0&&+month(a,n)>+b)n--;if(n<0&&+month(a,n)<+b)n++;return n;};\n  return {DAY,own,fail,num,choice,date,iso,shift,month,weekdays,work,wholeMonths};\n})();const result=(function(v,h){\n    const {date,num,choice,month,shift,iso,DAY,weekdays,wholeMonths}=h,a=date(v.startDate),ref=date(v.today);\n    choice(v.operation,['add','subtract']);choice(v.inputMode,['single','combined']);if(v.inputMode==='single')choice(v.unit,['days','weeks','months','years']);\n    const sign=v.operation==='subtract'?-1:1;let m=0,d=0;\n    if(v.inputMode==='combined'){num(v.days,-100000,100000,true);num(v.weeks,-10000,10000,true);num(v.months,-1200,1200,true);num(v.years,-100,100,true);m=v.months+12*v.years;d=v.days+7*v.weeks;}\n    else{num(v.amount,0,100000,true);if(v.unit==='months')m=v.amount;else if(v.unit==='years')m=v.amount*12;else d=v.amount*(v.unit==='weeks'?7:1);}\n    const result=shift(month(a,sign*m),sign*d),delta=Math.round((result-a)/DAY),away=Math.round((result-ref)/DAY);\n    const th=new Date(+result);th.setUTCDate(th.getUTCDate()+3-(th.getUTCDay()+6)%7);const yearStart=new Date(+th);yearStart.setUTCMonth(0,1);\n    const jan=new Date(+result);jan.setUTCMonth(0,1);\n    return {resultDate:iso(result),dayOfWeek:weekdays[result.getUTCDay()],daysBetween:delta,weeksBetween:delta/7,monthsBetween:wholeMonths(a,result),weekNumber:Math.ceil(((th-yearStart)/DAY+1)/7),isLeapYear:(result.getUTCFullYear()%4===0&&(result.getUTCFullYear()%100!==0||result.getUTCFullYear()%400===0))?'Yes':'No',note:away===0?'The result falls on the reference date itself.':'The result is '+Math.abs(away)+(Math.abs(away)===1?' day ':' days ')+(away>0?'after':'before')+' the reference date.',totalDays:delta,dayOfYear:Math.round((result-jan)/DAY)+1,daysAgo:-delta,weeksAgo:-delta/7};\n  })({\"startDate\":(startDate),\"today\":(today),\"operation\":(operation),\"amount\":(amount),\"unit\":(unit),\"inputMode\":(inputMode),\"days\":(days),\"weeks\":(weeks),\"months\":(months),\"years\":(years)},h);return result;};const out=calculate();return Object.fromEntries([\"resultDate\",\"dayOfWeek\",\"daysBetween\",\"weeksBetween\",\"monthsBetween\",\"weekNumber\",\"isLeapYear\",\"note\",\"dayOfYear\"].map(k=>[k,out[k]??null]).concat(Object.entries(out).filter(([k])=>k.startsWith('__'))));}catch(error){return Object.assign(Object.fromEntries([\"resultDate\",\"dayOfWeek\",\"daysBetween\",\"weeksBetween\",\"monthsBetween\",\"weekNumber\",\"isLeapYear\",\"note\",\"dayOfYear\"].map(k=>[k,null])),{__error:error.message==='openingsExceedWallArea'?'openingsExceedWallArea':'inputError'});}",
    "inputs": [
      {
        "name": "startDate",
        "type": "date",
        "default": "@today",
        "step": 1,
        "requiredValue": true,
        "preserveEmpty": true
      },
      {
        "name": "operation",
        "type": "select",
        "default": "add",
        "options": [
          {
            "value": "add"
          },
          {
            "value": "subtract"
          }
        ]
      },
      {
        "name": "amount",
        "type": "number",
        "default": 90,
        "min": 0,
        "max": 100000,
        "step": 1,
        "requiredValue": true,
        "strictRange": true,
        "showWhen": {
          "inputMode": [
            "single"
          ]
        }
      },
      {
        "name": "unit",
        "type": "select",
        "default": "days",
        "options": [
          {
            "value": "days"
          },
          {
            "value": "weeks"
          },
          {
            "value": "months"
          },
          {
            "value": "years"
          }
        ],
        "showWhen": {
          "inputMode": [
            "single"
          ]
        }
      },
      {
        "name": "today",
        "type": "date",
        "default": "@today",
        "step": 1,
        "advanced": true,
        "requiredValue": true,
        "preserveEmpty": true
      },
      {
        "type": "select",
        "default": "single",
        "options": [
          {
            "value": "single"
          },
          {
            "value": "combined"
          }
        ],
        "advanced": true,
        "name": "inputMode"
      },
      {
        "name": "days",
        "type": "number",
        "default": 90,
        "min": -100000,
        "max": 100000,
        "step": 1,
        "advanced": true,
        "showWhen": {
          "inputMode": [
            "combined"
          ]
        },
        "strictRange": true,
        "requiredValue": true
      },
      {
        "name": "weeks",
        "type": "number",
        "default": 0,
        "min": -10000,
        "max": 10000,
        "step": 1,
        "advanced": true,
        "showWhen": {
          "inputMode": [
            "combined"
          ]
        },
        "strictRange": true,
        "requiredValue": true
      },
      {
        "name": "months",
        "type": "number",
        "default": 0,
        "min": -1200,
        "max": 1200,
        "step": 1,
        "slider": true,
        "advanced": true,
        "showWhen": {
          "inputMode": [
            "combined"
          ]
        },
        "strictRange": true,
        "requiredValue": true
      },
      {
        "name": "years",
        "type": "number",
        "default": 0,
        "min": -100,
        "max": 100,
        "step": 1,
        "slider": true,
        "advanced": true,
        "showWhen": {
          "inputMode": [
            "combined"
          ]
        },
        "strictRange": true,
        "requiredValue": true
      }
    ],
    "outputs": [
      {
        "name": "resultDate",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "dayOfWeek",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "daysBetween",
        "precision": 0,
        "unit": "days",
        "notation": "auto"
      },
      {
        "name": "weeksBetween",
        "precision": 1,
        "unit": "weeks",
        "notation": "auto"
      },
      {
        "name": "monthsBetween",
        "precision": 0,
        "unit": "months",
        "notation": "auto"
      },
      {
        "name": "weekNumber",
        "precision": 0,
        "unit": "",
        "notation": "auto"
      },
      {
        "name": "isLeapYear",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "note",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "dayOfYear",
        "precision": 0,
        "unit": "days",
        "notation": "auto"
      }
    ],
    "interpret": null
  }
}
