diff --git a/deepdiff/deephash.py b/deepdiff/deephash.py index e0d60da2..8f401502 100644 --- a/deepdiff/deephash.py +++ b/deepdiff/deephash.py @@ -683,6 +683,9 @@ def _hash(self, obj: Any, parent: str, parents_ids: frozenset = EMPTY_FROZENSET) elif isinstance(obj, datetime.date): result = self._prep_date(obj) + elif isinstance(obj, datetime.timezone): + result = KEY_TO_VAL_STR.format('timezone', obj.utcoffset(None)) + elif isinstance(obj, numbers): # type: ignore result = self._prep_number(obj) diff --git a/deepdiff/diff.py b/deepdiff/diff.py index 4c7a4e5c..05572f6d 100755 --- a/deepdiff/diff.py +++ b/deepdiff/diff.py @@ -2187,6 +2187,10 @@ def _diff(self, level, parents_ids=frozenset(), _original_type=None, local_tree= elif isinstance(level.t1, datetime.datetime): self._diff_datetime(level, local_tree=local_tree) + elif isinstance(level.t1, datetime.timezone): + if level.t1 != level.t2: + self._report_result('values_changed', level, local_tree=local_tree) + elif isinstance(level.t1, ipranges): self._diff_ipranges(level, local_tree=local_tree) diff --git a/tests/test_diff_datetime.py b/tests/test_diff_datetime.py index c3905291..c47bb2aa 100644 --- a/tests/test_diff_datetime.py +++ b/tests/test_diff_datetime.py @@ -1,9 +1,60 @@ import pytz -from datetime import date, datetime, time, timezone +import pytest +from datetime import date, datetime, time, timedelta, timezone from deepdiff import DeepDiff class TestDiffDatetime: + @pytest.mark.parametrize('old_offset,new_offset', [ + (timedelta(), timedelta(hours=1)), + (timedelta(hours=1), timedelta(hours=2)), + (timedelta(hours=-5), timedelta(hours=5, minutes=30)), + (timedelta(microseconds=1), timedelta(microseconds=2)), + ]) + @pytest.mark.parametrize('truncate_datetime', [None, 'day']) + def test_timezone_diff(self, old_offset, new_offset, truncate_datetime): + old = timezone(old_offset) + new = timezone(new_offset) + + assert DeepDiff(old, new, truncate_datetime=truncate_datetime) == { + 'values_changed': {'root': {'old_value': old, 'new_value': new}} + } + + def test_timezone_diff_in_dictionary(self): + old = timezone(timedelta(hours=1)) + new = timezone(timedelta(hours=2)) + + assert DeepDiff({'timezone': old}, {'timezone': new}) == { + 'values_changed': {"root['timezone']": {'old_value': old, 'new_value': new}} + } + + @pytest.mark.parametrize('ignore_order', [False, True]) + def test_timezone_diff_in_list(self, ignore_order): + old = timezone(timedelta(hours=1)) + new = timezone(timedelta(hours=2)) + + assert DeepDiff([old], [new], ignore_order=ignore_order) == { + 'values_changed': {'root[0]': {'old_value': old, 'new_value': new}} + } + + def test_timezone_diff_in_set(self): + old = timezone(timedelta(hours=1)) + new = timezone(timedelta(hours=2)) + + assert DeepDiff({old}, {new}) == { + 'set_item_removed': {f'root[{old}]'}, + 'set_item_added': {f'root[{new}]'}, + } + + @pytest.mark.parametrize('offset', [timedelta(), timedelta(hours=1), timedelta(hours=-5)]) + def test_timezone_equal_offsets_different_names(self, offset): + old = timezone(offset) + new = timezone(offset, 'custom name') + + assert DeepDiff(old, new) == {} + assert DeepDiff([old], [new], ignore_order=True) == {} + assert DeepDiff({old}, {new}) == {} + def test_datetime_diff(self): """Testing for the correct setting and usage of epsilon.""" d1 = {"a": datetime(2023, 7, 5, 10, 11, 12)} diff --git a/tests/test_hash.py b/tests/test_hash.py index f1e2e912..63b291ff 100755 --- a/tests/test_hash.py +++ b/tests/test_hash.py @@ -90,6 +90,32 @@ def test_datetime(self): b_hash = DeepHash(b) assert a_hash[a] == b_hash[b] + @pytest.mark.parametrize('offset', [ + datetime.timedelta(), + datetime.timedelta(hours=1), + datetime.timedelta(hours=-5, minutes=-30), + datetime.timedelta(microseconds=1), + ]) + def test_timezone_hash_uses_offset(self, offset): + zone = datetime.timezone(offset) + + assert DeepHashPrep(zone)[zone] == f'timezone:{offset}' + + def test_timezone_hash_distinguishes_offsets(self): + old = datetime.timezone(datetime.timedelta(hours=1)) + new = datetime.timezone(datetime.timedelta(hours=2)) + + assert DeepHash(old)[old] != DeepHash(new)[new] + + @pytest.mark.parametrize('offset', [ + datetime.timedelta(), datetime.timedelta(hours=1), datetime.timedelta(hours=-5), + ]) + def test_timezone_hash_ignores_name(self, offset): + old = datetime.timezone(offset) + new = datetime.timezone(offset, 'custom name') + + assert DeepHash(old)[old] == DeepHash(new)[new] + def test_date1(self): date = datetime.date(2024, 2, 1) date_hash = DeepHash(date)