From ea8f33b7e4849e1aac16c2676578eb71aa134128 Mon Sep 17 00:00:00 2001 From: Roy Date: Sun, 22 Feb 2026 02:46:58 +0100 Subject: [PATCH 1/3] Replacing pytz with zoneinfo HA is moving away from pytz This will replace pytz with zoneinfo --- buienradar/buienradar_json.py | 37 +++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/buienradar/buienradar_json.py b/buienradar/buienradar_json.py index 25e7972..249dc41 100644 --- a/buienradar/buienradar_json.py +++ b/buienradar/buienradar_json.py @@ -1,9 +1,9 @@ """Buienradar library to get parsed weather data from buienradar.nl.""" import json import logging -from datetime import datetime # , timedelta +from datetime import datetime +from zoneinfo import ZoneInfo -import pytz import requests from vincenty import vincenty @@ -67,7 +67,7 @@ # "2019-02-03T19:20:00", __DATE_FORMAT = '%Y-%m-%dT%H:%M:%S' __TIMEZONE = 'Europe/Amsterdam' -__PYTZ_TIMEZONE = pytz.timezone(__TIMEZONE) +__ZI_TIMEZONE = ZoneInfo(__TIMEZONE) __ACTUAL = "actual" __STATIONMEASUREMENTS = "stationmeasurements" @@ -125,17 +125,34 @@ def __to_float1(val): """Convert val into float with 1 decimal.""" return __to_float(val, 1) +def __to_localdatetime(val: str | None) -> datetime | None: + """ + Convert an ISO-like timestamp string into a timezone-aware + datetime in Europe/Amsterdam. + + Expected format: + YYYY-MM-DDTHH:MM:SS + + Example: + "2019-02-03T19:20:00" + + Args: + val: Timestamp string. + + Returns: + Timezone-aware datetime in Europe/Amsterdam, + or None if parsing fails. + """ + if not isinstance(val, str) or not val: + return None -def __to_localdatetime(val): - """Convert val into a local datetime for tz Europe/Amsterdam.""" try: - # "timestamp": "2019-02-03T19:20:00", - dt = datetime.strptime(val, __DATE_FORMAT) - dt = __PYTZ_TIMEZONE.localize(dt) - return dt - except (AttributeError, ValueError, TypeError): + naive_dt = datetime.strptime(val, __DATE_FORMAT) + except ValueError: return None + # Attach timezone directly (zoneinfo handles DST correctly) + return naive_dt.replace(tzinfo=__ZI_TIMEZONE) def __getBarFC(pressure): """Parse the pressure and return FC (numerical).""" From 2e132b7d6e2948b2bbcf0214b974a666266c5667 Mon Sep 17 00:00:00 2001 From: Roy Date: Sun, 22 Feb 2026 02:48:37 +0100 Subject: [PATCH 2/3] Replacing pytz with zoneinfo HA is moving away from pytz This will replace pytz with ZoneInfo --- buienradar/buienradar_xml.py | 39 +++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/buienradar/buienradar_xml.py b/buienradar/buienradar_xml.py index 1232788..2eba4e8 100644 --- a/buienradar/buienradar_xml.py +++ b/buienradar/buienradar_xml.py @@ -2,10 +2,10 @@ import logging from datetime import datetime, timedelta -import pytz import requests import xmltodict from vincenty import vincenty +from zoneinfo import ZoneInfo from buienradar.constants import ( ATTRIBUTION, @@ -81,7 +81,7 @@ # buienradat date format: '07/26/2017 15:50:00' __DATE_FORMAT = '%m/%d/%Y %H:%M:%S' __TIMEZONE = 'Europe/Amsterdam' -__PYTZ_TIMEZONE = pytz.timezone(__TIMEZONE) +__ZI_TIMEZONE = ZoneInfo(__TIMEZONE) def __to_int(val): @@ -110,15 +110,34 @@ def __to_float1(val): return __to_float(val, 1) -def __to_localdatetime(val): - """Convert val into a local datetime for tz Europe/Amsterdam.""" +def __to_localdatetime(val: str | None) -> datetime | None: + """ + Convert an ISO-like timestamp string into a timezone-aware + datetime in Europe/Amsterdam. + + Expected format: + YYYY-MM-DDTHH:MM:SS + + Example: + "2019-02-03T19:20:00" + + Args: + val: Timestamp string. + + Returns: + Timezone-aware datetime in Europe/Amsterdam, + or None if parsing fails. + """ + if not isinstance(val, str) or not val: + return None + try: - dt = datetime.strptime(val, __DATE_FORMAT) - dt = __PYTZ_TIMEZONE.localize(dt) - return dt - except (ValueError, TypeError): + naive_dt = datetime.strptime(val, __DATE_FORMAT) + except ValueError: return None + # Attach timezone directly (zoneinfo handles DST correctly) + return naive_dt.replace(tzinfo=__ZI_TIMEZONE) # Sensor types are defined like so: # SENSOR_TYPES = { 'key': ['key in buienradar xml', conversion function], } @@ -294,7 +313,7 @@ def __parse_ws_data(content, latitude=52.091579, longitude=5.119734): return result -def __parse_precipfc_data(data, timeframe): +def __parse_precipfc_data(data, timeframe) -> dict[str, object]: """Parse the forecasted precipitation data.""" result = {AVERAGE: None, TOTAL: None, TIMEFRAME: None} @@ -400,7 +419,7 @@ def __parse_fc_data(fc_data): daysection = __BRDAYFC % daycnt if daysection in fc_data: tmpsect = fc_data[daysection] - fcdatetime = datetime.now(__PYTZ_TIMEZONE) + fcdatetime = datetime.now(__ZI_TIMEZONE) fcdatetime = fcdatetime.replace(hour=12, minute=0, second=0, From 5b2fa910d67d1924298afa752ebd7e985e561738 Mon Sep 17 00:00:00 2001 From: Roy Date: Sun, 22 Feb 2026 03:20:28 +0100 Subject: [PATCH 3/3] Update setup.py --- setup.py | 128 +++++++++++++------------------------------------------ 1 file changed, 30 insertions(+), 98 deletions(-) diff --git a/setup.py b/setup.py index 5a761b4..443da20 100755 --- a/setup.py +++ b/setup.py @@ -1,110 +1,42 @@ -"""Library and CLI tools for interacting with buienradar.""" - -import sys -from codecs import open -from os import path -from subprocess import check_output - +from pathlib import Path from setuptools import find_packages, setup -here = path.abspath(path.dirname(__file__)) - -if sys.version_info < (3, 4): - raise RuntimeError("This package requires at least Python 3.4") - -# Get the long description from the README file -with open(path.join(here, 'README.rst'), encoding='utf-8') as f: - long_description = f.read() - - -def version_from_git(): - """Acquire package version from current git tag.""" - return check_output(['git', 'describe', '--tags', '--abbrev=0'], - universal_newlines=True) - +BASE_DIR = Path(__file__).resolve().parent +LONG_DESCRIPTION = (BASE_DIR / "README.rst").read_text(encoding="utf-8") setup( - name='buienradar', - - version='1.0.9', - - description=__doc__, - long_description_content_type='text/x-rst', - long_description=long_description, - - # The project's main homepage. - url='https://github.com/mjj4791/python-buienradar', - - # Author details - author='mjj4791', - author_email='', - - # Choose your license - license='MIT', - - # See https://pypi.python.org/pypi?%3Aaction=list_classifiers + name="buienradar", + version="1.0.9", + description="Library and CLI tools for interacting with buienradar.", + long_description=LONG_DESCRIPTION, + long_description_content_type="text/x-rst", + url="https://github.com/mjj4791/python-buienradar", + author="mjj4791", + license="MIT", classifiers=[ - # How mature is this project? Common values are - # 3 - Alpha - # 4 - Beta - # 5 - Production/Stable - 'Development Status :: 5 - Production/Stable', - - # Indicate who your project is intended for - 'Intended Audience :: Developers', - - # Pick your license as you wish (should match "license" above) - 'License :: OSI Approved :: MIT License', - - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12' + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ], - - keywords='buienradar weather', - - packages=find_packages(exclude=['contrib', 'docs', 'tests']), - + keywords="buienradar weather", + packages=find_packages(exclude=("contrib", "docs", "tests")), + python_requires=">=3.9", install_requires=[ - 'docopt', - 'pytz', - 'requests', - 'xmltodict', - 'vincenty', + "tzdata; platform_system=='Windows'", + "docopt", + "requests", + "xmltodict", + "vincenty", ], - - # # List additional groups of dependencies here (e.g. development - # # dependencies). You can install these using the following syntax, - # # for example: - # # $ pip install -e .[dev,test] - # extras_require={ - # 'dev': ['check-manifest'], - # 'test': ['coverage'], - # }, - - # # If there are data files included in your packages that need to be - # # installed, specify them here. If using Python 2.6 or less, then these - # # have to be included in MANIFEST.in as well. - # package_data={ - # 'sample': ['package_data.dat'], - # }, - - # # Although 'package_data' is the preferred approach, in some case you may - # # need to place data files outside of your packages. See: - # # http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files # noqa - # # In this case, 'data_file' will be installed into '/my_data' - # data_files=[('my_data', ['data/data_file'])], - - # To provide executable scripts, use entry points in preference to the - # "scripts" keyword. Entry points provide cross-platform support and allow - # pip to create the appropriate form of executable for the target platform. entry_points={ - 'console_scripts': [ - 'buienradar=buienradar.__main__:main', + "console_scripts": [ + "buienradar=buienradar.__main__:main", ], }, )