1
0
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:
Cody
2026-03-17 13:09:25 -04:00
committed by GitHub
parent c05210683e
commit 5caf8a5b83
2 changed files with 86 additions and 31 deletions

View File

@@ -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)

View File

@@ -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