Parse AirNow observation timezone correctly (#122006)

Parse observation timezone correctly

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Sean Chen 2024-09-22 07:44:53 -05:00 committed by GitHub
parent bd3efe57f7
commit 705af35dd6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 42 additions and 19 deletions

View File

@ -14,10 +14,32 @@ ATTR_API_POLLUTANT = "Pollutant"
ATTR_API_REPORT_DATE = "DateObserved" ATTR_API_REPORT_DATE = "DateObserved"
ATTR_API_REPORT_HOUR = "HourObserved" ATTR_API_REPORT_HOUR = "HourObserved"
ATTR_API_REPORT_TZ = "LocalTimeZone" ATTR_API_REPORT_TZ = "LocalTimeZone"
ATTR_API_REPORT_TZINFO = "LocalTimeZoneInfo"
ATTR_API_STATE = "StateCode" ATTR_API_STATE = "StateCode"
ATTR_API_STATION = "ReportingArea" ATTR_API_STATION = "ReportingArea"
ATTR_API_STATION_LATITUDE = "Latitude" ATTR_API_STATION_LATITUDE = "Latitude"
ATTR_API_STATION_LONGITUDE = "Longitude" ATTR_API_STATION_LONGITUDE = "Longitude"
DEFAULT_NAME = "AirNow" DEFAULT_NAME = "AirNow"
DOMAIN = "airnow" DOMAIN = "airnow"
SECONDS_PER_HOUR = 3600
# AirNow seems to only use standard time zones,
# but we include daylight savings for completeness/futureproofing.
US_TZ_OFFSETS = {
"HST": -10 * SECONDS_PER_HOUR,
"HDT": -9 * SECONDS_PER_HOUR,
# AirNow returns AKT instead of AKST or AKDT, use standard
"AKT": -9 * SECONDS_PER_HOUR,
"AKST": -9 * SECONDS_PER_HOUR,
"AKDT": -8 * SECONDS_PER_HOUR,
"PST": -8 * SECONDS_PER_HOUR,
"PDT": -7 * SECONDS_PER_HOUR,
"MST": -7 * SECONDS_PER_HOUR,
"MDT": -6 * SECONDS_PER_HOUR,
"CST": -6 * SECONDS_PER_HOUR,
"CDT": -5 * SECONDS_PER_HOUR,
"EST": -5 * SECONDS_PER_HOUR,
"EDT": -4 * SECONDS_PER_HOUR,
"AST": -4 * SECONDS_PER_HOUR,
"ADT": -3 * SECONDS_PER_HOUR,
}

View File

@ -12,7 +12,6 @@ from pyairnow.errors import AirNowError
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util import dt as dt_util
from .const import ( from .const import (
ATTR_API_AQI, ATTR_API_AQI,
@ -27,7 +26,6 @@ from .const import (
ATTR_API_REPORT_DATE, ATTR_API_REPORT_DATE,
ATTR_API_REPORT_HOUR, ATTR_API_REPORT_HOUR,
ATTR_API_REPORT_TZ, ATTR_API_REPORT_TZ,
ATTR_API_REPORT_TZINFO,
ATTR_API_STATE, ATTR_API_STATE,
ATTR_API_STATION, ATTR_API_STATION,
ATTR_API_STATION_LATITUDE, ATTR_API_STATION_LATITUDE,
@ -98,9 +96,7 @@ class AirNowDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
# Copy Report Details # Copy Report Details
data[ATTR_API_REPORT_DATE] = obv[ATTR_API_REPORT_DATE] data[ATTR_API_REPORT_DATE] = obv[ATTR_API_REPORT_DATE]
data[ATTR_API_REPORT_HOUR] = obv[ATTR_API_REPORT_HOUR] data[ATTR_API_REPORT_HOUR] = obv[ATTR_API_REPORT_HOUR]
data[ATTR_API_REPORT_TZINFO] = await dt_util.async_get_time_zone( data[ATTR_API_REPORT_TZ] = obv[ATTR_API_REPORT_TZ]
obv[ATTR_API_REPORT_TZ]
)
# Copy Station Details # Copy Station Details
data[ATTR_API_STATE] = obv[ATTR_API_STATE] data[ATTR_API_STATE] = obv[ATTR_API_STATE]

View File

@ -4,9 +4,10 @@ from __future__ import annotations
from collections.abc import Callable from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime
from typing import Any from typing import Any
from dateutil import parser
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
SensorDeviceClass, SensorDeviceClass,
SensorEntity, SensorEntity,
@ -34,12 +35,13 @@ from .const import (
ATTR_API_PM25, ATTR_API_PM25,
ATTR_API_REPORT_DATE, ATTR_API_REPORT_DATE,
ATTR_API_REPORT_HOUR, ATTR_API_REPORT_HOUR,
ATTR_API_REPORT_TZINFO, ATTR_API_REPORT_TZ,
ATTR_API_STATION, ATTR_API_STATION,
ATTR_API_STATION_LATITUDE, ATTR_API_STATION_LATITUDE,
ATTR_API_STATION_LONGITUDE, ATTR_API_STATION_LONGITUDE,
DEFAULT_NAME, DEFAULT_NAME,
DOMAIN, DOMAIN,
US_TZ_OFFSETS,
) )
ATTRIBUTION = "Data provided by AirNow" ATTRIBUTION = "Data provided by AirNow"
@ -69,6 +71,18 @@ def station_extra_attrs(data: dict[str, Any]) -> dict[str, Any]:
return {} return {}
def aqi_extra_attrs(data: dict[str, Any]) -> dict[str, Any]:
"""Process extra attributes for main AQI sensor."""
return {
ATTR_DESCR: data[ATTR_API_AQI_DESCRIPTION],
ATTR_LEVEL: data[ATTR_API_AQI_LEVEL],
ATTR_TIME: parser.parse(
f"{data[ATTR_API_REPORT_DATE]} {data[ATTR_API_REPORT_HOUR]}:00 {data[ATTR_API_REPORT_TZ]}",
tzinfos=US_TZ_OFFSETS,
).isoformat(),
}
SENSOR_TYPES: tuple[AirNowEntityDescription, ...] = ( SENSOR_TYPES: tuple[AirNowEntityDescription, ...] = (
AirNowEntityDescription( AirNowEntityDescription(
key=ATTR_API_AQI, key=ATTR_API_AQI,
@ -76,16 +90,7 @@ SENSOR_TYPES: tuple[AirNowEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.AQI, device_class=SensorDeviceClass.AQI,
value_fn=lambda data: data.get(ATTR_API_AQI), value_fn=lambda data: data.get(ATTR_API_AQI),
extra_state_attributes_fn=lambda data: { extra_state_attributes_fn=aqi_extra_attrs,
ATTR_DESCR: data[ATTR_API_AQI_DESCRIPTION],
ATTR_LEVEL: data[ATTR_API_AQI_LEVEL],
ATTR_TIME: datetime.strptime(
f"{data[ATTR_API_REPORT_DATE]} {data[ATTR_API_REPORT_HOUR]}",
"%Y-%m-%d %H",
)
.replace(tzinfo=data[ATTR_API_REPORT_TZINFO])
.isoformat(),
},
), ),
AirNowEntityDescription( AirNowEntityDescription(
key=ATTR_API_PM10, key=ATTR_API_PM10,

View File

@ -8,7 +8,7 @@
'DateObserved': '2020-12-20', 'DateObserved': '2020-12-20',
'HourObserved': 15, 'HourObserved': 15,
'Latitude': '**REDACTED**', 'Latitude': '**REDACTED**',
'LocalTimeZoneInfo': 'PST', 'LocalTimeZone': 'PST',
'Longitude': '**REDACTED**', 'Longitude': '**REDACTED**',
'O3': 0.048, 'O3': 0.048,
'PM10': 12, 'PM10': 12,