Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions Lib/datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
16 changes: 16 additions & 0 deletions Lib/test/datetimetester.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need the minus sign here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the utc offset contributes to the time in a negative way, in other words, the greater the utc offset, the earlier the actual time.

-y.utcoffset().microseconds)
self.assertEqual(got, expected)



# Testing time objects with a non-None tzinfo.
class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix compare operations for datetime.time values with microseconds resolution in utcoffset.
32 changes: 17 additions & 15 deletions Modules/_datetimemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be a minor discussion point, but we could avoid using int64_t and the following big-integer calculations by doing a multi-step comparison, i.e. comparing the hours, then minutes, then seconds etc.
The motivation for this is a bit of gut feeling, but I think that way will be slightly more efficient in terms of time and space.

@blenq blenq Nov 27, 2022

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually implemented it like you suggest earlier, because I share your gut feeling. Using the existing code for checks up to seconds and adjusting only the microseconds check. Indeed with the advantage that it all fits in 32 bits.

What I encountered using this approach was the following: 13:59:59.9+02 is actually later than 14:00:00+02:00:00.9. The combined microseconds values, in the time values and the offsets, can contribute up to a maximum of 1.999998 seconds to the diff in either way. That means that the simple intermediary check that is used in other multi step comparisons if (diff == 0) can not be used. It should become if (diff > -2 && diff < 2). I chose to go for the int64_t approach because I thought it to be less confusing, than the 2 seconds intermediary check, but I am perfectly willing to go with your suggestion, if that is the consensus.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@blenq Does one of the tests already handle the use case of 13:59:59.9+02 vs. 14:00:00+02:00:00.9? If not we should add that as a test case.

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) * 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) * INT64_C(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) {
Expand Down