27be1892ed
Desktop (Electron/Windows): device dashboard, DWM scheduling engine, native firmware rules editor, Windows background service, web remote, sunrise/sunset support. Homebridge plugin (homebridge-dibby-wemo v1.0.0): HomeKit switches for all local Wemo devices, custom UI with DWM rules, device rules, scheduler heartbeat, and location-based sunrise/sunset scheduling. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
82 lines
2.7 KiB
JavaScript
82 lines
2.7 KiB
JavaScript
'use strict';
|
||
|
||
/**
|
||
* Calculate sunrise and sunset times for a given location and date.
|
||
* Pure JS – no external dependencies.
|
||
*
|
||
* Algorithm: NOAA Solar Calculator (Jean Meeus, Astronomical Algorithms).
|
||
*
|
||
* @param {number} lat Latitude in decimal degrees (positive = North)
|
||
* @param {number} lng Longitude in decimal degrees (positive = East)
|
||
* @param {Date} date Date to calculate for (default: today)
|
||
* @returns {{ sunrise: number|null, sunset: number|null }}
|
||
* Times as integer seconds from LOCAL midnight.
|
||
* null for each value if polar day or polar night.
|
||
*/
|
||
function sunTimes(lat, lng, date = new Date()) {
|
||
const D2R = Math.PI / 180;
|
||
const R2D = 180 / Math.PI;
|
||
|
||
const year = date.getFullYear();
|
||
const month = date.getMonth() + 1;
|
||
const day = date.getDate();
|
||
|
||
const A = Math.floor((14 - month) / 12);
|
||
const Y = year + 4800 - A;
|
||
const M = month + 12 * A - 3;
|
||
const JDN = day + Math.floor((153 * M + 2) / 5) + 365 * Y
|
||
+ Math.floor(Y / 4) - Math.floor(Y / 100) + Math.floor(Y / 400) - 32045;
|
||
const JD = JDN - 0.5;
|
||
|
||
const T = (JD - 2451545.0) / 36525.0;
|
||
|
||
let L0 = 280.46646 + T * (36000.76983 + T * 0.0003032);
|
||
L0 = ((L0 % 360) + 360) % 360;
|
||
|
||
let Mdeg = 357.52911 + T * (35999.05029 - 0.0001537 * T);
|
||
Mdeg = ((Mdeg % 360) + 360) % 360;
|
||
const Mrad = Mdeg * D2R;
|
||
|
||
const C = (1.914602 - T * (0.004817 + 0.000014 * T)) * Math.sin(Mrad)
|
||
+ (0.019993 - 0.000101 * T) * Math.sin(2 * Mrad)
|
||
+ 0.000289 * Math.sin(3 * Mrad);
|
||
|
||
const omega = 125.04 - 1934.136 * T;
|
||
const lambda = (L0 + C) - 0.00569 - 0.00478 * Math.sin(omega * D2R);
|
||
|
||
const eps0 = 23.0
|
||
+ (26.0 + (21.448 - T * (46.8150 + T * (0.00059 - T * 0.001813))) / 60.0) / 60.0;
|
||
const eps = (eps0 + 0.00256 * Math.cos(omega * D2R)) * D2R;
|
||
|
||
const sinDec = Math.sin(eps) * Math.sin(lambda * D2R);
|
||
const decl = Math.asin(sinDec);
|
||
|
||
const e = 0.016708634 - T * (0.000042037 + 0.0000001267 * T);
|
||
const y = Math.pow(Math.tan(eps / 2), 2);
|
||
const EqT = 4 * R2D * (
|
||
y * Math.sin(2 * L0 * D2R)
|
||
- 2 * e * Math.sin(Mrad)
|
||
+ 4 * e * y * Math.sin(Mrad) * Math.cos(2 * L0 * D2R)
|
||
- 0.5 * y * y * Math.sin(4 * L0 * D2R)
|
||
- 1.25 * e * e * Math.sin(2 * Mrad)
|
||
);
|
||
|
||
const cosHA = (Math.cos(90.833 * D2R) - Math.sin(lat * D2R) * sinDec)
|
||
/ (Math.cos(lat * D2R) * Math.cos(decl));
|
||
|
||
if (cosHA < -1 || cosHA > 1) {
|
||
return { sunrise: null, sunset: null };
|
||
}
|
||
|
||
const HA = Math.acos(cosHA) * R2D;
|
||
const tzOffsetMin = -date.getTimezoneOffset();
|
||
const solarNoon = 720.0 - 4.0 * lng - EqT + tzOffsetMin;
|
||
|
||
return {
|
||
sunrise: Math.round((solarNoon - HA * 4.0) * 60),
|
||
sunset: Math.round((solarNoon + HA * 4.0) * 60),
|
||
};
|
||
}
|
||
|
||
module.exports = { sunTimes };
|