"""Implementation of time module.""" from __future__ import annotations from datetime import UTC, datetime, timedelta, timezone, tzinfo from typing import cast from zoneinfo import ZoneInfo, ZoneInfoNotFoundError from ...cachedzoneinfo import CachedZoneInfo from ...feature import Feature from ...interfaces import Time as TimeInterface from ..smartmodule import SmartModule class Time(SmartModule, TimeInterface): """Implementation of device_local_time.""" REQUIRED_COMPONENT = "time" QUERY_GETTER_NAME = "get_device_time" _timezone: tzinfo = UTC def _initialize_features(self) -> None: """Initialize features after the initial update.""" self._add_feature( Feature( device=self._device, id="device_time", name="Device time", attribute_getter="time", container=self, category=Feature.Category.Debug, type=Feature.Type.Sensor, ) ) async def _post_update_hook(self) -> None: """Perform actions after a device update.""" td = timedelta(minutes=cast(float, self.data.get("time_diff"))) if region := self.data.get("region"): try: # Zoneinfo will return a DST aware object tz: tzinfo = await CachedZoneInfo.get_cached_zone_info(region) except ZoneInfoNotFoundError: tz = timezone(td, region) else: # in case the device returns a blank region this will result in the # tzname being a UTC offset tz = timezone(td) self._timezone = tz @property def timezone(self) -> tzinfo: """Return current timezone.""" return self._timezone @property def time(self) -> datetime: """Return device's current datetime.""" return datetime.fromtimestamp( cast(float, self.data.get("timestamp")), tz=self.timezone, ) async def set_time(self, dt: datetime) -> dict: """Set device time.""" if not dt.tzinfo: timestamp = dt.replace(tzinfo=self.timezone).timestamp() utc_offset = cast(timedelta, self.timezone.utcoffset(dt)) else: timestamp = dt.timestamp() utc_offset = cast(timedelta, dt.utcoffset()) time_diff = utc_offset / timedelta(minutes=1) params: dict[str, int | str] = { "timestamp": int(timestamp), "time_diff": int(time_diff), } if tz := dt.tzinfo: region = tz.key if isinstance(tz, ZoneInfo) else dt.tzname() # tzname can return null if a simple timezone object is provided. if region: params["region"] = region return await self.call("set_device_time", params) async def _check_supported(self) -> bool: """Additional check to see if the module is supported by the device. Hub attached sensors report the time module but do return device time. """ if self._device._is_hub_child: return False return await super()._check_supported()