I wrote a test Python script that I used to determine the algorithm I’d use. At this point it looks like this:
from datetime import date, datetime, time, timedelta
from astral import AstralError, Location
from homeassistant.util import dt as dt_util
def _get_astral_event(location, solar_depression, event, date):
try:
location.solar_depression = solar_depression
return getattr(location, event)(date)
except AstralError:
return 'none'
def _sd_angle(solar_depression):
angle = {'astronomical': -18, 'nautical': -12, 'civil': -6}
if solar_depression in angle:
return angle[solar_depression]
return solar_depression
def _get_period(location, solar_depression, daylight, today):
if daylight:
if (location.solar_elevation(location.solar_noon(today))
< _sd_angle(solar_depression)):
return 0
start_event, end_event, end_offset = 'dawn', 'dusk', 0
else:
if (location.solar_elevation(location.solar_midnight(today))
>= _sd_angle(solar_depression)):
return 0
start_event, end_event, end_offset = 'dusk', 'dawn', 1
prev_end = _get_astral_event(location, solar_depression, end_event,
today+timedelta(days=end_offset-1))
start = _get_astral_event(location, solar_depression, start_event, today)
end = _get_astral_event(location, solar_depression, end_event,
today+timedelta(days=end_offset))
next_start = _get_astral_event(location, solar_depression, start_event,
today+timedelta(days=1))
# Due to rounding errors, it's possible for a dawn to be returned when the
# previous day did not return a dusk, and vice versa. Drop these outliers.
if prev_end == 'none':
start = 'none'
if next_start == 'none':
end = 'none'
if start == 'none':
if daylight:
start = dt_util.as_local(dt_util.as_utc(datetime.combine(
today, time())))
else:
return 0
if end == 'none':
for days in range(1, 366):
end = _get_astral_event(location, solar_depression, end_event,
today+timedelta(days=days+end_offset))
if end != 'none':
break
if end == 'none':
return 'none'
return (end - start).total_seconds()/3600
def test(latitude, longitude, elevation, time_zone):
print(latitude, longitude, elevation, time_zone)
location = Location(('', '', latitude, longitude, time_zone, elevation))
dt_util.set_default_time_zone(dt_util.get_time_zone(location.timezone))
for month, day in ((5, 21), (5, 22), (5, 23), (5, 24),
(7, 20), (7, 21), (7, 22), (7, 23), (7, 24)):
today = date(2019, month, day)
print('----------', today, sep='\n')
for solar_depression in ('astronomical', ):
print(solar_depression)
print(_get_astral_event(location, solar_depression, 'dawn', today))
print(_get_astral_event(location, solar_depression, 'dusk', today))
daylight = _get_period(location, solar_depression, True, today)
night = _get_period(location, solar_depression, False, today)
print(round(daylight, 3), round(daylight/24, 3))
print(round(night, 3), round(night/24, 3))