From bd271024dd67128265fc77e6fc392cb61f70d60b Mon Sep 17 00:00:00 2001 From: 5j9 <5j9@users.noreply.github.com> Date: Sun, 7 Apr 2024 21:19:28 +0330 Subject: [PATCH 01/10] fix appveyor builds by installing current project (#155) running jdatetime requires jalai-core package which needs to be installed through the requirements. --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 1298ffd..8652d7f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -16,7 +16,7 @@ init: - "ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH%" install: - - py -m pip install tzdata + - py -m pip install . tzdata test_script: - py -m unittest discover tests From 06352d51028d85c8f271e374223a20aad9170d6b Mon Sep 17 00:00:00 2001 From: 5j9 <5j9@users.noreply.github.com> Date: Mon, 8 Apr 2024 00:35:50 +0330 Subject: [PATCH 02/10] Move datetime-specific code from date methods into datetime class (#154) _strftime_p: only raises Exception (AttributeError) on date objects because dates do not have hour attribute. _strftime_z: only raises AttributeError on a date class becuase date does not have utcoffset attribute. _strftime_cap_z: date does not have tzname attribute, but datetime has. --- jdatetime/__init__.py | 56 ++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/jdatetime/__init__.py b/jdatetime/__init__.py index 2aa83f2..4b92f1d 100644 --- a/jdatetime/__init__.py +++ b/jdatetime/__init__.py @@ -576,37 +576,13 @@ def _strftime_get_method_value(self, attr, fmt): return fmt % getattr(self, attr)() def _strftime_p(self): - try: - if self.hour >= 12: - return self.j_ampm['PM'] - return self.j_ampm['AM'] - except Exception: - return self.j_ampm['AM'] + return self.j_ampm['AM'] def _strftime_z(self): - try: - sign = "+" - diff = self.utcoffset() - diff_sec = diff.seconds - if diff.days > 0 or diff.days < -1: - raise ValueError( - "tzinfo.utcoffset() returned big time delta! ; must be in -1439 .. 1439" - ) - if diff.days != 0: - sign = "-" - diff_sec = (1 * 24 * 60 * 60) - diff_sec - tmp_min = diff_sec / 60 - diff_hour = tmp_min / 60 - diff_min = tmp_min % 60 - return '%s%02.d%02.d' % (sign, diff_hour, diff_min) - except AttributeError: - return '' + return '' def _strftime_cap_z(self): - if hasattr(self, 'tzname') and self.tzname() is not None: - return self.tzname() - else: - return '' + return '' def _strftime_c(self): return self.strftime("%a %b %d %H:%M:%S %Y") @@ -1340,3 +1316,29 @@ def _timezone_from_string(timezone_string): gmtoff_fraction = -gmtoff_fraction timezone = py_datetime.timezone(timedelta(seconds=gmtoff, microseconds=gmtoff_fraction)) return timezone + + def _strftime_p(self): + if self.hour >= 12: + return self.j_ampm['PM'] + return self.j_ampm['AM'] + + def _strftime_z(self): + diff = self.utcoffset() + if diff is None: + return '' + sign = '+' + diff_sec = diff.seconds + if diff.days > 0 or diff.days < -1: + raise ValueError( + 'tzinfo.utcoffset() returned big time delta! ; must be in -1439 .. 1439' + ) + if diff.days != 0: + sign = '-' + diff_sec = (1 * 24 * 60 * 60) - diff_sec + tmp_min = diff_sec / 60 + diff_hour = tmp_min / 60 + diff_min = tmp_min % 60 + return '%s%02.d%02.d' % (sign, diff_hour, diff_min) + + def _strftime_cap_z(self): + return self.tzname() or '' From ac6c2052e41462714431946cf13cee28967082b4 Mon Sep 17 00:00:00 2001 From: Hasan Ramezani Date: Mon, 21 Oct 2024 11:58:01 +0200 Subject: [PATCH 03/10] Add Python 3.13 support and drop Pyhton 3.8 support (#157) --- .github/workflows/test.yml | 6 +++--- CHANGELOG.md | 5 +++++ appveyor.yml | 3 ++- setup.py | 4 ++-- tests/test_jdatetime.py | 2 +- tox.ini | 4 ++-- 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1e08035..385b9d4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] name: OS ${{ matrix.os}} - Python ${{ matrix.python-version }} steps: - uses: actions/checkout@v2 @@ -37,7 +37,7 @@ jobs: - name: Setup python uses: actions/setup-python@v2 with: - python-version: "3.8" + python-version: "3.9" architecture: x64 - name: Install dependencies run: | @@ -54,7 +54,7 @@ jobs: - name: Setup python uses: actions/setup-python@v2 with: - python-version: "3.8" + python-version: "3.9" architecture: x64 - name: Install dependencies run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 87f1734..b0ed6ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +### Add +* Drop Python 3.8 support +* Add support for Python 3.13 + + ## [5.0.0] - 2024-03-26 ### Add diff --git a/appveyor.yml b/appveyor.yml index 8652d7f..c3122c7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,7 +2,6 @@ build: false environment: matrix: - - PYTHON: "C:\\Python38" - PYTHON: "C:\\Python39" APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - PYTHON: "C:\\Python310" @@ -11,6 +10,8 @@ environment: APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - PYTHON: "C:\\Python312" APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + - PYTHON: "C:\\Python313" + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 init: - "ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH%" diff --git a/setup.py b/setup.py index 3c6c123..8827626 100644 --- a/setup.py +++ b/setup.py @@ -14,18 +14,18 @@ description=("Jalali datetime binding for python"), url="https://github.com/slashmili/python-jalali", long_description=open('README').read(), - python_requires=">=3.8", + python_requires=">=3.9", install_requires=["jalali-core>=1.0"], classifiers=[ "Intended Audience :: Developers", "Intended Audience :: System Administrators", "Operating System :: OS Independent", "Programming Language :: Python", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Software Development", ], ) diff --git a/tests/test_jdatetime.py b/tests/test_jdatetime.py index a9e69ec..76d62fe 100644 --- a/tests/test_jdatetime.py +++ b/tests/test_jdatetime.py @@ -645,7 +645,7 @@ def reset_locale(self): if platform.system() == 'Windows': locale.setlocale(locale.LC_ALL, 'English_United States') else: - locale.resetlocale() + locale.setlocale(locale.LC_ALL, '') def test_with_fa_locale(self): self.set_fa_locale() diff --git a/tox.ini b/tox.ini index 73c4000..751b30b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,16 +1,16 @@ [tox] envlist = - py{38,39,310,311,312} + py{39,310,311,312,313} flake8 isort [gh-actions] python = - 3.8: py38 3.9: py39 3.10: py310 3.11: py311 3.12: py312 + 3.13: py313 [testenv] deps = From 41e49a847f9c9f0dd91aa8dc7257ad2cf3f24d4d Mon Sep 17 00:00:00 2001 From: Mobin Ghoveoud Date: Fri, 25 Oct 2024 20:30:14 +0330 Subject: [PATCH 04/10] feat: Add support compare datetime with offset --- jdatetime/__init__.py | 139 ++++++++++++++++++++++++++---------------- 1 file changed, 88 insertions(+), 51 deletions(-) diff --git a/jdatetime/__init__.py b/jdatetime/__init__.py index 4b92f1d..09093d3 100644 --- a/jdatetime/__init__.py +++ b/jdatetime/__init__.py @@ -86,6 +86,10 @@ 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})" @@ -1042,93 +1046,57 @@ def __eq__(self, other_datetime): """x.__eq__(y) <==> x==y""" if other_datetime is None: return False + if isinstance(other_datetime, py_datetime.datetime): return self.__eq__(datetime.fromgregorian(datetime=other_datetime)) + if not isinstance(other_datetime, datetime): return NotImplemented - if ( - self.year == other_datetime.year and - self.month == other_datetime.month and - self.day == other_datetime.day and - self.locale == other_datetime.locale - ): - return ( - self.timetz() == other_datetime.timetz() and - self.microsecond == other_datetime.microsecond - ) + + if self.locale == other_datetime.locale: + return self._cmp(other_datetime, allow_mixed=True) == 0 + return False 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 not isinstance(other_datetime, datetime): return NotImplemented - return ( - self.year, - self.month, - self.day, - self.hour, - self.minute, - self.second, - self.microsecond, - ) >= ( - other_datetime.year, - other_datetime.month, - other_datetime.day, - other_datetime.hour, - other_datetime.minute, - other_datetime.second, - other_datetime.microsecond, - ) + return self._cmp(other_datetime) >= 0 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 not isinstance(other_datetime, datetime): return NotImplemented - return ( - self.year, - self.month, - self.day, - self.hour, - self.minute, - self.second, - self.microsecond - ) > ( - other_datetime.year, - other_datetime.month, - other_datetime.day, - other_datetime.hour, - other_datetime.minute, - other_datetime.second, - other_datetime.microsecond, - ) - - def __hash__(self): - """x.__hash__() <==> hash(x)""" - gdt = self.togregorian() - return gdt.__hash__() + return self._cmp(other_datetime) > 0 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 not isinstance(other_datetime, datetime): return NotImplemented - return not self.__gt__(other_datetime) + return self._cmp(other_datetime) <= 0 def __lt__(self, other_datetime): """x.__lt__(y) <==> x x!=y""" @@ -1141,6 +1109,75 @@ def __ne__(self, other_datetime): 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 + + def __hash__(self): + """x.__hash__() <==> hash(x)""" + gdt = self.togregorian() + return gdt.__hash__() + @staticmethod def fromgregorian(**kw): """Convert gregorian to jalali and return jadatetime.datetime From 80bb9b6e7f3e96ab506e8de36c6a36812813c9f9 Mon Sep 17 00:00:00 2001 From: Mobin Ghoveoud Date: Fri, 25 Oct 2024 20:41:24 +0330 Subject: [PATCH 05/10] test: Add datetime comparison tests --- jdatetime/__init__.py | 3 +- tests/test_jdatetime.py | 282 ++++++++++++++++++++++++++++++++++------ 2 files changed, 242 insertions(+), 43 deletions(-) diff --git a/jdatetime/__init__.py b/jdatetime/__init__.py index 09093d3..3442340 100644 --- a/jdatetime/__init__.py +++ b/jdatetime/__init__.py @@ -1112,7 +1112,8 @@ def __ne__(self, 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. + If allow_mixed is True, returns 2 for ambiguous times during DST transitions, + as needed for the __eq__ method. """ assert isinstance(other, datetime) diff --git a/tests/test_jdatetime.py b/tests/test_jdatetime.py index 76d62fe..510aef8 100644 --- a/tests/test_jdatetime.py +++ b/tests/test_jdatetime.py @@ -6,6 +6,7 @@ import threading import time from unittest import TestCase, skipIf, skipUnless +from unittest.mock import patch import jdatetime @@ -446,54 +447,12 @@ def test_strptime_invalid_date_string_z_directive(self): with self.assertRaises(ValueError, msg=msg): jdatetime.datetime.strptime(date_string, date_format) - def test_datetime_eq(self): - date_string = "1363-6-6 12:13:14" - date_format = "%Y-%m-%d %H:%M:%S" - - dt1 = jdatetime.datetime.strptime(date_string, date_format) - - date_string = "1364-6-6 12:13:14" - dt2 = jdatetime.datetime.strptime(date_string, date_format) - - self.assertNotEqual(dt2, dt1) - - def test_datetime_eq_now(self): - import time - dt1 = jdatetime.datetime.now() - time.sleep(0.1) - dt2 = jdatetime.datetime.now() - self.assertNotEqual(dt2, dt1) - def test_timetz(self): teh = TehranTime() dt_gmt = datetime.datetime(2015, 6, 27, 1, 2, 3, tzinfo=teh) self.assertEqual("01:02:03+03:30", dt_gmt.timetz().__str__()) - def test_datetime_eq_diff_tz(self): - gmt = GMTTime() - teh = TehranTime() - - dt_gmt = datetime.datetime(2015, 6, 27, 0, 0, 0, tzinfo=gmt) - dt_teh = datetime.datetime(2015, 6, 27, 3, 30, 0, tzinfo=teh) - self.assertEqual(dt_teh, dt_gmt, "In standrd python datetime, __eq__ considers timezone") - - jdt_gmt = jdatetime.datetime(1389, 2, 17, 0, 0, 0, tzinfo=gmt) - - jdt_teh = jdatetime.datetime(1389, 2, 17, 3, 30, 0, tzinfo=teh) - - self.assertEqual(jdt_teh, jdt_gmt) - - def test_datetimes_with_different_locales_are_not_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) - - def test_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) - def test_fromgregorian_accepts_named_argument_of_date_and_locale(self): gd = datetime.date(2018, 7, 14) jdt = jdatetime.datetime.fromgregorian(date=gd, locale='nl_NL') @@ -769,6 +728,245 @@ def test_unpickle_older_datetime_object(self): self.assertEqual(dt, jdatetime.datetime(1400, 10, 11, 1, 2, 3, 30)) +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_ambiguous_time_in_iran(self): + tz_teh = zoneinfo.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" + date_format = "%Y-%m-%d %H:%M:%S" + + dt1 = jdatetime.datetime.strptime(date_string, date_format) + + date_string = "1364-6-6 12:13:14" + dt2 = jdatetime.datetime.strptime(date_string, date_format) + + self.assertNotEqual(dt2, dt1) + + def test_eq_datetime_now(self): + import time + dt1 = jdatetime.datetime.now() + time.sleep(0.1) + dt2 = jdatetime.datetime.now() + self.assertNotEqual(dt2, dt1) + + def test_eq_datetime_diff_tz(self): + gmt = GMTTime() + teh = TehranTime() + + dt_gmt = datetime.datetime(2015, 6, 27, 0, 0, 0, tzinfo=gmt) + dt_teh = datetime.datetime(2015, 6, 27, 3, 30, 0, tzinfo=teh) + self.assertEqual(dt_teh, dt_gmt, "In standrd python datetime, __eq__ considers timezone") + + jdt_gmt = jdatetime.datetime(1389, 2, 17, 0, 0, 0, tzinfo=gmt) + 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): + 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) + + 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): + 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): + 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): + 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): + 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): + 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): + 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): + 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): + 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): + 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): + 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): + 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): + 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): + 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): + 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): @staticmethod def record_thread_locale(record, event, locale): From 8d03a1cbddcbc7ce20d1452c263fc07726dfbf13 Mon Sep 17 00:00:00 2001 From: Mobin Ghoveoud Date: Sat, 26 Oct 2024 22:47:38 +0330 Subject: [PATCH 06/10] fix: Fix import zoneinfo in test_jdatetime.py --- tests/test_jdatetime.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/tests/test_jdatetime.py b/tests/test_jdatetime.py index 510aef8..f41b935 100644 --- a/tests/test_jdatetime.py +++ b/tests/test_jdatetime.py @@ -7,6 +7,7 @@ import time from unittest import TestCase, skipIf, skipUnless from unittest.mock import patch +from zoneinfo import ZoneInfo import jdatetime @@ -17,17 +18,6 @@ except ImportError: greenlet_installed = False -try: - import zoneinfo - if platform.system() == 'Windows': - # Windows systems, do not have system time zone data available - # therefore tzdata is required. See: - # https://docs.python.org/3/library/zoneinfo.html#data-sources - import tzinfo as _ # noqa -except ImportError: - zoneinfo = None - - from tests import load_pickle @@ -713,9 +703,8 @@ def test_isoformat_custom_timespec(self): self.assertEqual(milliseconds, '1398-04-11T11:06:05.123') self.assertEqual(microseconds, '1398-04-11T11:06:05.123456') - @skipIf(zoneinfo is None, "ZoneInfo not supported!") def test_zoneinfo_as_timezone(self): - tzinfo = zoneinfo.ZoneInfo('Asia/Tehran') + tzinfo = ZoneInfo('Asia/Tehran') jdt = jdatetime.datetime(1398, 4, 11, 11, 6, 5, 123456, tzinfo=tzinfo) self.assertEqual(str(jdt), '1398-04-11 11:06:05.123456+0430') @@ -746,8 +735,8 @@ def test_cmp_with_none_timezone(self): with self.assertRaises(TypeError): dt1._cmp(dt2) - def test_ambiguous_time_in_iran(self): - tz_teh = zoneinfo.ZoneInfo("Asia/Tehran") + 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) From 14e8de39810d67c4fc0feda027bf5bc13a17d2fc Mon Sep 17 00:00:00 2001 From: Mobin Ghoveoud Date: Fri, 15 Nov 2024 22:52:14 +0330 Subject: [PATCH 07/10] 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): From 2f067c4505066e3650f110a415d5b3e602893914 Mon Sep 17 00:00:00 2001 From: Mobin Ghoveoud Date: Fri, 15 Nov 2024 22:54:38 +0330 Subject: [PATCH 08/10] fix(flake8): Fix extra line --- tests/test_jdatetime.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_jdatetime.py b/tests/test_jdatetime.py index 2088f42..4d8c84a 100644 --- a/tests/test_jdatetime.py +++ b/tests/test_jdatetime.py @@ -753,7 +753,6 @@ def test_eq_datetimes_with_different_locales_are_equal(self): dt_fa = jdatetime.datetime(2018, 4, 15, 0, 0, 0, locale='fa_IR') self.assertEqual(dt_en, dt_fa) - def test_eq_with_none(self): dt1 = jdatetime.datetime(2023, 9, 30, 12, 0, 0, locale='fa_IR') self.assertFalse(dt1.__eq__(None)) From fc5a63c977af5592024269d09853e15201ce4409 Mon Sep 17 00:00:00 2001 From: Mobin Ghoveoud Date: Sat, 16 Nov 2024 03:15:48 +0330 Subject: [PATCH 09/10] feat(datetime): Update datetime __eq__ for different locales --- jdatetime/__init__.py | 4 ++++ tests/test_jdatetime.py | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/jdatetime/__init__.py b/jdatetime/__init__.py index 2872d82..97fcb45 100644 --- a/jdatetime/__init__.py +++ b/jdatetime/__init__.py @@ -1043,6 +1043,10 @@ def __eq__(self, other_datetime): if other_datetime is None: return False + other_locale = other_datetime.locale if isinstance(other_datetime, datetime) else get_locale() + if self.locale != other_locale: + return False + if isinstance(other_datetime, datetime): other_datetime = other_datetime.togregorian() diff --git a/tests/test_jdatetime.py b/tests/test_jdatetime.py index 4d8c84a..f8b82cf 100644 --- a/tests/test_jdatetime.py +++ b/tests/test_jdatetime.py @@ -748,10 +748,11 @@ 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_equal(self): + def test_eq_datetimes_with_different_locales_are_not_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.assertEqual(dt_en, dt_fa) + self.assertNotEqual(dt_en, dt_fa) + self.assertNotEqual(dt_fa, dt_en) def test_eq_with_none(self): dt1 = jdatetime.datetime(2023, 9, 30, 12, 0, 0, locale='fa_IR') From 9e077064e56a821e6a6cd7a4b29c95bf364af9d0 Mon Sep 17 00:00:00 2001 From: Milad Date: Mon, 13 Jan 2025 12:03:02 +0100 Subject: [PATCH 10/10] bump version to 5.1.0 --- CHANGELOG.md | 6 ++++++ jdatetime/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0ed6ae..cc2e30e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [5.1.0] - 2025-01-13 + +### Fixed + +* Fix compare datetime with diffrent timezones #159 + ### Add * Drop Python 3.8 support * Add support for Python 3.13 diff --git a/jdatetime/__init__.py b/jdatetime/__init__.py index 97fcb45..cf3641c 100644 --- a/jdatetime/__init__.py +++ b/jdatetime/__init__.py @@ -16,7 +16,7 @@ from jalali_core import GregorianToJalali, JalaliToGregorian, j_days_in_month -__VERSION__ = "5.0.0" +__VERSION__ = "5.1.0" MINYEAR = 1 MAXYEAR = 9377 diff --git a/setup.py b/setup.py index 8827626..2ab22b2 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ setup( name='jdatetime', - version='5.0.0', + version='5.1.0', packages=['jdatetime', ], license='Python Software Foundation License', keywords='Jalali implementation of Python datetime',