From 2212ecf2e9f1b25b6b405a7982c0b9614f60f073 Mon Sep 17 00:00:00 2001 From: Bart Lengkeek Date: Fri, 25 Nov 2022 13:11:35 +0100 Subject: [PATCH 1/3] gh-99772: Do not ignore utc offset microseconds --- Lib/datetime.py | 14 ++++++++++---- Lib/test/datetimetester.py | 16 ++++++++++++++++ Modules/_datetimemodule.c | 32 +++++++++++++++++--------------- 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/Lib/datetime.py b/Lib/datetime.py index 1b0c5cb2d1c6ff4..7350b4f2d1992e0 100644 --- a/Lib/datetime.py +++ b/Lib/datetime.py @@ -1466,10 +1466,16 @@ def _cmp(self, other, allow_mixed=False): return 2 # arbitrary non-zero value else: raise TypeError("cannot compare naive and aware times") - myhhmm = self._hour * 60 + self._minute - myoff//timedelta(minutes=1) - othhmm = other._hour * 60 + other._minute - otoff//timedelta(minutes=1) - return _cmp((myhhmm, self._second, self._microsecond), - (othhmm, other._second, other._microsecond)) + + mydelta = timedelta( + seconds=self._hour * 3600 + self._minute * 60 + self._second, + microseconds=self._microsecond + ) - myoff + otdelta = timedelta( + seconds=other._hour * 3600 + other._minute * 60 + other._second, + microseconds=other._microsecond + ) - otoff + return _cmp(mydelta, otdelta) def __hash__(self): """Hash.""" diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 121d973b6d5f20d..85025dcc26c6210 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -3861,6 +3861,22 @@ def utcoffset(self, t): expected = 1 self.assertEqual(got, expected) + # Test tz offset with microseconds + d0 = base.replace(minute=5, tzinfo=timezone(timedelta(microseconds=3))) + d1 = base.replace( + minute=5, tzinfo=timezone(timedelta(microseconds=456))) + d2 = base.replace( + minute=5, tzinfo=timezone(timedelta(microseconds=98764))) + for x in d0, d1, d2: + for y in d0, d1, d2: + for op in lt, le, gt, ge, eq, ne: + got = op(x, y) + expected = op( + -x.utcoffset().microseconds, + -y.utcoffset().microseconds) + self.assertEqual(got, expected) + + # Testing time objects with a non-None tzinfo. class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase): diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index eda8c5610ba659e..efe5fbcd1c2e859 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -4473,22 +4473,24 @@ time_richcompare(PyObject *self, PyObject *other, int op) } /* The hard case: both aware with different UTC offsets */ else if (offset1 != Py_None && offset2 != Py_None) { - int offsecs1, offsecs2; + int64_t offusecs1, offusecs2, diff64; assert(offset1 != offset2); /* else last "if" handled it */ - offsecs1 = TIME_GET_HOUR(self) * 3600 + - TIME_GET_MINUTE(self) * 60 + - TIME_GET_SECOND(self) - - GET_TD_DAYS(offset1) * 86400 - - GET_TD_SECONDS(offset1); - offsecs2 = TIME_GET_HOUR(other) * 3600 + - TIME_GET_MINUTE(other) * 60 + - TIME_GET_SECOND(other) - - GET_TD_DAYS(offset2) * 86400 - - GET_TD_SECONDS(offset2); - diff = offsecs1 - offsecs2; - if (diff == 0) - diff = TIME_GET_MICROSECOND(self) - - TIME_GET_MICROSECOND(other); + offusecs1 = TIME_GET_HOUR(self) * 3600000000 + + TIME_GET_MINUTE(self) * INT64_C(60000000) + + TIME_GET_SECOND(self) * 1000000 + + TIME_GET_MICROSECOND(self) - + GET_TD_DAYS(offset1) * 86400000000 - + GET_TD_SECONDS(offset1) * INT64_C(1000000) - + GET_TD_MICROSECONDS(offset1); + offusecs2 = TIME_GET_HOUR(other) * 3600000000 + + TIME_GET_MINUTE(other) * INT64_C(60000000) + + TIME_GET_SECOND(other) * 1000000 + + TIME_GET_MICROSECOND(other) - + GET_TD_DAYS(offset2) * 86400000000 - + GET_TD_SECONDS(offset2) * INT64_C(1000000) - + GET_TD_MICROSECONDS(offset2); + diff64 = offusecs1 - offusecs2; + diff = diff64 ? diff64 < 0 ? -1 : 1 : 0; /* diff_to_bool needs int */ result = diff_to_bool(diff, op); } else if (op == Py_EQ) { From 9079ad35afde0431c07f5569a88a0aaca29effa3 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Fri, 25 Nov 2022 12:25:29 +0000 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2022-11-25-12-25-29.gh-issue-99772.ZO23Uc.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2022-11-25-12-25-29.gh-issue-99772.ZO23Uc.rst diff --git a/Misc/NEWS.d/next/Library/2022-11-25-12-25-29.gh-issue-99772.ZO23Uc.rst b/Misc/NEWS.d/next/Library/2022-11-25-12-25-29.gh-issue-99772.ZO23Uc.rst new file mode 100644 index 000000000000000..2621dfa92ea647f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-11-25-12-25-29.gh-issue-99772.ZO23Uc.rst @@ -0,0 +1 @@ +Fix compare operations for datetime.time values with microseconds resolution in utcoffset. From 08766ad0bbad47530e8c475b2507e831082761a8 Mon Sep 17 00:00:00 2001 From: Bart Lengkeek Date: Fri, 25 Nov 2022 15:09:21 +0100 Subject: [PATCH 3/3] gh-99772: Fix for Windows --- Modules/_datetimemodule.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index efe5fbcd1c2e859..68d1f1ca48e5ee5 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -4475,14 +4475,14 @@ time_richcompare(PyObject *self, PyObject *other, int op) else if (offset1 != Py_None && offset2 != Py_None) { int64_t offusecs1, offusecs2, diff64; assert(offset1 != offset2); /* else last "if" handled it */ - offusecs1 = TIME_GET_HOUR(self) * 3600000000 + + offusecs1 = TIME_GET_HOUR(self) * INT64_C(3600000000) + TIME_GET_MINUTE(self) * INT64_C(60000000) + TIME_GET_SECOND(self) * 1000000 + TIME_GET_MICROSECOND(self) - GET_TD_DAYS(offset1) * 86400000000 - GET_TD_SECONDS(offset1) * INT64_C(1000000) - GET_TD_MICROSECONDS(offset1); - offusecs2 = TIME_GET_HOUR(other) * 3600000000 + + offusecs2 = TIME_GET_HOUR(other) * INT64_C(3600000000) + TIME_GET_MINUTE(other) * INT64_C(60000000) + TIME_GET_SECOND(other) * 1000000 + TIME_GET_MICROSECOND(other) -