diff --git a/src/pendulum/tz/local_timezone.py b/src/pendulum/tz/local_timezone.py index 9418b05d..5549a7e3 100644 --- a/src/pendulum/tz/local_timezone.py +++ b/src/pendulum/tz/local_timezone.py @@ -215,7 +215,9 @@ def _get_unix_timezone(_root: str = "/") -> Timezone: tzpath = Path(_root) / filename if tzpath.is_file(): with tzpath.open("rb") as f: - return Timezone.from_file(f) + tz = Timezone.from_file(f) + if tz.key is not None: + return tz warnings.warn( "Unable not find any timezone configuration, defaulting to UTC.", stacklevel=1 @@ -231,7 +233,7 @@ def _tz_from_env(tzenv: str) -> Timezone: # TZ specifies a file if os.path.isfile(tzenv): with open(tzenv, "rb") as f: - return Timezone.from_file(f) + return Timezone.from_file(f, key=tzenv) # TZ specifies a zoneinfo zone. try: diff --git a/tests/tz/test_local_timezone.py b/tests/tz/test_local_timezone.py index b8eff6de..09b22a7c 100644 --- a/tests/tz/test_local_timezone.py +++ b/tests/tz/test_local_timezone.py @@ -50,3 +50,27 @@ def test_unix_etc_timezone_dir(): tz = _get_unix_timezone(_root=root_path) assert tz.name == "Europe/Paris" + + +@pytest.mark.skipif( + sys.platform == "win32", reason="Test only available for UNIX systems" +) +def test_unix_localtime_regular_file_defaults_to_utc(): + # /etc/localtime as a bare copy of a zoneinfo file (no IANA key), as + # found in minimal containers / Slurm jobs, used to yield a Timezone + # with key=None, which crashed the Rust helpers (#989). It must now + # fall through to the UTC default with a named key. + import shutil + import tempfile + + local_path = os.path.join(os.path.split(__file__)[0], "..") + root_path = tempfile.mkdtemp() + os.makedirs(os.path.join(root_path, "etc"), exist_ok=True) + shutil.copy( + os.path.join(local_path, "fixtures", "tz", "Paris"), + os.path.join(root_path, "etc", "localtime"), + ) + tz = _get_unix_timezone(_root=root_path) + + assert tz.key is not None + assert tz.name == "UTC" diff --git a/tests/tz/test_timezone.py b/tests/tz/test_timezone.py index 6ce475d3..a8f88b72 100644 --- a/tests/tz/test_timezone.py +++ b/tests/tz/test_timezone.py @@ -487,7 +487,10 @@ def _paris_tzif_bytes() -> bytes: def test_from_file_without_a_key_can_be_pickled(): - # get_local_timezone()'s last-resort fallback builds a Timezone this same way, with no key. + # Timezone.from_file without a key yields a keyless Timezone (key is + # None); this is still supported for pickling / utcoffset (#989). The + # local-timezone resolver no longer returns such a value, defaulting + # to UTC instead (see test_unix_localtime_regular_file_defaults_to_utc). from io import BytesIO import pendulum.tz.timezone as timezone_module