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.
@@ -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(timestamp)
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
- arrow.get(
312
- list(self.forecast_data.keys())[0], # type: ignore
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
- return self.forecast_data[self.strip_to_hour_str(timestamp)][ # type: ignore
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(timestamp)][ # type: ignore
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
- time_step = self.strip_to_hour(timestamp)
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
- if timestamp.day != first_entry_date.day:
629
- time_step = self.strip_to_day(timestamp)
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
- hour_str = self.strip_to_hour_str(time_step)
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
- time_step += timedelta(hours=1)
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
- endtime = datetime(
639
- time_step.year,
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
- timediff = endtime - time_step
649
- for _ in range(round(timediff.total_seconds() / 3600)):
650
- result.append(self.forecast_data[self.strip_to_hour_str(time_step)]) # type: ignore
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 (shouldUpdate or self.weather_report is None) and self.region is not None:
949
+ if shouldUpdate and self.region is not None:
904
950
  self.update(with_report=True)
905
951
  return self.weather_report
906
952
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simple_dwd_weatherforecast
3
- Version: 3.0.4
3
+ Version: 3.1.6
4
4
  Summary: A simple tool to retrieve a weather forecast from DWD OpenData
5
5
  Home-page: https://github.com/FL550/simple_dwd_weatherforecast.git
6
6
  Author: Max Fermor
@@ -1,9 +1,9 @@
1
1
  simple_dwd_weatherforecast/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- simple_dwd_weatherforecast/dwdforecast.py,sha256=UJopfqrI13qV27nLgSS0GZY6_qPQ7S5ZWqiI-PGT9bo,39081
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.0.4.dist-info/licenses/LICENCE,sha256=27UG7gteqvSWuZlsbIq2_OAbh7VyifGGl-1zpuUoBcw,1072
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=3y88L3N73NxSTJ-_edx6OCnxHWKJWWFma98gjZvJDGg,1338
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.0.4.dist-info/METADATA,sha256=UinjW8wBvFNYCsBrVGP1bGBxiyXJz4cdYiXOGQlDCOc,12869
40
- simple_dwd_weatherforecast-3.0.4.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
41
- simple_dwd_weatherforecast-3.0.4.dist-info/top_level.txt,sha256=iyEobUh14Tzitx39Oi8qm0NhBrnZovl_dNKtvLUkLEM,33
42
- simple_dwd_weatherforecast-3.0.4.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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))