{
  "schemaVersion": 1,
  "slug": "moon-phase-calculator",
  "category": "date-time",
  "kind": "implementation",
  "independentValidation": false,
  "implementationHash": "6b16b4ab810f8289794a84297a6fccd182da1763b8a125b84c7ed23dcb0e51c0",
  "provenance": "Generated from the calculation contract used by the published page. Publication is not external validation.",
  "sourceFiles": [
    "data/calculators/moon-phase-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 − d₀) ÷ 24 h) mod S; k = (1 − cos(2π a ÷ S)) ÷ 2; S = 29.530588853",
    "vars": [
      [
        "a",
        "moon age, days since new moon"
      ],
      [
        "d",
        "query date (12:00 UTC)"
      ],
      [
        "d₀",
        "new moon 2000-01-06 18:14 UTC"
      ],
      [
        "k",
        "illuminated fraction"
      ],
      [
        "S",
        "synodic month, days"
      ]
    ],
    "note": "Uses UTC date fields and a fixed reference new moon with a mean synodic month of 29.530588853 days. The result is an approximate phase estimate, not an astronomical ephemeris; years 1–99 are validated as written."
  },
  "externalEvidence": null,
  "contract": {
    "compute": "const out=(()=>{const SYNODIC=29.530588853,knownNewMoon=Date.UTC(2000,0,6,18,14,0);const raw=String(date);const m=raw.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);if(!m)return{phaseIndex:null,illumination:null,moonAge:null,daysToFullMoon:null,__error:'inputError'};const y=Number(m[1]),mo=Number(m[2]),day=Number(m[3]),dcheck=new Date(0);dcheck.setUTCHours(0,0,0,0);dcheck.setUTCFullYear(y,mo-1,day);if(dcheck.getUTCFullYear()!==y||dcheck.getUTCMonth()!==mo-1||dcheck.getUTCDate()!==day)return{phaseIndex:null,illumination:null,moonAge:null,daysToFullMoon:null,__error:'inputError'};const d=new Date(raw+'T12:00:00Z'),since=(d.getTime()-knownNewMoon)/86400000,age=((since%SYNODIC)+SYNODIC)%SYNODIC,frac=age/SYNODIC;return{phaseIndex:Math.floor(frac*8+.5)%8,illumination:(1-Math.cos(2*Math.PI*frac))/2*100,moonAge:age,daysToFullMoon:((SYNODIC/2-age)+SYNODIC)%SYNODIC}})();if(out.__error)return out;return {...out,phaseName:Number.isInteger(out.phaseIndex)?['New Moon','Waxing Crescent','First Quarter','Waxing Gibbous','Full Moon','Waning Gibbous','Last Quarter','Waning Crescent'][out.phaseIndex]:null};",
    "inputs": [
      {
        "name": "date",
        "type": "date",
        "default": "@today",
        "step": 1,
        "preserveEmpty": true,
        "requiredValue": true
      }
    ],
    "outputs": [
      {
        "name": "phaseName",
        "type": "text",
        "precision": 0,
        "unit": ""
      },
      {
        "name": "phaseIndex",
        "precision": 0,
        "unit": "",
        "trimTrailingZeros": true
      },
      {
        "name": "illumination",
        "precision": 1,
        "unit": "%",
        "trimTrailingZeros": true
      },
      {
        "name": "moonAge",
        "precision": 2,
        "unit": "days",
        "trimTrailingZeros": true
      },
      {
        "name": "daysToFullMoon",
        "precision": 2,
        "unit": "days",
        "trimTrailingZeros": true
      }
    ],
    "interpret": [
      {
        "max": 0.5,
        "key": "new"
      },
      {
        "max": 1.5,
        "key": "waxingCrescent"
      },
      {
        "max": 2.5,
        "key": "firstQuarter"
      },
      {
        "max": 3.5,
        "key": "waxingGibbous"
      },
      {
        "max": 4.5,
        "key": "full"
      },
      {
        "max": 5.5,
        "key": "waningGibbous"
      },
      {
        "max": 6.5,
        "key": "lastQuarter"
      },
      {
        "max": 1000000000,
        "key": "waningCrescent"
      }
    ]
  }
}
