meteocat 0.1.46 → 0.1.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yml +7 -1
- package/CHANGELOG.md +10 -0
- package/custom_components/meteocat/__init__.py +1 -1
- package/custom_components/meteocat/coordinator.py +65 -31
- package/custom_components/meteocat/manifest.json +1 -1
- package/custom_components/meteocat/sensor.py +29 -1
- package/custom_components/meteocat/version.py +1 -1
- package/package.json +1 -1
- package/pyproject.toml +1 -1
|
@@ -24,8 +24,14 @@ jobs:
|
|
|
24
24
|
# Paso 3: Instalar dependencias necesarias
|
|
25
25
|
- name: Install dependencies
|
|
26
26
|
run: npm ci
|
|
27
|
+
|
|
28
|
+
# Paso 4: Configurar el autor de Git
|
|
29
|
+
- name: Configure Git author
|
|
30
|
+
run: |
|
|
31
|
+
git config user.name "semantic-release-bot"
|
|
32
|
+
git config user.email "jdcuartero@yahoo.es"
|
|
27
33
|
|
|
28
|
-
# Paso
|
|
34
|
+
# Paso 5: Ejecutar semantic-release
|
|
29
35
|
- name: Run semantic-release
|
|
30
36
|
env:
|
|
31
37
|
GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
## [0.1.47](https://github.com/figorr/meteocat/compare/v0.1.46...v0.1.47) (2025-01-02)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* 0.1.47 ([03d1d08](https://github.com/figorr/meteocat/commit/03d1d08dd837dc47d8f2062ba561be706e1d8b05))
|
|
7
|
+
* convert to local time from UTC ([0dfe9f9](https://github.com/figorr/meteocat/commit/0dfe9f9ef7409bcd23d839963076aff14921f114))
|
|
8
|
+
* fix semantic-release-bot ([754f18b](https://github.com/figorr/meteocat/commit/754f18b2c0378c704eb255259192655b76100c43))
|
|
9
|
+
* fix timestamp sensor from UTC to local time Europe/Madrid ([e64539a](https://github.com/figorr/meteocat/commit/e64539a091adecfd4e23419c63bc54eda2293da3))
|
|
10
|
+
|
|
1
11
|
## [0.1.46](https://github.com/figorr/meteocat/compare/v0.1.45...v0.1.46) (2025-01-01)
|
|
2
12
|
|
|
3
13
|
|
|
@@ -25,7 +25,7 @@ from .const import DOMAIN, PLATFORMS
|
|
|
25
25
|
_LOGGER = logging.getLogger(__name__)
|
|
26
26
|
|
|
27
27
|
# Versión
|
|
28
|
-
__version__ = "0.1.
|
|
28
|
+
__version__ = "0.1.47"
|
|
29
29
|
|
|
30
30
|
def safe_remove(path: Path, is_folder: bool = False):
|
|
31
31
|
"""Elimina de forma segura un archivo o carpeta si existe."""
|
|
@@ -6,6 +6,7 @@ import aiofiles
|
|
|
6
6
|
import logging
|
|
7
7
|
import asyncio
|
|
8
8
|
from datetime import datetime, timedelta, timezone, time
|
|
9
|
+
from zoneinfo import ZoneInfo
|
|
9
10
|
from typing import Dict, Any
|
|
10
11
|
|
|
11
12
|
from homeassistant.core import HomeAssistant
|
|
@@ -80,6 +81,30 @@ async def load_json_from_file(input_file: str) -> dict:
|
|
|
80
81
|
_LOGGER.error("Error al decodificar JSON del archivo %s: %s", input_file, err)
|
|
81
82
|
return {}
|
|
82
83
|
|
|
84
|
+
# Cambiar UTC a la zona horaria local
|
|
85
|
+
def convert_to_local_time(utc_time: str, local_tz: str = "Europe/Madrid") -> datetime | None:
|
|
86
|
+
"""
|
|
87
|
+
Convierte una fecha/hora UTC en formato ISO 8601 a la zona horaria local especificada.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
utc_time (str): Fecha/hora en formato ISO 8601 (ejemplo: '2025-01-02T12:00:00Z').
|
|
91
|
+
local_tz (str): Zona horaria local en formato IANA (por defecto, 'Europe/Madrid').
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
datetime | None: Objeto datetime convertido a la zona horaria local, o None si hay un error.
|
|
95
|
+
"""
|
|
96
|
+
try:
|
|
97
|
+
utc_dt = datetime.fromisoformat(utc_time.replace("Z", "+00:00"))
|
|
98
|
+
local_dt = utc_dt.replace(tzinfo=ZoneInfo("UTC")).astimezone(ZoneInfo(local_tz))
|
|
99
|
+
return local_dt
|
|
100
|
+
except ValueError:
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
# Obtener la hora local en lugar de la hora UTC
|
|
104
|
+
def get_local_datetime(tz: str = "Europe/Madrid"):
|
|
105
|
+
"""Obtiene la fecha y hora local según la zona horaria proporcionada."""
|
|
106
|
+
return datetime.now(ZoneInfo(tz))
|
|
107
|
+
|
|
83
108
|
class MeteocatSensorCoordinator(DataUpdateCoordinator):
|
|
84
109
|
"""Coordinator para manejar la actualización de datos de los sensores."""
|
|
85
110
|
|
|
@@ -286,9 +311,9 @@ class MeteocatUviCoordinator(DataUpdateCoordinator):
|
|
|
286
311
|
data = json.loads(content)
|
|
287
312
|
|
|
288
313
|
# Validar la fecha del primer elemento superior a 1 día
|
|
289
|
-
first_date =
|
|
290
|
-
today =
|
|
291
|
-
current_time =
|
|
314
|
+
first_date = convert_to_local_time(data["uvi"][0].get("date"), "%Y-%m-%d").date()
|
|
315
|
+
today = get_local_datetime().date() # Usar la hora local
|
|
316
|
+
current_time = get_local_datetime().time()
|
|
292
317
|
|
|
293
318
|
# Log detallado
|
|
294
319
|
_LOGGER.info(
|
|
@@ -428,8 +453,8 @@ class MeteocatUviFileCoordinator(DataUpdateCoordinator):
|
|
|
428
453
|
|
|
429
454
|
def _get_uv_for_current_hour(self, raw_data):
|
|
430
455
|
"""Get UV data for the current hour."""
|
|
431
|
-
#
|
|
432
|
-
current_datetime =
|
|
456
|
+
# # Obtiene la fecha y hora locales
|
|
457
|
+
current_datetime = get_local_datetime()
|
|
433
458
|
current_date = current_datetime.strftime("%Y-%m-%d")
|
|
434
459
|
current_hour = current_datetime.hour
|
|
435
460
|
|
|
@@ -510,10 +535,10 @@ class MeteocatEntityCoordinator(DataUpdateCoordinator):
|
|
|
510
535
|
content = await f.read()
|
|
511
536
|
data = json.loads(content)
|
|
512
537
|
|
|
513
|
-
#
|
|
514
|
-
first_date =
|
|
515
|
-
today =
|
|
516
|
-
current_time =
|
|
538
|
+
# Convertir la fecha del primer día a la zona horaria local
|
|
539
|
+
first_date = convert_to_local_time(data["dies"][0]["data"]).date()
|
|
540
|
+
today = get_local_datetime().date() # Usar la hora local
|
|
541
|
+
current_time = get_local_datetime().time()
|
|
517
542
|
|
|
518
543
|
# Log detallado
|
|
519
544
|
_LOGGER.info(
|
|
@@ -552,6 +577,11 @@ class MeteocatEntityCoordinator(DataUpdateCoordinator):
|
|
|
552
577
|
hourly_data = await self._fetch_and_save_data(
|
|
553
578
|
self.meteocat_forecast.get_prediccion_horaria, self.hourly_file
|
|
554
579
|
)
|
|
580
|
+
|
|
581
|
+
# Convertir las fechas horarias a hora local
|
|
582
|
+
for day in hourly_data.get("dies", []):
|
|
583
|
+
for hour_data in day["variables"].get("temp", {}).get("valors", []):
|
|
584
|
+
hour_data["data"] = convert_to_local_time(hour_data["data"]).isoformat()
|
|
555
585
|
|
|
556
586
|
# Validar o actualizar datos diarios
|
|
557
587
|
daily_data = await self.validate_forecast_data(self.daily_file)
|
|
@@ -560,6 +590,10 @@ class MeteocatEntityCoordinator(DataUpdateCoordinator):
|
|
|
560
590
|
self.meteocat_forecast.get_prediccion_diaria, self.daily_file
|
|
561
591
|
)
|
|
562
592
|
|
|
593
|
+
# Convertir las fechas diarias a hora local
|
|
594
|
+
for day in daily_data.get("dies", []):
|
|
595
|
+
day["data"] = convert_to_local_time(day["data"]).isoformat()
|
|
596
|
+
|
|
563
597
|
return {"hourly": hourly_data, "daily": daily_data}
|
|
564
598
|
|
|
565
599
|
except asyncio.TimeoutError as err:
|
|
@@ -646,10 +680,10 @@ class HourlyForecastCoordinator(DataUpdateCoordinator):
|
|
|
646
680
|
if not data or "dies" not in data:
|
|
647
681
|
return False
|
|
648
682
|
|
|
649
|
-
now =
|
|
683
|
+
now = get_local_datetime()
|
|
650
684
|
for dia in data["dies"]:
|
|
651
685
|
for forecast in dia.get("variables", {}).get("estatCel", {}).get("valors", []):
|
|
652
|
-
forecast_time =
|
|
686
|
+
forecast_time = convert_to_local_time(forecast["data"])
|
|
653
687
|
if forecast_time >= now:
|
|
654
688
|
return True
|
|
655
689
|
|
|
@@ -675,7 +709,7 @@ class HourlyForecastCoordinator(DataUpdateCoordinator):
|
|
|
675
709
|
variables = dia.get("variables", {})
|
|
676
710
|
condition_code = next(
|
|
677
711
|
(item["valor"] for item in variables.get("estatCel", {}).get("valors", []) if
|
|
678
|
-
|
|
712
|
+
convert_to_local_time(item["data"]) == forecast_time),
|
|
679
713
|
-1,
|
|
680
714
|
)
|
|
681
715
|
|
|
@@ -704,10 +738,10 @@ class HourlyForecastCoordinator(DataUpdateCoordinator):
|
|
|
704
738
|
return []
|
|
705
739
|
|
|
706
740
|
forecasts = []
|
|
707
|
-
now =
|
|
741
|
+
now = get_local_datetime()
|
|
708
742
|
for dia in self.data["dies"]:
|
|
709
743
|
for forecast in dia.get("variables", {}).get("estatCel", {}).get("valors", []):
|
|
710
|
-
forecast_time =
|
|
744
|
+
forecast_time = convert_to_local_time(forecast["data"])
|
|
711
745
|
if forecast_time >= now:
|
|
712
746
|
forecasts.append(self.parse_hourly_forecast(dia, forecast_time))
|
|
713
747
|
return forecasts
|
|
@@ -727,7 +761,7 @@ class HourlyForecastCoordinator(DataUpdateCoordinator):
|
|
|
727
761
|
|
|
728
762
|
for valor in valores:
|
|
729
763
|
try:
|
|
730
|
-
data_hora =
|
|
764
|
+
data_hora = convert_to_local_time(valor["data"])
|
|
731
765
|
if data_hora == target_time:
|
|
732
766
|
return float(valor["valor"])
|
|
733
767
|
except (KeyError, ValueError) as e:
|
|
@@ -777,9 +811,9 @@ class DailyForecastCoordinator(DataUpdateCoordinator):
|
|
|
777
811
|
if not data or "dies" not in data or not data["dies"]:
|
|
778
812
|
return False
|
|
779
813
|
|
|
780
|
-
today =
|
|
814
|
+
today = get_local_datetime().date() # Usar la hora local
|
|
781
815
|
for dia in data["dies"]:
|
|
782
|
-
forecast_date =
|
|
816
|
+
forecast_date = convert_to_local_time(dia["data"], "Europe/Madrid").date()
|
|
783
817
|
if forecast_date >= today:
|
|
784
818
|
return True
|
|
785
819
|
|
|
@@ -797,10 +831,10 @@ class DailyForecastCoordinator(DataUpdateCoordinator):
|
|
|
797
831
|
data = json.loads(content)
|
|
798
832
|
|
|
799
833
|
# Filtrar días pasados
|
|
800
|
-
today =
|
|
834
|
+
today = get_local_datetime().date() # Usar la hora local
|
|
801
835
|
data["dies"] = [
|
|
802
836
|
dia for dia in data["dies"]
|
|
803
|
-
if
|
|
837
|
+
if convert_to_local_time(dia["data"], "Europe/Madrid").date() >= today
|
|
804
838
|
]
|
|
805
839
|
return data
|
|
806
840
|
except Exception as e:
|
|
@@ -813,9 +847,9 @@ class DailyForecastCoordinator(DataUpdateCoordinator):
|
|
|
813
847
|
if not self.data or "dies" not in self.data or not self.data["dies"]:
|
|
814
848
|
return None
|
|
815
849
|
|
|
816
|
-
today =
|
|
850
|
+
today = get_local_datetime().date() # Usar la hora local
|
|
817
851
|
for dia in self.data["dies"]:
|
|
818
|
-
forecast_date =
|
|
852
|
+
forecast_date = convert_to_local_time(dia["data"], "Europe/Madrid").date()
|
|
819
853
|
if forecast_date == today:
|
|
820
854
|
return dia
|
|
821
855
|
return None
|
|
@@ -827,7 +861,7 @@ class DailyForecastCoordinator(DataUpdateCoordinator):
|
|
|
827
861
|
condition = get_condition_from_code(int(condition_code))
|
|
828
862
|
|
|
829
863
|
forecast_data = {
|
|
830
|
-
"date":
|
|
864
|
+
"date": convert_to_local_time(dia["data"], "Europe/Madrid").date(),
|
|
831
865
|
"temperature_max": float(variables.get("tmax", {}).get("valor", 0.0)),
|
|
832
866
|
"temperature_min": float(variables.get("tmin", {}).get("valor", 0.0)),
|
|
833
867
|
"precipitation": float(variables.get("precipitacio", {}).get("valor", 0.0)),
|
|
@@ -907,7 +941,7 @@ class MeteocatConditionCoordinator(DataUpdateCoordinator):
|
|
|
907
941
|
def _get_condition_for_current_hour(self, raw_data):
|
|
908
942
|
"""Get condition data for the current hour."""
|
|
909
943
|
# Fecha y hora actual
|
|
910
|
-
current_datetime =
|
|
944
|
+
current_datetime = get_local_datetime() # Usar la zona horaria local
|
|
911
945
|
current_date = current_datetime.strftime("%Y-%m-%d")
|
|
912
946
|
current_hour = current_datetime.hour
|
|
913
947
|
|
|
@@ -915,7 +949,7 @@ class MeteocatConditionCoordinator(DataUpdateCoordinator):
|
|
|
915
949
|
for day in raw_data.get("dies", []):
|
|
916
950
|
if day["data"].startswith(current_date):
|
|
917
951
|
for value in day["variables"]["estatCel"]["valors"]:
|
|
918
|
-
data_hour =
|
|
952
|
+
data_hour = convert_to_local_time(value["data"])
|
|
919
953
|
if data_hour.hour == current_hour:
|
|
920
954
|
codi_estatcel = value["valor"]
|
|
921
955
|
condition = get_condition_from_statcel(
|
|
@@ -986,9 +1020,9 @@ class MeteocatTempForecastCoordinator(DataUpdateCoordinator):
|
|
|
986
1020
|
if not data or "dies" not in data or not data["dies"]:
|
|
987
1021
|
return False
|
|
988
1022
|
|
|
989
|
-
today =
|
|
1023
|
+
today = get_local_datetime().date()
|
|
990
1024
|
for dia in data["dies"]:
|
|
991
|
-
forecast_date =
|
|
1025
|
+
forecast_date = convert_to_local_time(dia["data"]).date() # Convertir a hora local
|
|
992
1026
|
if forecast_date >= today:
|
|
993
1027
|
return True
|
|
994
1028
|
|
|
@@ -1006,10 +1040,10 @@ class MeteocatTempForecastCoordinator(DataUpdateCoordinator):
|
|
|
1006
1040
|
data = json.loads(content)
|
|
1007
1041
|
|
|
1008
1042
|
# Filtrar días pasados
|
|
1009
|
-
today =
|
|
1043
|
+
today = get_local_datetime().date() # Usar la hora local
|
|
1010
1044
|
data["dies"] = [
|
|
1011
1045
|
dia for dia in data["dies"]
|
|
1012
|
-
if
|
|
1046
|
+
if convert_to_local_time(dia["data"]).date() >= today # Convertir a hora local
|
|
1013
1047
|
]
|
|
1014
1048
|
|
|
1015
1049
|
# Usar datos de temperatura del día actual si están disponibles
|
|
@@ -1027,9 +1061,9 @@ class MeteocatTempForecastCoordinator(DataUpdateCoordinator):
|
|
|
1027
1061
|
if not data or "dies" not in data or not data["dies"]:
|
|
1028
1062
|
return None
|
|
1029
1063
|
|
|
1030
|
-
today =
|
|
1064
|
+
today = get_local_datetime().date() # Usar la hora local
|
|
1031
1065
|
for dia in data["dies"]:
|
|
1032
|
-
forecast_date =
|
|
1066
|
+
forecast_date = convert_to_local_time(dia["data"]).date() # Convertir a hora local
|
|
1033
1067
|
if forecast_date == today:
|
|
1034
1068
|
return dia
|
|
1035
1069
|
return None
|
|
@@ -1039,7 +1073,7 @@ class MeteocatTempForecastCoordinator(DataUpdateCoordinator):
|
|
|
1039
1073
|
variables = dia.get("variables", {})
|
|
1040
1074
|
|
|
1041
1075
|
temp_forecast_data = {
|
|
1042
|
-
"date":
|
|
1076
|
+
"date": convert_to_local_time(dia["data"]), # Fecha convertida a hora local
|
|
1043
1077
|
"max_temp_forecast": float(variables.get("tmax", {}).get("valor", 0.0)),
|
|
1044
1078
|
"min_temp_forecast": float(variables.get("tmin", {}).get("valor", 0.0)),
|
|
1045
1079
|
}
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from datetime import datetime, timezone, time
|
|
5
|
+
from zoneinfo import ZoneInfo
|
|
5
6
|
import logging
|
|
6
7
|
from homeassistant.helpers.entity import (
|
|
7
8
|
DeviceInfo,
|
|
@@ -327,6 +328,29 @@ async def async_setup_entry(hass, entry, async_add_entities: AddEntitiesCallback
|
|
|
327
328
|
if description.key == UVI_FILE_STATUS
|
|
328
329
|
)
|
|
329
330
|
|
|
331
|
+
# Cambiar UTC a la zona horaria local
|
|
332
|
+
def convert_to_local_time(utc_time: str, local_tz: str = "Europe/Madrid") -> datetime | None:
|
|
333
|
+
"""
|
|
334
|
+
Convierte una fecha/hora UTC en formato ISO 8601 a la zona horaria local especificada.
|
|
335
|
+
|
|
336
|
+
Args:
|
|
337
|
+
utc_time (str): Fecha/hora en formato ISO 8601 (ejemplo: '2025-01-02T12:00:00Z').
|
|
338
|
+
local_tz (str): Zona horaria local en formato IANA (por defecto, 'Europe/Madrid').
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
datetime | None: Objeto datetime convertido a la zona horaria local, o None si hay un error.
|
|
342
|
+
"""
|
|
343
|
+
try:
|
|
344
|
+
# Convertir la cadena UTC a un objeto datetime
|
|
345
|
+
utc_dt = datetime.fromisoformat(utc_time.replace("Z", "+00:00"))
|
|
346
|
+
|
|
347
|
+
# Convertir a la zona horaria local usando ZoneInfo
|
|
348
|
+
local_dt = utc_dt.replace(tzinfo=ZoneInfo("UTC")).astimezone(ZoneInfo(local_tz))
|
|
349
|
+
|
|
350
|
+
return local_dt
|
|
351
|
+
except ValueError:
|
|
352
|
+
return None
|
|
353
|
+
|
|
330
354
|
class MeteocatStaticSensor(CoordinatorEntity[MeteocatStaticSensorCoordinator], SensorEntity):
|
|
331
355
|
"""Representation of a static Meteocat sensor."""
|
|
332
356
|
STATIC_KEYS = {TOWN_NAME, TOWN_ID, STATION_NAME, STATION_ID}
|
|
@@ -633,9 +657,13 @@ class MeteocatSensor(CoordinatorEntity[MeteocatSensorCoordinator], SensorEntity)
|
|
|
633
657
|
if raw_timestamp:
|
|
634
658
|
# Convertir el timestamp a un objeto datetime
|
|
635
659
|
try:
|
|
636
|
-
|
|
660
|
+
# Convertimos raw_timestamp a hora local
|
|
661
|
+
local_time = convert_to_local_time(raw_timestamp)
|
|
662
|
+
_LOGGER.debug("Hora UTC: %s convertida a hora local: %s", raw_timestamp, local_time)
|
|
663
|
+
return local_time
|
|
637
664
|
except ValueError:
|
|
638
665
|
# Manejo de errores si el formato no es válido
|
|
666
|
+
_LOGGER.error(f"Error al convertir el timestamp '{raw_timestamp}' a hora local.")
|
|
639
667
|
return None
|
|
640
668
|
|
|
641
669
|
# Nuevo sensor para la precipitación acumulada
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# version.py
|
|
2
|
-
__version__ = "0.1.
|
|
2
|
+
__version__ = "0.1.47"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "meteocat",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.47",
|
|
4
4
|
"description": "[](https://opensource.org/licenses/Apache-2.0)\r [](https://pypi.org/project/meteocat)\r [](https://gitlab.com/figorr/meteocat/commits/master)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"directories": {
|