mirror of
https://github.com/home-assistant/core.git
synced 2026-04-02 00:20:30 +01:00
Make Season integration timezone aware (#164876)
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date, datetime
|
||||
from datetime import datetime
|
||||
|
||||
import ephem
|
||||
|
||||
@@ -12,7 +12,7 @@ from homeassistant.const import CONF_TYPE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.util.dt import utcnow
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .const import DOMAIN, TYPE_ASTRONOMICAL
|
||||
|
||||
@@ -50,7 +50,7 @@ async def async_setup_entry(
|
||||
|
||||
|
||||
def get_season(
|
||||
current_date: date, hemisphere: str, season_tracking_type: str
|
||||
current_datetime: datetime, hemisphere: str, season_tracking_type: str
|
||||
) -> str | None:
|
||||
"""Calculate the current season."""
|
||||
|
||||
@@ -58,22 +58,36 @@ def get_season(
|
||||
return None
|
||||
|
||||
if season_tracking_type == TYPE_ASTRONOMICAL:
|
||||
spring_start = ephem.next_equinox(str(current_date.year)).datetime()
|
||||
summer_start = ephem.next_solstice(str(current_date.year)).datetime()
|
||||
autumn_start = ephem.next_equinox(spring_start).datetime()
|
||||
winter_start = ephem.next_solstice(summer_start).datetime()
|
||||
spring_start = (
|
||||
ephem.next_equinox(str(current_datetime.year))
|
||||
.datetime()
|
||||
.replace(tzinfo=dt_util.UTC)
|
||||
)
|
||||
summer_start = (
|
||||
ephem.next_solstice(str(current_datetime.year))
|
||||
.datetime()
|
||||
.replace(tzinfo=dt_util.UTC)
|
||||
)
|
||||
autumn_start = (
|
||||
ephem.next_equinox(spring_start).datetime().replace(tzinfo=dt_util.UTC)
|
||||
)
|
||||
winter_start = (
|
||||
ephem.next_solstice(summer_start).datetime().replace(tzinfo=dt_util.UTC)
|
||||
)
|
||||
else:
|
||||
spring_start = datetime(2017, 3, 1).replace(year=current_date.year)
|
||||
spring_start = current_datetime.replace(
|
||||
month=3, day=1, hour=0, minute=0, second=0, microsecond=0
|
||||
)
|
||||
summer_start = spring_start.replace(month=6)
|
||||
autumn_start = spring_start.replace(month=9)
|
||||
winter_start = spring_start.replace(month=12)
|
||||
|
||||
season = STATE_WINTER
|
||||
if spring_start <= current_date < summer_start:
|
||||
if spring_start <= current_datetime < summer_start:
|
||||
season = STATE_SPRING
|
||||
elif summer_start <= current_date < autumn_start:
|
||||
elif summer_start <= current_datetime < autumn_start:
|
||||
season = STATE_SUMMER
|
||||
elif autumn_start <= current_date < winter_start:
|
||||
elif autumn_start <= current_datetime < winter_start:
|
||||
season = STATE_AUTUMN
|
||||
|
||||
# If user is located in the southern hemisphere swap the season
|
||||
@@ -104,6 +118,4 @@ class SeasonSensorEntity(SensorEntity):
|
||||
|
||||
def update(self) -> None:
|
||||
"""Update season."""
|
||||
self._attr_native_value = get_season(
|
||||
utcnow().replace(tzinfo=None), self.hemisphere, self.type
|
||||
)
|
||||
self._attr_native_value = get_season(dt_util.now(), self.hemisphere, self.type)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""The tests for the Season integration."""
|
||||
|
||||
from datetime import datetime
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from freezegun import freeze_time
|
||||
import pytest
|
||||
@@ -20,6 +21,8 @@ from homeassistant.components.sensor import ATTR_OPTIONS, SensorDeviceClass
|
||||
from homeassistant.const import ATTR_DEVICE_CLASS, CONF_TYPE, STATE_UNKNOWN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.entity_component import async_update_entity
|
||||
from homeassistant.util.dt import UTC
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
@@ -44,25 +47,25 @@ HEMISPHERE_EMPTY = {
|
||||
}
|
||||
|
||||
NORTHERN_PARAMETERS = [
|
||||
(TYPE_ASTRONOMICAL, datetime(2017, 9, 3, 0, 0), STATE_SUMMER),
|
||||
(TYPE_METEOROLOGICAL, datetime(2017, 8, 13, 0, 0), STATE_SUMMER),
|
||||
(TYPE_ASTRONOMICAL, datetime(2017, 9, 23, 0, 0), STATE_AUTUMN),
|
||||
(TYPE_METEOROLOGICAL, datetime(2017, 9, 3, 0, 0), STATE_AUTUMN),
|
||||
(TYPE_ASTRONOMICAL, datetime(2017, 12, 25, 0, 0), STATE_WINTER),
|
||||
(TYPE_METEOROLOGICAL, datetime(2017, 12, 3, 0, 0), STATE_WINTER),
|
||||
(TYPE_ASTRONOMICAL, datetime(2017, 4, 1, 0, 0), STATE_SPRING),
|
||||
(TYPE_METEOROLOGICAL, datetime(2017, 3, 3, 0, 0), STATE_SPRING),
|
||||
(TYPE_ASTRONOMICAL, datetime(2017, 9, 3, 0, 0, tzinfo=UTC), STATE_SUMMER),
|
||||
(TYPE_METEOROLOGICAL, datetime(2017, 8, 13, 0, 0, tzinfo=UTC), STATE_SUMMER),
|
||||
(TYPE_ASTRONOMICAL, datetime(2017, 9, 23, 0, 0, tzinfo=UTC), STATE_AUTUMN),
|
||||
(TYPE_METEOROLOGICAL, datetime(2017, 9, 3, 0, 0, tzinfo=UTC), STATE_AUTUMN),
|
||||
(TYPE_ASTRONOMICAL, datetime(2017, 12, 25, 0, 0, tzinfo=UTC), STATE_WINTER),
|
||||
(TYPE_METEOROLOGICAL, datetime(2017, 12, 3, 0, 0, tzinfo=UTC), STATE_WINTER),
|
||||
(TYPE_ASTRONOMICAL, datetime(2017, 4, 1, 0, 0, tzinfo=UTC), STATE_SPRING),
|
||||
(TYPE_METEOROLOGICAL, datetime(2017, 3, 3, 0, 0, tzinfo=UTC), STATE_SPRING),
|
||||
]
|
||||
|
||||
SOUTHERN_PARAMETERS = [
|
||||
(TYPE_ASTRONOMICAL, datetime(2017, 12, 25, 0, 0), STATE_SUMMER),
|
||||
(TYPE_METEOROLOGICAL, datetime(2017, 12, 3, 0, 0), STATE_SUMMER),
|
||||
(TYPE_ASTRONOMICAL, datetime(2017, 4, 1, 0, 0), STATE_AUTUMN),
|
||||
(TYPE_METEOROLOGICAL, datetime(2017, 3, 3, 0, 0), STATE_AUTUMN),
|
||||
(TYPE_ASTRONOMICAL, datetime(2017, 9, 3, 0, 0), STATE_WINTER),
|
||||
(TYPE_METEOROLOGICAL, datetime(2017, 8, 13, 0, 0), STATE_WINTER),
|
||||
(TYPE_ASTRONOMICAL, datetime(2017, 9, 23, 0, 0), STATE_SPRING),
|
||||
(TYPE_METEOROLOGICAL, datetime(2017, 9, 3, 0, 0), STATE_SPRING),
|
||||
(TYPE_ASTRONOMICAL, datetime(2017, 12, 25, 0, 0, tzinfo=UTC), STATE_SUMMER),
|
||||
(TYPE_METEOROLOGICAL, datetime(2017, 12, 3, 0, 0, tzinfo=UTC), STATE_SUMMER),
|
||||
(TYPE_ASTRONOMICAL, datetime(2017, 4, 1, 0, 0, tzinfo=UTC), STATE_AUTUMN),
|
||||
(TYPE_METEOROLOGICAL, datetime(2017, 3, 3, 0, 0, tzinfo=UTC), STATE_AUTUMN),
|
||||
(TYPE_ASTRONOMICAL, datetime(2017, 9, 3, 0, 0, tzinfo=UTC), STATE_WINTER),
|
||||
(TYPE_METEOROLOGICAL, datetime(2017, 8, 13, 0, 0, tzinfo=UTC), STATE_WINTER),
|
||||
(TYPE_ASTRONOMICAL, datetime(2017, 9, 23, 0, 0, tzinfo=UTC), STATE_SPRING),
|
||||
(TYPE_METEOROLOGICAL, datetime(2017, 9, 3, 0, 0, tzinfo=UTC), STATE_SPRING),
|
||||
]
|
||||
|
||||
|
||||
@@ -154,7 +157,7 @@ async def test_season_equator(
|
||||
hass.config.latitude = HEMISPHERE_EQUATOR["homeassistant"]["latitude"]
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
with freeze_time(datetime(2017, 9, 3, 0, 0)):
|
||||
with freeze_time(datetime(2017, 9, 3, 0, 0, tzinfo=UTC)):
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -165,3 +168,43 @@ async def test_season_equator(
|
||||
entry = entity_registry.async_get("sensor.season")
|
||||
assert entry
|
||||
assert entry.unique_id == mock_config_entry.entry_id
|
||||
|
||||
|
||||
async def test_season_local_midnight(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test that season changes at local midnight, not UTC."""
|
||||
await hass.config.async_set_time_zone("Australia/Sydney")
|
||||
hass.config.latitude = HEMISPHERE_SOUTHERN["homeassistant"]["latitude"]
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
hass.config_entries.async_update_entry(
|
||||
mock_config_entry,
|
||||
unique_id=TYPE_METEOROLOGICAL,
|
||||
data={CONF_TYPE: TYPE_METEOROLOGICAL},
|
||||
)
|
||||
|
||||
sydney_tz = ZoneInfo("Australia/Sydney")
|
||||
|
||||
# The day before autumn starts, at 23:59:59 local time (summer)
|
||||
day_before = datetime(2017, 2, 28, 23, 59, 59, tzinfo=sydney_tz)
|
||||
|
||||
with freeze_time(day_before):
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("sensor.season")
|
||||
assert state
|
||||
assert state.state == STATE_SUMMER
|
||||
|
||||
# Exactly midnight local time (autumn)
|
||||
midnight = datetime(2017, 3, 1, 0, 0, 0, tzinfo=sydney_tz)
|
||||
|
||||
with freeze_time(midnight):
|
||||
await async_update_entity(hass, "sensor.season")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("sensor.season")
|
||||
assert state
|
||||
assert state.state == STATE_AUTUMN
|
||||
|
||||
Reference in New Issue
Block a user