mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-08-09 20:24:02 +00:00
Make iot time timezone aware (#1147)
Also makes on_since for iot devices use device time. Changes the return value for device.timezone to be tzinfo instead of a dict.
This commit is contained in:
178
kasa/iot/iottimezone.py
Normal file
178
kasa/iot/iottimezone.py
Normal file
@@ -0,0 +1,178 @@
|
||||
"""Module for io device timezone lookups."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import datetime, tzinfo
|
||||
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def get_timezone(index: int) -> tzinfo:
|
||||
"""Get the timezone from the index."""
|
||||
if index > 109:
|
||||
_LOGGER.error(
|
||||
"Unexpected index %s not configured as a timezone, defaulting to UTC", index
|
||||
)
|
||||
return await _CachedZoneInfo.get_cached_zone_info("Etc/UTC")
|
||||
|
||||
name = TIMEZONE_INDEX[index]
|
||||
return await _CachedZoneInfo.get_cached_zone_info(name)
|
||||
|
||||
|
||||
async def get_timezone_index(name: str) -> int:
|
||||
"""Return the iot firmware index for a valid IANA timezone key."""
|
||||
rev = {val: key for key, val in TIMEZONE_INDEX.items()}
|
||||
if name in rev:
|
||||
return rev[name]
|
||||
|
||||
# Try to find a supported timezone matching dst true/false
|
||||
zone = await _CachedZoneInfo.get_cached_zone_info(name)
|
||||
now = datetime.now()
|
||||
winter = datetime(now.year, 1, 1, 12)
|
||||
summer = datetime(now.year, 7, 1, 12)
|
||||
for i in range(110):
|
||||
configured_zone = await get_timezone(i)
|
||||
if zone.utcoffset(winter) == configured_zone.utcoffset(
|
||||
winter
|
||||
) and zone.utcoffset(summer) == configured_zone.utcoffset(summer):
|
||||
return i
|
||||
raise ValueError("Device does not support timezone %s", name)
|
||||
|
||||
|
||||
class _CachedZoneInfo(ZoneInfo):
|
||||
"""Cache zone info objects."""
|
||||
|
||||
_cache: dict[str, ZoneInfo] = {}
|
||||
|
||||
@classmethod
|
||||
async def get_cached_zone_info(cls, time_zone_str: str) -> ZoneInfo:
|
||||
"""Get a cached zone info object."""
|
||||
if cached := cls._cache.get(time_zone_str):
|
||||
return cached
|
||||
loop = asyncio.get_running_loop()
|
||||
zinfo = await loop.run_in_executor(None, _get_zone_info, time_zone_str)
|
||||
cls._cache[time_zone_str] = zinfo
|
||||
return zinfo
|
||||
|
||||
|
||||
def _get_zone_info(time_zone_str: str) -> ZoneInfo:
|
||||
"""Get a time zone object for the given time zone string."""
|
||||
return ZoneInfo(time_zone_str)
|
||||
|
||||
|
||||
TIMEZONE_INDEX = {
|
||||
0: "Etc/GMT+12",
|
||||
1: "Pacific/Samoa",
|
||||
2: "US/Hawaii",
|
||||
3: "US/Alaska",
|
||||
4: "Mexico/BajaNorte",
|
||||
5: "Etc/GMT+8",
|
||||
6: "PST8PDT",
|
||||
7: "US/Arizona",
|
||||
8: "America/Mazatlan",
|
||||
9: "MST",
|
||||
10: "MST7MDT",
|
||||
11: "Mexico/General",
|
||||
12: "Etc/GMT+6",
|
||||
13: "CST6CDT",
|
||||
14: "America/Monterrey",
|
||||
15: "Canada/Saskatchewan",
|
||||
16: "America/Bogota",
|
||||
17: "Etc/GMT+5",
|
||||
18: "EST",
|
||||
19: "America/Indiana/Indianapolis",
|
||||
20: "America/Caracas",
|
||||
21: "America/Asuncion",
|
||||
22: "Etc/GMT+4",
|
||||
23: "Canada/Atlantic",
|
||||
24: "America/Cuiaba",
|
||||
25: "Brazil/West",
|
||||
26: "America/Santiago",
|
||||
27: "Canada/Newfoundland",
|
||||
28: "America/Sao_Paulo",
|
||||
29: "America/Argentina/Buenos_Aires",
|
||||
30: "America/Cayenne",
|
||||
31: "America/Miquelon",
|
||||
32: "America/Montevideo",
|
||||
33: "Chile/Continental",
|
||||
34: "Etc/GMT+2",
|
||||
35: "Atlantic/Azores",
|
||||
36: "Atlantic/Cape_Verde",
|
||||
37: "Africa/Casablanca",
|
||||
38: "UCT",
|
||||
39: "GB",
|
||||
40: "Africa/Monrovia",
|
||||
41: "Europe/Amsterdam",
|
||||
42: "Europe/Belgrade",
|
||||
43: "Europe/Brussels",
|
||||
44: "Europe/Sarajevo",
|
||||
45: "Africa/Lagos",
|
||||
46: "Africa/Windhoek",
|
||||
47: "Asia/Amman",
|
||||
48: "Europe/Athens",
|
||||
49: "Asia/Beirut",
|
||||
50: "Africa/Cairo",
|
||||
51: "Asia/Damascus",
|
||||
52: "EET",
|
||||
53: "Africa/Harare",
|
||||
54: "Europe/Helsinki",
|
||||
55: "Asia/Istanbul",
|
||||
56: "Asia/Jerusalem",
|
||||
57: "Europe/Kaliningrad",
|
||||
58: "Africa/Tripoli",
|
||||
59: "Asia/Baghdad",
|
||||
60: "Asia/Kuwait",
|
||||
61: "Europe/Minsk",
|
||||
62: "Europe/Moscow",
|
||||
63: "Africa/Nairobi",
|
||||
64: "Asia/Tehran",
|
||||
65: "Asia/Muscat",
|
||||
66: "Asia/Baku",
|
||||
67: "Europe/Samara",
|
||||
68: "Indian/Mauritius",
|
||||
69: "Asia/Tbilisi",
|
||||
70: "Asia/Yerevan",
|
||||
71: "Asia/Kabul",
|
||||
72: "Asia/Ashgabat",
|
||||
73: "Asia/Yekaterinburg",
|
||||
74: "Asia/Karachi",
|
||||
75: "Asia/Kolkata",
|
||||
76: "Asia/Colombo",
|
||||
77: "Asia/Kathmandu",
|
||||
78: "Asia/Almaty",
|
||||
79: "Asia/Dhaka",
|
||||
80: "Asia/Novosibirsk",
|
||||
81: "Asia/Rangoon",
|
||||
82: "Asia/Bangkok",
|
||||
83: "Asia/Krasnoyarsk",
|
||||
84: "Asia/Chongqing",
|
||||
85: "Asia/Irkutsk",
|
||||
86: "Asia/Singapore",
|
||||
87: "Australia/Perth",
|
||||
88: "Asia/Taipei",
|
||||
89: "Asia/Ulaanbaatar",
|
||||
90: "Asia/Tokyo",
|
||||
91: "Asia/Seoul",
|
||||
92: "Asia/Yakutsk",
|
||||
93: "Australia/Adelaide",
|
||||
94: "Australia/Darwin",
|
||||
95: "Australia/Brisbane",
|
||||
96: "Australia/Canberra",
|
||||
97: "Pacific/Guam",
|
||||
98: "Australia/Hobart",
|
||||
99: "Antarctica/DumontDUrville",
|
||||
100: "Asia/Magadan",
|
||||
101: "Asia/Srednekolymsk",
|
||||
102: "Etc/GMT-11",
|
||||
103: "Asia/Anadyr",
|
||||
104: "Pacific/Auckland",
|
||||
105: "Etc/GMT-12",
|
||||
106: "Pacific/Fiji",
|
||||
107: "Etc/GMT-13",
|
||||
108: "Pacific/Apia",
|
||||
109: "Etc/GMT-14",
|
||||
}
|
Reference in New Issue
Block a user