mirror of
https://github.com/home-assistant/core.git
synced 2026-08-14 01:02:51 +01:00
165 lines
4.9 KiB
Python
165 lines
4.9 KiB
Python
"""Helpers for sun events."""
|
|
|
|
from collections.abc import Callable
|
|
import datetime
|
|
from typing import TYPE_CHECKING, Any, cast
|
|
|
|
from homeassistant.const import SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET
|
|
from homeassistant.core import HomeAssistant, callback
|
|
from homeassistant.util import dt as dt_util
|
|
|
|
from .deprecation import deprecated_function
|
|
|
|
if TYPE_CHECKING:
|
|
import astral
|
|
import astral.location
|
|
|
|
type _AstralSunEventCallable = Callable[..., datetime.datetime]
|
|
|
|
|
|
@deprecated_function(
|
|
"homeassistant.helpers.sun.get_astral_observer",
|
|
breaks_in_ha_version="2027.7",
|
|
)
|
|
@callback
|
|
def get_astral_location(
|
|
hass: HomeAssistant,
|
|
) -> tuple[astral.location.Location, astral.Elevation]:
|
|
"""Get an astral location for the current Home Assistant configuration."""
|
|
from astral import LocationInfo # noqa: PLC0415
|
|
from astral.location import Location # noqa: PLC0415
|
|
|
|
latitude = hass.config.latitude
|
|
longitude = hass.config.longitude
|
|
timezone = str(hass.config.time_zone)
|
|
elevation = hass.config.elevation
|
|
|
|
return Location(LocationInfo("", "", timezone, latitude, longitude)), elevation
|
|
|
|
|
|
@callback
|
|
def get_astral_observer(hass: HomeAssistant) -> astral.Observer:
|
|
"""Get an astral observer for the current Home Assistant configuration."""
|
|
from astral import Observer # noqa: PLC0415
|
|
|
|
return Observer(hass.config.latitude, hass.config.longitude, hass.config.elevation)
|
|
|
|
|
|
@callback
|
|
def get_astral_event_next(
|
|
hass: HomeAssistant,
|
|
event: str,
|
|
utc_point_in_time: datetime.datetime | None = None,
|
|
offset: datetime.timedelta | None = None,
|
|
) -> datetime.datetime:
|
|
"""Calculate the next specified solar event."""
|
|
observer = get_astral_observer(hass)
|
|
return get_observer_astral_event_next(observer, event, utc_point_in_time, offset)
|
|
|
|
|
|
@deprecated_function(
|
|
"homeassistant.helpers.sun.get_observer_astral_event_next",
|
|
breaks_in_ha_version="2027.7",
|
|
)
|
|
@callback
|
|
def get_location_astral_event_next(
|
|
location: astral.location.Location,
|
|
elevation: astral.Elevation,
|
|
event: str,
|
|
utc_point_in_time: datetime.datetime | None = None,
|
|
offset: datetime.timedelta | None = None,
|
|
) -> datetime.datetime:
|
|
"""Calculate the next specified solar event."""
|
|
from astral import Observer # noqa: PLC0415
|
|
|
|
observer = Observer(location.latitude, location.longitude, elevation)
|
|
depression = location.solar_depression if event in ("dawn", "dusk") else None
|
|
return get_observer_astral_event_next(
|
|
observer, event, utc_point_in_time, offset, depression
|
|
)
|
|
|
|
|
|
@callback
|
|
def get_observer_astral_event_next(
|
|
observer: astral.Observer,
|
|
event: str,
|
|
utc_point_in_time: datetime.datetime | None = None,
|
|
offset: datetime.timedelta | None = None,
|
|
depression: float | None = None,
|
|
) -> datetime.datetime:
|
|
"""Calculate the next specified solar event."""
|
|
import astral.sun # noqa: PLC0415
|
|
|
|
if offset is None:
|
|
offset = datetime.timedelta()
|
|
|
|
if utc_point_in_time is None:
|
|
utc_point_in_time = dt_util.utcnow()
|
|
|
|
event_func = cast(_AstralSunEventCallable, getattr(astral.sun, event))
|
|
kwargs: dict[str, Any] = {}
|
|
if depression is not None:
|
|
kwargs["depression"] = depression
|
|
|
|
mod = -1
|
|
first_err = None
|
|
while mod < 367:
|
|
try:
|
|
next_dt = (
|
|
event_func(
|
|
observer,
|
|
dt_util.as_local(utc_point_in_time).date()
|
|
+ datetime.timedelta(days=mod),
|
|
**kwargs,
|
|
)
|
|
+ offset
|
|
)
|
|
if next_dt > utc_point_in_time:
|
|
return next_dt
|
|
except ValueError as err:
|
|
if not first_err:
|
|
first_err = err
|
|
mod += 1
|
|
raise ValueError(
|
|
f"Unable to find event after one year, initial ValueError: {first_err}"
|
|
) from first_err
|
|
|
|
|
|
@callback
|
|
def get_astral_event_date(
|
|
hass: HomeAssistant,
|
|
event: str,
|
|
date: datetime.date | datetime.datetime | None = None,
|
|
) -> datetime.datetime | None:
|
|
"""Calculate the astral event time for the specified date."""
|
|
import astral.sun # noqa: PLC0415
|
|
|
|
observer = get_astral_observer(hass)
|
|
|
|
if date is None:
|
|
date = dt_util.now().date()
|
|
|
|
if isinstance(date, datetime.datetime):
|
|
date = dt_util.as_local(date).date()
|
|
|
|
event_func = cast(_AstralSunEventCallable, getattr(astral.sun, event))
|
|
try:
|
|
return event_func(observer, date)
|
|
except ValueError:
|
|
# Event never occurs for specified date.
|
|
return None
|
|
|
|
|
|
@callback
|
|
def is_up(
|
|
hass: HomeAssistant, utc_point_in_time: datetime.datetime | None = None
|
|
) -> bool:
|
|
"""Calculate if the sun is currently up."""
|
|
if utc_point_in_time is None:
|
|
utc_point_in_time = dt_util.utcnow()
|
|
|
|
next_sunrise = get_astral_event_next(hass, SUN_EVENT_SUNRISE, utc_point_in_time)
|
|
next_sunset = get_astral_event_next(hass, SUN_EVENT_SUNSET, utc_point_in_time)
|
|
|
|
return next_sunrise > next_sunset
|