From d3ffbb8bc282a77bd6ae2be54a43d866ac167118 Mon Sep 17 00:00:00 2001 From: mishra-prince <88850888+mishra-prince@users.noreply.github.com> Date: Mon, 28 Sep 2026 12:42:27 +0000 Subject: [PATCH 1/2] Make Timezone and FixedTimezone hashable Both classes define __eq__ but not __hash__, which makes Python set __hash__ to None and the instances unhashable (a regression since 3.1.0 when __eq__ was added). Restore hashability by defining __hash__ consistently with __eq__ on both classes. Fixes #1008 --- src/pendulum/tz/timezone.py | 6 ++++++ tests/tz/test_timezone.py | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/pendulum/tz/timezone.py b/src/pendulum/tz/timezone.py index e46e13df1..6fcff5bde 100644 --- a/src/pendulum/tz/timezone.py +++ b/src/pendulum/tz/timezone.py @@ -69,6 +69,9 @@ def __new__(cls, key: str) -> Self: def __eq__(self, other: object) -> bool: return isinstance(other, Timezone) and self.key == other.key + def __hash__(self) -> int: + return hash((self.__class__, self.key)) + @property def name(self) -> str: return self.key @@ -178,6 +181,9 @@ def __init__(self, offset: int, name: str | None = None) -> None: def __eq__(self, other: object) -> bool: return isinstance(other, FixedTimezone) and self._offset == other._offset + def __hash__(self) -> int: + return hash((self.__class__, self._offset)) + @property def name(self) -> str: return self._name diff --git a/tests/tz/test_timezone.py b/tests/tz/test_timezone.py index 3f0901681..cef378419 100644 --- a/tests/tz/test_timezone.py +++ b/tests/tz/test_timezone.py @@ -47,6 +47,18 @@ def test_equality(): assert timezone("Europe/Paris") != timezone("Europe/Berlin") +def test_hashable(): + # Timezones are hashable and can be used as dict keys / set members. + # __eq__ and __hash__ must stay consistent: equal timezones hash equally. + assert hash(timezone("Europe/Paris")) == hash(timezone("Europe/Paris")) + assert timezone("Europe/Paris") in {timezone("Europe/Paris")} + assert len({timezone("Europe/Paris"), timezone("Europe/Berlin")}) == 2 + + assert hash(fixed_timezone(3600)) == hash(fixed_timezone(3600)) + assert fixed_timezone(3600) in {fixed_timezone(3600)} + assert len({fixed_timezone(3600), fixed_timezone(7200)}) == 2 + + def test_skipped_time_with_pre_rule(): dt = datetime(2013, 3, 31, 2, 30, 45, 123456, fold=0) tz = timezone("Europe/Paris") From 09853814711126a427c43cc3e52a46f1171a0e5d Mon Sep 17 00:00:00 2001 From: mishra-prince <88850888+mishra-prince@users.noreply.github.com> Date: Mon, 28 Sep 2026 15:16:37 +0000 Subject: [PATCH 2/2] Hash only the equality value to preserve eq/hash invariant __eq__ matches any Timezone/FixedTimezone with the same key/offset, including subclasses, so the hash must not depend on the concrete class. Hash the key (Timezone) and offset (FixedTimezone) directly. --- src/pendulum/tz/timezone.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pendulum/tz/timezone.py b/src/pendulum/tz/timezone.py index 6fcff5bde..ffc3c1304 100644 --- a/src/pendulum/tz/timezone.py +++ b/src/pendulum/tz/timezone.py @@ -70,7 +70,7 @@ def __eq__(self, other: object) -> bool: return isinstance(other, Timezone) and self.key == other.key def __hash__(self) -> int: - return hash((self.__class__, self.key)) + return hash(self.key) @property def name(self) -> str: @@ -182,7 +182,7 @@ def __eq__(self, other: object) -> bool: return isinstance(other, FixedTimezone) and self._offset == other._offset def __hash__(self) -> int: - return hash((self.__class__, self._offset)) + return hash(self._offset) @property def name(self) -> str: