From f45417f3e0bb8c446d4149de5bb378ace2c2b6fc Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 17 Jul 2024 18:14:30 +0300 Subject: [PATCH] Avoid crashing on importing `babel.localtime` when TZ envvar is malformed When `pytz` is _not_ installed, importing `babel.localtime` could fail (repeatedly) when the `TZ` environment variable is malformed enough to be caught by `_validate_tzfile_path`, which might throw a certain `ValueError`. (When `pytz` is installed, it would raise an `UnknownTimeZoneError` we already catch and ignore for the same sort of input.) Fixes #1092 --- babel/localtime/_helpers.py | 14 ++++++++++++++ tests/test_localtime.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 tests/test_localtime.py diff --git a/babel/localtime/_helpers.py b/babel/localtime/_helpers.py index f27b31522..e7e67052c 100644 --- a/babel/localtime/_helpers.py +++ b/babel/localtime/_helpers.py @@ -2,7 +2,11 @@ import pytz except ModuleNotFoundError: pytz = None + +try: import zoneinfo +except ModuleNotFoundError: + zoneinfo = None def _get_tzinfo(tzenv: str): @@ -19,6 +23,16 @@ def _get_tzinfo(tzenv: str): else: try: return zoneinfo.ZoneInfo(tzenv) + except ValueError as ve: + # This is somewhat hacky, but since _validate_tzfile_path() doesn't + # raise a specific error type, we'll need to check the message to be + # one we know to be from that function. + # If so, we pretend it meant that the TZ didn't exist, for the benefit + # of `babel.localtime` catching the `LookupError` raised by + # `_get_tzinfo_or_raise()`. + # See https://github.com/python-babel/babel/issues/1092 + if str(ve).startswith("ZoneInfo keys "): + return None except zoneinfo.ZoneInfoNotFoundError: pass diff --git a/tests/test_localtime.py b/tests/test_localtime.py new file mode 100644 index 000000000..723ffa0b0 --- /dev/null +++ b/tests/test_localtime.py @@ -0,0 +1,29 @@ +import sys + +import pytest + +from babel.localtime import _helpers, get_localzone + + +@pytest.mark.skipif( + sys.platform == "win32", + reason="Issue 1092 is not applicable on Windows", +) +def test_issue_1092_without_pytz(monkeypatch): + pytest.importorskip("zoneinfo", reason="zoneinfo is not available") + monkeypatch.setenv("TZ", "/UTC") # Malformed timezone name. + # In case pytz _is_ also installed, we want to pretend it's not, so patch it out... + monkeypatch.setattr(_helpers, "pytz", None) + with pytest.raises(LookupError): + get_localzone() + + +@pytest.mark.skipif( + sys.platform == "win32", + reason="Issue 1092 is not applicable on Windows", +) +def test_issue_1092_with_pytz(monkeypatch): + pytest.importorskip("pytz", reason="pytz is not installed") + monkeypatch.setenv("TZ", "/UTC") # Malformed timezone name. + with pytest.raises(LookupError): + get_localzone()