diff --git a/tests/unit/test_solar.py b/tests/unit/test_solar.py new file mode 100644 index 0000000..3d02539 --- /dev/null +++ b/tests/unit/test_solar.py @@ -0,0 +1,18 @@ +import datetime +from vigilar.detection.solar import get_sunset + + +def test_sunset_returns_time(): + result = get_sunset(45.0, -85.0, datetime.date(2026, 6, 21)) + assert isinstance(result, datetime.time) + + +def test_sunset_equator(): + result = get_sunset(0.0, 0.0, datetime.date(2026, 3, 20)) + assert 17 <= result.hour <= 19 + + +def test_sunset_different_dates_vary(): + d1 = get_sunset(45.0, -85.0, datetime.date(2026, 3, 1)) + d2 = get_sunset(45.0, -85.0, datetime.date(2026, 9, 1)) + assert d1 != d2 diff --git a/vigilar/detection/solar.py b/vigilar/detection/solar.py new file mode 100644 index 0000000..d2dcc61 --- /dev/null +++ b/vigilar/detection/solar.py @@ -0,0 +1,31 @@ +"""Sunset calculation using NOAA solar equations. Stdlib only.""" + +import datetime +import math + + +def get_sunset(latitude: float, longitude: float, date: datetime.date) -> datetime.time: + n = date.timetuple().tm_yday + gamma = 2 * math.pi / 365 * (n - 1) + eqtime = 229.18 * ( + 0.000075 + 0.001868 * math.cos(gamma) - 0.032077 * math.sin(gamma) + - 0.014615 * math.cos(2 * gamma) - 0.040849 * math.sin(2 * gamma) + ) + decl = ( + 0.006918 - 0.399912 * math.cos(gamma) + 0.070257 * math.sin(gamma) + - 0.006758 * math.cos(2 * gamma) + 0.000907 * math.sin(2 * gamma) + - 0.002697 * math.cos(3 * gamma) + 0.00148 * math.sin(3 * gamma) + ) + lat_rad = math.radians(latitude) + zenith = math.radians(90.833) + cos_ha = ( + math.cos(zenith) / (math.cos(lat_rad) * math.cos(decl)) + - math.tan(lat_rad) * math.tan(decl) + ) + cos_ha = max(-1.0, min(1.0, cos_ha)) + ha = math.degrees(math.acos(cos_ha)) + sunset_minutes = 720 - 4 * (longitude - ha) - eqtime + sunset_minutes = sunset_minutes % 1440 + hours = int(sunset_minutes // 60) + minutes = int(sunset_minutes % 60) + return datetime.time(hour=hours, minute=minutes)