From 14e8de39810d67c4fc0feda027bf5bc13a17d2fc Mon Sep 17 00:00:00 2001 From: Mobin Ghoveoud Date: Fri, 15 Nov 2024 22:52:14 +0330 Subject: [PATCH] feat(datetime): Update datetime comparison methods with convert to python datetime + Update methods + Update tests --- jdatetime/__init__.py | 123 ++++++--------------------------- tests/test_jdatetime.py | 148 +++++----------------------------------- 2 files changed, 36 insertions(+), 235 deletions(-) diff --git a/jdatetime/__init__.py b/jdatetime/__init__.py index 3442340..2872d82 100644 --- a/jdatetime/__init__.py +++ b/jdatetime/__init__.py @@ -86,10 +86,6 @@ def _format_time(hour, minute, second, microsecond, timespec='auto'): return fmt.format(hour, minute, second, microsecond) -def _base_cmp(x, y): - return 0 if x == y else 1 if x > y else -1 - - class time(py_datetime.time): def __repr__(self): return f"jdatetime.time({self.hour}, {self.minute}, {self.second})" @@ -1047,132 +1043,53 @@ def __eq__(self, other_datetime): if other_datetime is None: return False - if isinstance(other_datetime, py_datetime.datetime): - return self.__eq__(datetime.fromgregorian(datetime=other_datetime)) + if isinstance(other_datetime, datetime): + other_datetime = other_datetime.togregorian() - if not isinstance(other_datetime, datetime): + if not isinstance(other_datetime, py_datetime.datetime): return NotImplemented - if self.locale == other_datetime.locale: - return self._cmp(other_datetime, allow_mixed=True) == 0 - - return False + return self.togregorian() == other_datetime def __ge__(self, other_datetime): """x.__ge__(y) <==> x>=y""" - if isinstance(other_datetime, py_datetime.datetime): - return self.__ge__(datetime.fromgregorian(datetime=other_datetime)) + if isinstance(other_datetime, datetime): + other_datetime = other_datetime.togregorian() - if not isinstance(other_datetime, datetime): + if not isinstance(other_datetime, py_datetime.datetime): return NotImplemented - return self._cmp(other_datetime) >= 0 + return self.togregorian() >= other_datetime def __gt__(self, other_datetime): """x.__gt__(y) <==> x>y""" - if isinstance(other_datetime, py_datetime.datetime): - return self.__gt__(datetime.fromgregorian(datetime=other_datetime)) + if isinstance(other_datetime, datetime): + other_datetime = other_datetime.togregorian() - if not isinstance(other_datetime, datetime): + if not isinstance(other_datetime, py_datetime.datetime): return NotImplemented - return self._cmp(other_datetime) > 0 + return self.togregorian() > other_datetime def __le__(self, other_datetime): """x.__le__(y) <==> x<=y""" - if isinstance(other_datetime, py_datetime.datetime): - return self.__le__(datetime.fromgregorian(datetime=other_datetime)) + if isinstance(other_datetime, datetime): + other_datetime = other_datetime.togregorian() - if not isinstance(other_datetime, datetime): + if not isinstance(other_datetime, py_datetime.datetime): return NotImplemented - return self._cmp(other_datetime) <= 0 + return self.togregorian() <= other_datetime def __lt__(self, other_datetime): """x.__lt__(y) <==> x x!=y""" - if other_datetime is None: - return True - if isinstance(other_datetime, py_datetime.datetime): - return self.__ne__(datetime.fromgregorian(datetime=other_datetime)) - if not isinstance(other_datetime, datetime): + if not isinstance(other_datetime, py_datetime.datetime): return NotImplemented - return not self.__eq__(other_datetime) - - def _cmp(self, other, allow_mixed=False): - """ - Compare two datetime objects. - If allow_mixed is True, returns 2 for ambiguous times during DST transitions, - as needed for the __eq__ method. - """ - assert isinstance(other, datetime) - - self_tz = self.tzinfo - other_tz = other.tzinfo - self_offset = None - other_offset = None - - if self_tz is other_tz: - base_compare = True - else: - self_offset = self.utcoffset() - other_offset = other.utcoffset() - - # Assume that allow_mixed means that we are called from __eq__ and - # Return 2 for ambiguous times during DST transitions. - if allow_mixed: - if self_offset != self.replace(fold=not self.fold).utcoffset(): - return 2 - if other_offset != other.replace(fold=not other.fold).utcoffset(): - return 2 - - base_compare = self_offset == other_offset - - if base_compare: - return _base_cmp( - ( - self.year, - self.month, - self.day, - self.hour, - self.minute, - self.second, - self.microsecond - ), - ( - other.year, - other.month, - other.day, - other.hour, - other.minute, - other.second, - other.microsecond - ), - ) - - if self_offset is None or other_offset is None: - if allow_mixed: - return 2 # arbitrary non-zero value for __eq__ - else: - raise TypeError("cannot compare naive and aware datetimes") - - diff = self - other - if diff.days < 0: - return -1 - elif diff: - return 1 - else: - return 0 + return self.togregorian() < other_datetime def __hash__(self): """x.__hash__() <==> hash(x)""" diff --git a/tests/test_jdatetime.py b/tests/test_jdatetime.py index f41b935..2088f42 100644 --- a/tests/test_jdatetime.py +++ b/tests/test_jdatetime.py @@ -6,7 +6,6 @@ import threading import time from unittest import TestCase, skipIf, skipUnless -from unittest.mock import patch from zoneinfo import ZoneInfo import jdatetime @@ -718,77 +717,6 @@ def test_unpickle_older_datetime_object(self): class TestJdatetimeComparison(TestCase): - def test_cmp_equal_times_same_timezone(self): - dt1 = jdatetime.datetime(1402, 1, 1, 10, 0) - dt2 = jdatetime.datetime(1402, 1, 1, 10, 0) - self.assertEqual(dt1._cmp(dt2), 0) - - def test_cmp_different_times_same_timezone(self): - dt1 = jdatetime.datetime(1402, 1, 1, 10, 0) - dt2 = jdatetime.datetime(1402, 1, 1, 11, 0) - self.assertEqual(dt1._cmp(dt2), -1) - self.assertEqual(dt2._cmp(dt1), 1) - - def test_cmp_with_none_timezone(self): - dt1 = jdatetime.datetime(1402, 1, 1, 10, 0) - dt2 = jdatetime.datetime(1402, 1, 1, 10, 0, tzinfo=datetime.timezone.utc) - with self.assertRaises(TypeError): - dt1._cmp(dt2) - - def test_cmp_ambiguous_time_in_iran_timezone(self): - tz_teh = ZoneInfo("Asia/Tehran") - tz_gmt = GMTTime() - - jd = jdatetime.datetime(1395, 1, 2, 0, 15, tzinfo=tz_teh) - dt2 = datetime.datetime(2023, 9, 30, 23, 30, 30, tzinfo=tz_gmt) - jdt2 = jdatetime.datetime.fromgregorian(datetime=dt2) - self.assertEqual(jd._cmp(jdt2, allow_mixed=True), 2) - - def test_cmp_naive_and_aware_datetime(self): - dt1 = jdatetime.datetime(1402, 1, 1, 10, 0) - dt2 = jdatetime.datetime(1402, 1, 1, 10, 0, tzinfo=datetime.timezone.utc) - with self.assertRaises(TypeError): - dt1._cmp(dt2) - - # Allow mixed should return 2 - self.assertEqual(dt1._cmp(dt2, allow_mixed=True), 2) - - def test_cmp_aware_with_different_offsets(self): - tz_teh = TehranTime() - tz_gmt = GMTTime() - - # dt1 < dt2 - dt1 = jdatetime.datetime(1402, 1, 1, 13, 0, tzinfo=tz_teh) - dt2 = jdatetime.datetime(1402, 1, 1, 10, 0, tzinfo=tz_gmt) - self.assertEqual(dt1._cmp(dt2), -1) - self.assertEqual(dt2._cmp(dt1), 1) - - def test_equal_times_with_minute_difference(self): - tz_teh = TehranTime() - - # dt1 < dt2 - dt1 = jdatetime.datetime(2023, 9, 30, 12, 0, 0, tzinfo=tz_teh) - dt2 = jdatetime.datetime(2023, 9, 30, 12, 1, 0, tzinfo=tz_teh) - - self.assertEqual(dt1._cmp(dt2), -1) - self.assertEqual(dt2._cmp(dt1), 1) - - def test_equal_times_with_day_difference(self): - tz_teh = TehranTime() - - # dt1 < dt2 - dt1 = jdatetime.datetime(2023, 9, 30, 23, 59, 59, tzinfo=tz_teh) - dt2 = jdatetime.datetime(2023, 10, 1, 0, 0, 0, tzinfo=tz_teh) - - self.assertEqual(dt1._cmp(dt2), -1) - self.assertEqual(dt2._cmp(dt1), 1) - - def test_same_times_with_different_offsets(self): - dt1 = jdatetime.datetime(2023, 9, 30, 10, 0, 0, tzinfo=datetime.timezone(datetime.timedelta(hours=3))) - dt2 = jdatetime.datetime(2023, 9, 30, 12, 0, 0, tzinfo=datetime.timezone(datetime.timedelta(hours=5))) - self.assertEqual(dt1._cmp(dt2), 0) - self.assertEqual(dt2._cmp(dt1), 0) - # __eq__ def test_eq_datetime(self): date_string = "1363-6-6 12:13:14" @@ -820,140 +748,96 @@ def test_eq_datetime_diff_tz(self): jdt_teh = jdatetime.datetime(1389, 2, 17, 3, 30, 0, tzinfo=teh) self.assertEqual(jdt_teh, jdt_gmt) - def test_eq_datetimes_with_different_locales_are_not_equal(self): + def test_eq_datetimes_with_different_locales_are_equal(self): dt_en = jdatetime.datetime(2018, 4, 15, 0, 0, 0, locale='en_US') dt_fa = jdatetime.datetime(2018, 4, 15, 0, 0, 0, locale='fa_IR') - self.assertNotEqual(dt_en, dt_fa) + self.assertEqual(dt_en, dt_fa) - def test_eq_datetimes_with_different_locales_inequality_works(self): - dt_en = jdatetime.datetime(2018, 4, 15, 0, 0, 0, locale='en_US') - dt_fa = jdatetime.datetime(2018, 4, 15, 0, 0, 0, locale='fa_IR') - self.assertTrue(dt_en != dt_fa) - @patch.object(jdatetime.datetime, '_cmp') - def test_eq_with_none(self, mock_cmp): + def test_eq_with_none(self): dt1 = jdatetime.datetime(2023, 9, 30, 12, 0, 0, locale='fa_IR') self.assertFalse(dt1.__eq__(None)) - mock_cmp.assert_not_called() - @patch.object(jdatetime.datetime, '_cmp') - def test_eq_with_not_implemented(self, mock_cmp): + def test_eq_with_not_implemented(self): dt1 = jdatetime.datetime(2023, 9, 30, 12, 0, 0, locale='fa_IR') dt2 = "not a datetime object" self.assertFalse(dt1 == dt2) - mock_cmp.assert_not_called() # __ge__ - @patch.object(jdatetime.datetime, '_cmp') - def test_ge_with_same_datetime(self, mock_cmp): + def test_ge_with_same_datetime(self): dt1 = jdatetime.datetime(1402, 7, 8, 12, 0, 0) dt2 = jdatetime.datetime(1402, 7, 8, 12, 0, 0) - mock_cmp.return_value = 0 self.assertTrue(dt1 >= dt2) - mock_cmp.assert_called_once_with(dt2) - @patch.object(jdatetime.datetime, '_cmp') - def test_ge_with_greater_datetime(self, mock_cmp): + def test_ge_with_greater_datetime(self): dt1 = jdatetime.datetime(1402, 7, 8, 12, 0, 0) dt2 = jdatetime.datetime(1402, 7, 7, 12, 0, 0) - mock_cmp.return_value = 1 self.assertTrue(dt1 >= dt2) - mock_cmp.assert_called_once_with(dt2) - @patch.object(jdatetime.datetime, '_cmp') - def test_ge_with_lesser_datetime(self, mock_cmp): + def test_ge_with_lesser_datetime(self): dt1 = jdatetime.datetime(1402, 7, 8, 12, 0, 0) dt2 = jdatetime.datetime(1402, 7, 9, 12, 0, 0) - mock_cmp.return_value = -1 self.assertFalse(dt1 >= dt2) - mock_cmp.assert_called_once_with(dt2) # __gt__ - @patch.object(jdatetime.datetime, '_cmp') - def test_gt_with_same_datetime(self, mock_cmp): + def test_gt_with_same_datetime(self): dt1 = jdatetime.datetime(2023, 9, 30, 12, 0, 0) dt2 = jdatetime.datetime(2023, 9, 30, 12, 0, 0) - mock_cmp.return_value = 0 self.assertFalse(dt1 > dt2) - mock_cmp.assert_called_once_with(dt2) - @patch.object(jdatetime.datetime, '_cmp') - def test_gt_with_greater_datetime(self, mock_cmp): + def test_gt_with_greater_datetime(self): dt1 = jdatetime.datetime(2023, 10, 1, 12, 0, 0) dt2 = jdatetime.datetime(2023, 9, 30, 12, 0, 0) - mock_cmp.return_value = 1 self.assertTrue(dt1 > dt2) - mock_cmp.assert_called_once_with(dt2) - @patch.object(jdatetime.datetime, '_cmp') - def test_gt_with_lesser_datetime(self, mock_cmp): + def test_gt_with_lesser_datetime(self): dt1 = jdatetime.datetime(2023, 9, 29, 12, 0, 0) dt2 = jdatetime.datetime(2023, 9, 30, 12, 0, 0) - mock_cmp.return_value = -1 self.assertFalse(dt1 > dt2) - mock_cmp.assert_called_once_with(dt2) # __le__ - @patch.object(jdatetime.datetime, '_cmp') - def test_le_with_same_datetime(self, mock_cmp): + def test_le_with_same_datetime(self): dt1 = jdatetime.datetime(1402, 7, 1, 12, 0, 0) dt2 = jdatetime.datetime(1402, 7, 1, 12, 0, 0) - mock_cmp.return_value = 0 self.assertTrue(dt1 <= dt2) - mock_cmp.assert_called_once_with(dt2) - @patch.object(jdatetime.datetime, '_cmp') - def test_le_with_greater_datetime(self, mock_cmp): + def test_le_with_greater_datetime(self): dt1 = jdatetime.datetime(1402, 7, 2, 12, 0, 0) dt2 = jdatetime.datetime(1402, 7, 1, 12, 0, 0) - mock_cmp.return_value = 1 self.assertFalse(dt1 <= dt2) - mock_cmp.assert_called_once_with(dt2) - @patch.object(jdatetime.datetime, '_cmp') - def test_le_with_lesser_datetime(self, mock_cmp): + def test_le_with_lesser_datetime(self): dt1 = jdatetime.datetime(1402, 6, 30, 12, 0, 0) dt2 = jdatetime.datetime(1402, 7, 1, 12, 0, 0) - mock_cmp.return_value = -1 self.assertTrue(dt1 <= dt2) - mock_cmp.assert_called_once_with(dt2) # __lt__ - @patch.object(jdatetime.datetime, '_cmp') - def test_lt_with_same_datetime(self, mock_cmp): + def test_lt_with_same_datetime(self): dt1 = jdatetime.datetime(1402, 7, 1, 12, 0, 0) dt2 = jdatetime.datetime(1402, 7, 1, 12, 0, 0) - mock_cmp.return_value = 0 self.assertFalse(dt1 < dt2) - mock_cmp.assert_called_once_with(dt2) - @patch.object(jdatetime.datetime, '_cmp') - def test_lt_with_greater_datetime(self, mock_cmp): + def test_lt_with_greater_datetime(self): dt1 = jdatetime.datetime(1402, 7, 2, 12, 0, 0) dt2 = jdatetime.datetime(1402, 7, 1, 12, 0, 0) - mock_cmp.return_value = 1 self.assertFalse(dt1 < dt2) - mock_cmp.assert_called_once_with(dt2) - @patch.object(jdatetime.datetime, '_cmp') - def test_lt_with_lesser_datetime(self, mock_cmp): + def test_lt_with_lesser_datetime(self): dt1 = jdatetime.datetime(1402, 6, 30, 12, 0, 0) dt2 = jdatetime.datetime(1402, 7, 1, 12, 0, 0) - mock_cmp.return_value = -1 self.assertTrue(dt1 < dt2) - mock_cmp.assert_called_once_with(dt2) class TestJdatetimeGetSetLocale(TestCase):