2024-10-08 07:16:51 +00:00
|
|
|
"""Module for io device timezone lookups."""
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
import logging
|
2024-10-15 07:59:25 +00:00
|
|
|
from datetime import datetime, timedelta, tzinfo
|
|
|
|
from typing import cast
|
|
|
|
from zoneinfo import ZoneInfo
|
2024-10-08 07:16:51 +00:00
|
|
|
|
2024-10-08 11:21:01 +00:00
|
|
|
from ..cachedzoneinfo import CachedZoneInfo
|
2024-10-08 07:16:51 +00:00
|
|
|
|
|
|
|
_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
|
|
|
|
)
|
2024-10-08 11:21:01 +00:00
|
|
|
return await CachedZoneInfo.get_cached_zone_info("Etc/UTC")
|
2024-10-08 07:16:51 +00:00
|
|
|
|
|
|
|
name = TIMEZONE_INDEX[index]
|
2024-10-08 11:21:01 +00:00
|
|
|
return await CachedZoneInfo.get_cached_zone_info(name)
|
2024-10-08 07:16:51 +00:00
|
|
|
|
|
|
|
|
2024-10-15 07:59:25 +00:00
|
|
|
async def get_timezone_index(tzone: tzinfo) -> int:
|
2024-10-08 07:16:51 +00:00
|
|
|
"""Return the iot firmware index for a valid IANA timezone key."""
|
2024-10-15 07:59:25 +00:00
|
|
|
if isinstance(tzone, ZoneInfo):
|
|
|
|
name = tzone.key
|
|
|
|
rev = {val: key for key, val in TIMEZONE_INDEX.items()}
|
|
|
|
if name in rev:
|
|
|
|
return rev[name]
|
2024-10-08 07:16:51 +00:00
|
|
|
|
|
|
|
for i in range(110):
|
2024-10-15 07:59:25 +00:00
|
|
|
if _is_same_timezone(tzone, await get_timezone(i)):
|
2024-10-08 07:16:51 +00:00
|
|
|
return i
|
|
|
|
raise ValueError("Device does not support timezone %s", name)
|
|
|
|
|
|
|
|
|
2024-10-15 07:59:25 +00:00
|
|
|
async def get_matching_timezones(tzone: tzinfo) -> list[str]:
|
|
|
|
"""Return the iot firmware index for a valid IANA timezone key."""
|
|
|
|
matches = []
|
|
|
|
if isinstance(tzone, ZoneInfo):
|
|
|
|
name = tzone.key
|
|
|
|
vals = {val for val in TIMEZONE_INDEX.values()}
|
|
|
|
if name in vals:
|
|
|
|
matches.append(name)
|
|
|
|
|
|
|
|
for i in range(110):
|
|
|
|
fw_tz = await get_timezone(i)
|
|
|
|
if _is_same_timezone(tzone, fw_tz):
|
|
|
|
match_key = cast(ZoneInfo, fw_tz).key
|
|
|
|
if match_key not in matches:
|
|
|
|
matches.append(match_key)
|
|
|
|
return matches
|
|
|
|
|
|
|
|
|
|
|
|
def _is_same_timezone(tzone1: tzinfo, tzone2: tzinfo) -> bool:
|
|
|
|
"""Return true if the timezones have the same utcffset and dst offset.
|
|
|
|
|
|
|
|
Iot devices only support a limited static list of IANA timezones; this is used to
|
|
|
|
check if a static timezone matches the same utc offset and dst settings.
|
|
|
|
"""
|
|
|
|
now = datetime.now()
|
|
|
|
start_day = datetime(now.year, 1, 1, 12)
|
|
|
|
for i in range(365):
|
|
|
|
the_day = start_day + timedelta(days=i)
|
|
|
|
if tzone1.utcoffset(the_day) != tzone2.utcoffset(the_day):
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2024-10-08 07:16:51 +00:00
|
|
|
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",
|
|
|
|
}
|