simple-dwd-weatherforecast 3.0.5__tar.gz → 3.1.6__tar.gz
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-3.0.5/simple_dwd_weatherforecast.egg-info → simple_dwd_weatherforecast-3.1.6}/PKG-INFO +1 -1
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/setup.py +1 -1
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/simple_dwd_weatherforecast/dwdforecast.py +78 -33
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6/simple_dwd_weatherforecast.egg-info}/PKG-INFO +1 -1
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_is_in_timerange.py +9 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/LICENCE +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/README.md +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/setup.cfg +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/simple_dwd_weatherforecast/__init__.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/simple_dwd_weatherforecast/dwdmap.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/simple_dwd_weatherforecast/stations.json +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/simple_dwd_weatherforecast/uv_stations.json +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/simple_dwd_weatherforecast.egg-info/SOURCES.txt +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/simple_dwd_weatherforecast.egg-info/dependency_links.txt +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/simple_dwd_weatherforecast.egg-info/requires.txt +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/simple_dwd_weatherforecast.egg-info/top_level.txt +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/__init__.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/dummy_data.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/dummy_data_full.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/dummy_uv.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_get_daily_avg.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_get_daily_condition.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_get_daily_max.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_get_daily_min.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_get_daily_sum.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_get_day_values.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_get_forecast_condition.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_get_forecast_data.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_get_station_name.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_get_timeframe_avg.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_get_timeframe_condition.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_get_timeframe_max.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_get_timeframe_min.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_get_timeframe_sum.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_get_timeframe_values.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_is_valid_timeframe.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_location_tools.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_map.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_parsekml.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_region.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_reported_weather.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_station.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_stationsfile.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_update.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_update_hourly.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_uv_index.py +0 -0
- {simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_weather.py +0 -0
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
|
|
5
5
|
|
6
6
|
setuptools.setup(
|
7
7
|
name="simple_dwd_weatherforecast",
|
8
|
-
version="v3.
|
8
|
+
version="v3.1.6",
|
9
9
|
author="Max Fermor",
|
10
10
|
description="A simple tool to retrieve a weather forecast from DWD OpenData",
|
11
11
|
long_description=long_description,
|
@@ -300,27 +300,44 @@ class Weather:
|
|
300
300
|
return self.station["name"] # type: ignore
|
301
301
|
|
302
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
|
+
)
|
303
309
|
return (
|
304
310
|
list(self.forecast_data.keys())[0] # type: ignore
|
305
|
-
<= self.strip_to_hour_str(
|
311
|
+
<= self.strip_to_hour_str(timestamp_utc)
|
306
312
|
<= list(self.forecast_data.keys())[-1] # type: ignore
|
307
313
|
)
|
308
314
|
|
309
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
|
+
|
310
337
|
return (
|
311
|
-
self.strip_to_day(
|
312
|
-
|
313
|
-
|
314
|
-
"YYYY-MM-DDTHH:mm:ss.SSSZ", # type: ignore
|
315
|
-
).datetime
|
316
|
-
)
|
317
|
-
<= self.strip_to_day(timestamp)
|
318
|
-
<= self.strip_to_day(
|
319
|
-
arrow.get(
|
320
|
-
list(self.forecast_data.keys())[-1], # type: ignore
|
321
|
-
"YYYY-MM-DDTHH:mm:ss.SSSZ", # type: ignore
|
322
|
-
).datetime
|
323
|
-
)
|
338
|
+
self.strip_to_day(first_entry_local)
|
339
|
+
<= self.strip_to_day(timestamp_local)
|
340
|
+
<= self.strip_to_day(last_entry_local)
|
324
341
|
)
|
325
342
|
|
326
343
|
def is_valid_timeframe(self, timeframe: int) -> bool:
|
@@ -339,7 +356,12 @@ class Weather:
|
|
339
356
|
if shouldUpdate:
|
340
357
|
self.update()
|
341
358
|
if self.is_in_timerange(timestamp):
|
342
|
-
|
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
|
343
365
|
weatherDataType.value[0]
|
344
366
|
]
|
345
367
|
return None
|
@@ -349,9 +371,14 @@ class Weather:
|
|
349
371
|
self.update()
|
350
372
|
|
351
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
|
+
)
|
352
379
|
return str(
|
353
380
|
self.weather_codes[
|
354
|
-
self.forecast_data[self.strip_to_hour_str(
|
381
|
+
self.forecast_data[self.strip_to_hour_str(timestamp_utc)][ # type: ignore
|
355
382
|
WeatherDataType.CONDITION.value[0]
|
356
383
|
]
|
357
384
|
][0]
|
@@ -610,7 +637,13 @@ class Weather:
|
|
610
637
|
def get_timeframe_values(self, timestamp: datetime, timeframe: int):
|
611
638
|
"timestamp has to be checked prior to be in timerange"
|
612
639
|
result = []
|
613
|
-
|
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)
|
614
647
|
for _ in range(timeframe):
|
615
648
|
hour_str = self.strip_to_hour_str(time_step)
|
616
649
|
time_step += timedelta(hours=1)
|
@@ -626,29 +659,41 @@ class Weather:
|
|
626
659
|
next(iter(self.forecast_data)), # type: ignore
|
627
660
|
"YYYY-MM-DDTHH:mm:ss.SSSZ", # type: ignore
|
628
661
|
).datetime # type: ignore
|
629
|
-
|
630
|
-
|
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
|
+
)
|
631
677
|
for _ in range(24):
|
632
|
-
|
678
|
+
time_step_utc = time_step_local.astimezone(timezone.utc)
|
679
|
+
hour_str = self.strip_to_hour_str(time_step_utc)
|
633
680
|
if hour_str not in self.forecast_data: # type: ignore
|
634
681
|
break
|
635
682
|
result.append(self.forecast_data[hour_str]) # type: ignore
|
636
|
-
|
683
|
+
time_step_local += timedelta(hours=1)
|
637
684
|
else:
|
685
|
+
# Use the first entry date and calculate until end of day in caller's timezone
|
638
686
|
time_step = first_entry_date
|
639
|
-
|
640
|
-
|
641
|
-
time_step.month,
|
642
|
-
time_step.day,
|
643
|
-
0,
|
644
|
-
0,
|
645
|
-
0,
|
646
|
-
0,
|
647
|
-
timezone.utc,
|
687
|
+
endtime_local = first_entry_local.replace(
|
688
|
+
hour=0, minute=0, second=0, microsecond=0
|
648
689
|
) + timedelta(days=1)
|
649
|
-
|
650
|
-
|
651
|
-
|
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
|
652
697
|
time_step += timedelta(hours=1)
|
653
698
|
return result
|
654
699
|
|
{simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_is_in_timerange.py
RENAMED
@@ -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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/dummy_data_full.py
RENAMED
File without changes
|
File without changes
|
{simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_get_daily_avg.py
RENAMED
File without changes
|
File without changes
|
{simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_get_daily_max.py
RENAMED
File without changes
|
{simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_get_daily_min.py
RENAMED
File without changes
|
{simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_get_daily_sum.py
RENAMED
File without changes
|
{simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_get_day_values.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
{simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_get_station_name.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_location_tools.py
RENAMED
File without changes
|
File without changes
|
{simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_parsekml.py
RENAMED
File without changes
|
File without changes
|
{simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_reported_weather.py
RENAMED
File without changes
|
File without changes
|
{simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_stationsfile.py
RENAMED
File without changes
|
File without changes
|
{simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_update_hourly.py
RENAMED
File without changes
|
{simple_dwd_weatherforecast-3.0.5 → simple_dwd_weatherforecast-3.1.6}/tests/test_uv_index.py
RENAMED
File without changes
|
File without changes
|