{
  "schemaVersion": 1,
  "slug": "sunrise-sunset-calculator",
  "category": "date-time",
  "kind": "implementation",
  "independentValidation": false,
  "implementationHash": "d3b397442847a9ec1fb1afe9cec27ab2088c0587f2922e0cad67e7dbfe96dfc9",
  "provenance": "Generated from the calculation contract used by the published page. Publication is not external validation.",
  "sourceFiles": [
    "data/calculators/sunrise-sunset-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": "δ = 23.44° sin(2π(N−81)÷365); sunrise = 12 − λ÷15 − acos(−tan φ tan δ)÷15",
    "vars": [
      [
        "N",
        "Day of year"
      ],
      [
        "φ",
        "Latitude (degrees)"
      ],
      [
        "λ",
        "Longitude (degrees)"
      ],
      [
        "δ",
        "Solar declination"
      ]
    ],
    "note": "Times in UTC. Solar noon = 12 − λ÷15; sunset = noon + H; daylight = 2H, where H = acos(−tan φ tan δ)÷15. Argument > 1 = polar night, < −1 = midnight sun."
  },
  "externalEvidence": null,
  "contract": {
    "compute": "const validDate = (s) => { const m = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(String(s)); if (!m) return false; const d = new Date(String(s) + 'T00:00:00Z'); return Number.isFinite(d.getTime()) && d.getUTCFullYear() === +m[1] && d.getUTCMonth()+1 === +m[2] && d.getUTCDate() === +m[3]; };\nif (!validDate(date) || latitude < -89 || latitude > 89 || longitude < -180 || longitude > 180) return { sunriseHour: null, sunsetHour: null, daylight: null, solarNoon: null, sunriseText: null, sunsetText: null, __error: 'inputError' };\nconst d=new Date(date+'T00:00:00Z'); const start=new Date(Date.UTC(d.getUTCFullYear(),0,0)); const N = Math.round((d-start)/86400000); const decl = 23.44*Math.PI/180*Math.sin(2*Math.PI/365*(N-81)); const lat = latitude*Math.PI/180; const cosH = -Math.tan(lat)*Math.tan(decl); const noon=12-longitude/15; const pad=(n)=>{const mins=Math.round((((n%24)+24)%24)*60);return String(Math.floor(mins/60)%24).padStart(2,'0')+':'+String(mins%60).padStart(2,'0');}; if(cosH > 1){ return { sunriseHour:null, sunsetHour:null, daylight:0, solarNoon:noon, sunriseText:'No sunrise (polar night)', sunsetText:'No sunset (polar night)' }; } if(cosH < -1){ return { sunriseHour:null, sunsetHour:null, daylight:24, solarNoon:noon, sunriseText:'No sunrise (midnight sun)', sunsetText:'No sunset (midnight sun)' }; } const H = Math.acos(cosH)*180/Math.PI/15; const rise=noon-H, set=noon+H; return { sunriseHour: rise, sunsetHour: set, daylight: 2*H, solarNoon: noon, sunriseText: pad(rise) + ' UTC', sunsetText: pad(set) + ' UTC' };",
    "inputs": [
      {
        "name": "date",
        "type": "date",
        "default": "@today",
        "step": 1
      },
      {
        "name": "latitude",
        "type": "number",
        "default": 51.5,
        "min": -89,
        "max": 89,
        "step": 0.01
      },
      {
        "name": "longitude",
        "type": "number",
        "default": -0.13,
        "min": -180,
        "max": 180,
        "step": 0.01
      }
    ],
    "outputs": [
      {
        "name": "sunriseHour",
        "precision": 2,
        "unit": "h",
        "optional": true
      },
      {
        "name": "sunsetHour",
        "precision": 2,
        "unit": "h",
        "optional": true
      },
      {
        "name": "daylight",
        "precision": 2,
        "unit": "h"
      },
      {
        "name": "solarNoon",
        "precision": 2,
        "unit": "h"
      },
      {
        "name": "sunriseText",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "sunsetText",
        "precision": 0,
        "unit": "",
        "type": "text"
      }
    ],
    "interpret": null
  }
}
