simple-dwd-weatherforecast 3.0.4__py3-none-any.whl → 3.1.6__py3-none-any.whl
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.
- simple_dwd_weatherforecast/dwdforecast.py +80 -34
- {simple_dwd_weatherforecast-3.0.4.dist-info → simple_dwd_weatherforecast-3.1.6.dist-info}/METADATA +1 -1
- {simple_dwd_weatherforecast-3.0.4.dist-info → simple_dwd_weatherforecast-3.1.6.dist-info}/RECORD +7 -7
- {simple_dwd_weatherforecast-3.0.4.dist-info → simple_dwd_weatherforecast-3.1.6.dist-info}/WHEEL +1 -1
- tests/test_is_in_timerange.py +9 -0
- {simple_dwd_weatherforecast-3.0.4.dist-info → simple_dwd_weatherforecast-3.1.6.dist-info}/licenses/LICENCE +0 -0
- {simple_dwd_weatherforecast-3.0.4.dist-info → simple_dwd_weatherforecast-3.1.6.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,6 @@
|
|
1
1
|
import csv
|
2
2
|
import importlib
|
3
|
+
import importlib.resources
|
3
4
|
import json
|
4
5
|
import math
|
5
6
|
from collections import OrderedDict, defaultdict
|
@@ -299,27 +300,44 @@ class Weather:
|
|
299
300
|
return self.station["name"] # type: ignore
|
300
301
|
|
301
302
|
def is_in_timerange(self, timestamp: datetime):
|
303
|
+
# Convert timestamp to UTC for comparison with forecast data keys
|
304
|
+
timestamp_utc = (
|
305
|
+
timestamp.astimezone(timezone.utc)
|
306
|
+
if timestamp.tzinfo
|
307
|
+
else timestamp.replace(tzinfo=timezone.utc)
|
308
|
+
)
|
302
309
|
return (
|
303
310
|
list(self.forecast_data.keys())[0] # type: ignore
|
304
|
-
<= self.strip_to_hour_str(
|
311
|
+
<= self.strip_to_hour_str(timestamp_utc)
|
305
312
|
<= list(self.forecast_data.keys())[-1] # type: ignore
|
306
313
|
)
|
307
314
|
|
308
315
|
def is_in_timerange_day(self, timestamp: datetime):
|
316
|
+
# Get the caller's timezone
|
317
|
+
caller_tz = timestamp.tzinfo or timezone.utc
|
318
|
+
|
319
|
+
# Convert forecast data boundaries to caller's timezone for comparison
|
320
|
+
first_entry_utc = arrow.get(
|
321
|
+
list(self.forecast_data.keys())[0], # type: ignore
|
322
|
+
"YYYY-MM-DDTHH:mm:ss.SSSZ", # type: ignore
|
323
|
+
).datetime
|
324
|
+
last_entry_utc = arrow.get(
|
325
|
+
list(self.forecast_data.keys())[-1], # type: ignore
|
326
|
+
"YYYY-MM-DDTHH:mm:ss.SSSZ", # type: ignore
|
327
|
+
).datetime
|
328
|
+
|
329
|
+
first_entry_local = first_entry_utc.astimezone(caller_tz)
|
330
|
+
last_entry_local = last_entry_utc.astimezone(caller_tz)
|
331
|
+
timestamp_local = (
|
332
|
+
timestamp.astimezone(caller_tz)
|
333
|
+
if timestamp.tzinfo
|
334
|
+
else timestamp.replace(tzinfo=caller_tz)
|
335
|
+
)
|
336
|
+
|
309
337
|
return (
|
310
|
-
self.strip_to_day(
|
311
|
-
|
312
|
-
|
313
|
-
"YYYY-MM-DDTHH:mm:ss.SSSZ", # type: ignore
|
314
|
-
).datetime
|
315
|
-
)
|
316
|
-
<= self.strip_to_day(timestamp)
|
317
|
-
<= self.strip_to_day(
|
318
|
-
arrow.get(
|
319
|
-
list(self.forecast_data.keys())[-1], # type: ignore
|
320
|
-
"YYYY-MM-DDTHH:mm:ss.SSSZ", # type: ignore
|
321
|
-
).datetime
|
322
|
-
)
|
338
|
+
self.strip_to_day(first_entry_local)
|
339
|
+
<= self.strip_to_day(timestamp_local)
|
340
|
+
<= self.strip_to_day(last_entry_local)
|
323
341
|
)
|
324
342
|
|
325
343
|
def is_valid_timeframe(self, timeframe: int) -> bool:
|
@@ -338,7 +356,12 @@ class Weather:
|
|
338
356
|
if shouldUpdate:
|
339
357
|
self.update()
|
340
358
|
if self.is_in_timerange(timestamp):
|
341
|
-
|
359
|
+
timestamp_utc = (
|
360
|
+
timestamp.astimezone(timezone.utc)
|
361
|
+
if timestamp.tzinfo
|
362
|
+
else timestamp.replace(tzinfo=timezone.utc)
|
363
|
+
)
|
364
|
+
return self.forecast_data[self.strip_to_hour_str(timestamp_utc)][ # type: ignore
|
342
365
|
weatherDataType.value[0]
|
343
366
|
]
|
344
367
|
return None
|
@@ -348,9 +371,14 @@ class Weather:
|
|
348
371
|
self.update()
|
349
372
|
|
350
373
|
if self.is_in_timerange(timestamp):
|
374
|
+
timestamp_utc = (
|
375
|
+
timestamp.astimezone(timezone.utc)
|
376
|
+
if timestamp.tzinfo
|
377
|
+
else timestamp.replace(tzinfo=timezone.utc)
|
378
|
+
)
|
351
379
|
return str(
|
352
380
|
self.weather_codes[
|
353
|
-
self.forecast_data[self.strip_to_hour_str(
|
381
|
+
self.forecast_data[self.strip_to_hour_str(timestamp_utc)][ # type: ignore
|
354
382
|
WeatherDataType.CONDITION.value[0]
|
355
383
|
]
|
356
384
|
][0]
|
@@ -609,7 +637,13 @@ class Weather:
|
|
609
637
|
def get_timeframe_values(self, timestamp: datetime, timeframe: int):
|
610
638
|
"timestamp has to be checked prior to be in timerange"
|
611
639
|
result = []
|
612
|
-
|
640
|
+
# Convert to UTC for internal operations since forecast data is stored in UTC
|
641
|
+
timestamp_utc = (
|
642
|
+
timestamp.astimezone(timezone.utc)
|
643
|
+
if timestamp.tzinfo
|
644
|
+
else timestamp.replace(tzinfo=timezone.utc)
|
645
|
+
)
|
646
|
+
time_step = self.strip_to_hour(timestamp_utc)
|
613
647
|
for _ in range(timeframe):
|
614
648
|
hour_str = self.strip_to_hour_str(time_step)
|
615
649
|
time_step += timedelta(hours=1)
|
@@ -625,29 +659,41 @@ class Weather:
|
|
625
659
|
next(iter(self.forecast_data)), # type: ignore
|
626
660
|
"YYYY-MM-DDTHH:mm:ss.SSSZ", # type: ignore
|
627
661
|
).datetime # type: ignore
|
628
|
-
|
629
|
-
|
662
|
+
|
663
|
+
# Convert to caller's timezone for comparison
|
664
|
+
caller_tz = timestamp.tzinfo or timezone.utc
|
665
|
+
first_entry_local = first_entry_date.astimezone(caller_tz)
|
666
|
+
timestamp_local = (
|
667
|
+
timestamp.astimezone(caller_tz)
|
668
|
+
if timestamp.tzinfo
|
669
|
+
else timestamp.replace(tzinfo=caller_tz)
|
670
|
+
)
|
671
|
+
|
672
|
+
if timestamp_local.date() != first_entry_local.date():
|
673
|
+
# Start from midnight in caller's timezone and convert to UTC for lookup
|
674
|
+
time_step_local = timestamp_local.replace(
|
675
|
+
hour=0, minute=0, second=0, microsecond=0
|
676
|
+
)
|
630
677
|
for _ in range(24):
|
631
|
-
|
678
|
+
time_step_utc = time_step_local.astimezone(timezone.utc)
|
679
|
+
hour_str = self.strip_to_hour_str(time_step_utc)
|
632
680
|
if hour_str not in self.forecast_data: # type: ignore
|
633
681
|
break
|
634
682
|
result.append(self.forecast_data[hour_str]) # type: ignore
|
635
|
-
|
683
|
+
time_step_local += timedelta(hours=1)
|
636
684
|
else:
|
685
|
+
# Use the first entry date and calculate until end of day in caller's timezone
|
637
686
|
time_step = first_entry_date
|
638
|
-
|
639
|
-
|
640
|
-
time_step.month,
|
641
|
-
time_step.day,
|
642
|
-
0,
|
643
|
-
0,
|
644
|
-
0,
|
645
|
-
0,
|
646
|
-
timezone.utc,
|
687
|
+
endtime_local = first_entry_local.replace(
|
688
|
+
hour=0, minute=0, second=0, microsecond=0
|
647
689
|
) + timedelta(days=1)
|
648
|
-
|
649
|
-
|
650
|
-
|
690
|
+
endtime_utc = endtime_local.astimezone(timezone.utc)
|
691
|
+
|
692
|
+
while time_step < endtime_utc:
|
693
|
+
hour_str = self.strip_to_hour_str(time_step)
|
694
|
+
if hour_str not in self.forecast_data: # type: ignore
|
695
|
+
break
|
696
|
+
result.append(self.forecast_data[hour_str]) # type: ignore
|
651
697
|
time_step += timedelta(hours=1)
|
652
698
|
return result
|
653
699
|
|
@@ -900,7 +946,7 @@ class Weather:
|
|
900
946
|
}
|
901
947
|
|
902
948
|
def get_weather_report(self, shouldUpdate=False):
|
903
|
-
if
|
949
|
+
if shouldUpdate and self.region is not None:
|
904
950
|
self.update(with_report=True)
|
905
951
|
return self.weather_report
|
906
952
|
|
{simple_dwd_weatherforecast-3.0.4.dist-info → simple_dwd_weatherforecast-3.1.6.dist-info}/RECORD
RENAMED
@@ -1,9 +1,9 @@
|
|
1
1
|
simple_dwd_weatherforecast/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
simple_dwd_weatherforecast/dwdforecast.py,sha256=
|
2
|
+
simple_dwd_weatherforecast/dwdforecast.py,sha256=a3qePEdDOcQ3u9Yv68JW9IGYuY8FwgXlOKq6OeFd3yc,41126
|
3
3
|
simple_dwd_weatherforecast/dwdmap.py,sha256=3R3ptBwBqTaLT2edDhkf4n7XPcvErHRbqq0U9BI41uI,14515
|
4
4
|
simple_dwd_weatherforecast/stations.json,sha256=1u8qc2CT_rVy49SAlOicGixzHln6Y0FXevuFAz2maBw,838948
|
5
5
|
simple_dwd_weatherforecast/uv_stations.json,sha256=ADenYo-aR6qbf0UFkfYr72kkFzL9HyUKe4VQ23POGF8,2292
|
6
|
-
simple_dwd_weatherforecast-3.
|
6
|
+
simple_dwd_weatherforecast-3.1.6.dist-info/licenses/LICENCE,sha256=27UG7gteqvSWuZlsbIq2_OAbh7VyifGGl-1zpuUoBcw,1072
|
7
7
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
8
|
tests/dummy_data.py,sha256=qapeQxjh8kURf5b-9h4gUdJaqQJQDVdqfMtS2g5JReY,103406
|
9
9
|
tests/dummy_data_full.py,sha256=3HRGAQBq4xPYs4jLzoLV_UAe1CXFzOsVBOouDL_5RHw,104062
|
@@ -23,7 +23,7 @@ tests/test_get_timeframe_max.py,sha256=gas73o1Ferkk87M4Y3k4LZ-fXKehI2ulsxcQfIviD
|
|
23
23
|
tests/test_get_timeframe_min.py,sha256=WnFO36U_E-55TW7jR598lyLS4u-TOtXmufadJF7IWQs,2407
|
24
24
|
tests/test_get_timeframe_sum.py,sha256=hjHY9fvJUqi4o8AdFLPSDwi8GpXtjzLqsRzVMoUVjn4,2424
|
25
25
|
tests/test_get_timeframe_values.py,sha256=D9NJ98sZykLKAW1eZgLFuy0FMb4f_jUCtr4clzg65ms,7231
|
26
|
-
tests/test_is_in_timerange.py,sha256=
|
26
|
+
tests/test_is_in_timerange.py,sha256=1D0eMJ8GVs_KuMkIbdCoItv782b0Jy7CIvmW7QFXJbE,1776
|
27
27
|
tests/test_is_valid_timeframe.py,sha256=mXjeu3lUyixiBUEljirTf6qDM_FZFQGWa-Rk0NBMUDU,891
|
28
28
|
tests/test_location_tools.py,sha256=wto_XzVnARJQ-Qc83YAn0ahfMBSaOHpfzqAeKRDsNm8,1208
|
29
29
|
tests/test_map.py,sha256=X4zFmnoE7YzT6UudE0TtFmsBwhlrRP5C1p8K70_0yTg,5701
|
@@ -36,7 +36,7 @@ tests/test_update.py,sha256=AIzzHMxcjwQjeTB0l3YFgB7HkGDbuqiHofwy41mS0m4,7440
|
|
36
36
|
tests/test_update_hourly.py,sha256=7Zl8ml3FTdqw3_Qwr_Tz-sWTzypvrBWmxeig2Vwp_ZQ,1781
|
37
37
|
tests/test_uv_index.py,sha256=tr6wnOyHlXT1S3yp1oeHc4-Brmc-EMEdM4mtyrdpcHg,579
|
38
38
|
tests/test_weather.py,sha256=ZyX4ldUoJpJp7YpiNQwU6Od-nYRay-3qcaDJdNq8fhY,780
|
39
|
-
simple_dwd_weatherforecast-3.
|
40
|
-
simple_dwd_weatherforecast-3.
|
41
|
-
simple_dwd_weatherforecast-3.
|
42
|
-
simple_dwd_weatherforecast-3.
|
39
|
+
simple_dwd_weatherforecast-3.1.6.dist-info/METADATA,sha256=1ztrxCKzTc4bXhEN35kbU0d15IzWLn0sVvSj4rz8PqY,12869
|
40
|
+
simple_dwd_weatherforecast-3.1.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
41
|
+
simple_dwd_weatherforecast-3.1.6.dist-info/top_level.txt,sha256=iyEobUh14Tzitx39Oi8qm0NhBrnZovl_dNKtvLUkLEM,33
|
42
|
+
simple_dwd_weatherforecast-3.1.6.dist-info/RECORD,,
|
tests/test_is_in_timerange.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import unittest
|
2
2
|
from datetime import datetime
|
3
|
+
from zoneinfo import ZoneInfo
|
3
4
|
from simple_dwd_weatherforecast import dwdforecast
|
4
5
|
from dummy_data import parsed_data
|
5
6
|
|
@@ -32,3 +33,11 @@ class Weather_is_in_timerange(unittest.TestCase):
|
|
32
33
|
def test_is_in_timerange_day_future(self):
|
33
34
|
test_time = datetime(2102, 11, 5, 7, 30)
|
34
35
|
self.assertFalse(self.dwd_weather.is_in_timerange(test_time))
|
36
|
+
|
37
|
+
def test_is_in_timerange_timezone(self):
|
38
|
+
test_time = datetime(2020, 11, 6, 5, 0, tzinfo=ZoneInfo("Europe/Berlin"))
|
39
|
+
self.assertTrue(self.dwd_weather.is_in_timerange(test_time))
|
40
|
+
|
41
|
+
def test_is_not_in_timerange_timezone(self):
|
42
|
+
test_time = datetime(2020, 11, 6, 4, 0, tzinfo=ZoneInfo("Europe/Berlin"))
|
43
|
+
self.assertFalse(self.dwd_weather.is_in_timerange(test_time))
|
File without changes
|
File without changes
|