forked from home-assistant/core
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsun.py
137 lines (108 loc) · 4 KB
/
sun.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
"""Helpers for sun events."""
from __future__ import annotations
import datetime
from typing import TYPE_CHECKING
from homeassistant.const import SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET
from homeassistant.core import HomeAssistant, callback
from homeassistant.loader import bind_hass
from homeassistant.util import dt as dt_util
if TYPE_CHECKING:
import astral
DATA_LOCATION_CACHE = "astral_location_cache"
ELEVATION_AGNOSTIC_EVENTS = ("noon", "midnight")
@callback
@bind_hass
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 # pylint: disable=import-outside-toplevel
from astral.location import Location # pylint: disable=import-outside-toplevel
latitude = hass.config.latitude
longitude = hass.config.longitude
timezone = str(hass.config.time_zone)
elevation = hass.config.elevation
info = ("", "", timezone, latitude, longitude)
# Cache astral locations so they aren't recreated with the same args
if DATA_LOCATION_CACHE not in hass.data:
hass.data[DATA_LOCATION_CACHE] = {}
if info not in hass.data[DATA_LOCATION_CACHE]:
hass.data[DATA_LOCATION_CACHE][info] = Location(LocationInfo(*info))
return hass.data[DATA_LOCATION_CACHE][info], elevation
@callback
@bind_hass
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."""
location, elevation = get_astral_location(hass)
return get_location_astral_event_next(
location, elevation, event, utc_point_in_time, offset
)
@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."""
if offset is None:
offset = datetime.timedelta()
if utc_point_in_time is None:
utc_point_in_time = dt_util.utcnow()
kwargs = {"local": False}
if event not in ELEVATION_AGNOSTIC_EVENTS:
kwargs["observer_elevation"] = elevation
mod = -1
while True:
try:
next_dt: datetime.datetime = (
getattr(location, event)(
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:
pass
mod += 1
@callback
@bind_hass
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."""
location, elevation = get_astral_location(hass)
if date is None:
date = dt_util.now().date()
if isinstance(date, datetime.datetime):
date = dt_util.as_local(date).date()
kwargs = {"local": False}
if event not in ELEVATION_AGNOSTIC_EVENTS:
kwargs["observer_elevation"] = elevation
try:
return getattr(location, event)(date, **kwargs) # type: ignore[no-any-return]
except ValueError:
# Event never occurs for specified date.
return None
@callback
@bind_hass
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