Skip to content

Commit 865f048

Browse files
committed
Fix comparison operators in neo4j.time library
1 parent eea44d7 commit 865f048

File tree

4 files changed

+820
-93
lines changed

4 files changed

+820
-93
lines changed

neo4j/time/__init__.py

Lines changed: 137 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1783,73 +1783,81 @@ def tzinfo(self):
17831783

17841784
# OPERATIONS #
17851785

1786+
def _get_both_normalized_ticks(self, other, strict=True):
1787+
if (isinstance(other, (time, Time))
1788+
and ((self.utc_offset() is None)
1789+
^ (other.utcoffset() is None))):
1790+
if strict:
1791+
raise TypeError("can't compare offset-naive and offset-aware "
1792+
"times")
1793+
else:
1794+
return None, None
1795+
if isinstance(other, Time):
1796+
other_ticks = other.__ticks
1797+
elif isinstance(other, time):
1798+
other_ticks = int(3600000000000 * other.hour
1799+
+ 60000000000 * other.minute
1800+
+ NANO_SECONDS * other.second
1801+
+ 1000 * other.microsecond)
1802+
else:
1803+
return None, None
1804+
utc_offset = other.utcoffset()
1805+
if utc_offset is not None:
1806+
other_ticks -= utc_offset.total_seconds() * NANO_SECONDS
1807+
self_ticks = self.__ticks
1808+
utc_offset = self.utc_offset()
1809+
if utc_offset is not None:
1810+
self_ticks -= utc_offset.total_seconds() * NANO_SECONDS
1811+
return self_ticks, other_ticks
1812+
17861813
def __hash__(self):
17871814
""""""
1788-
return hash(self.__ticks) ^ hash(self.tzinfo)
1815+
if self.__nanosecond % 1000 == 0:
1816+
return hash(self.to_native())
1817+
self_ticks = self.__ticks
1818+
if self.utc_offset() is not None:
1819+
self_ticks -= self.utc_offset().total_seconds() * NANO_SECONDS
1820+
return hash(self_ticks)
17891821

17901822
def __eq__(self, other):
17911823
"""`==` comparison with :class:`.Time` or :class:`datetime.time`."""
1792-
if isinstance(other, Time):
1793-
return self.__ticks == other.__ticks and self.tzinfo == other.tzinfo
1794-
if isinstance(other, time):
1795-
other_ticks = (3600000000000 * other.hour
1796-
+ 60000000000 * other.minute
1797-
+ NANO_SECONDS * other.second
1798-
+ 1000 * other.microsecond)
1799-
return self.ticks_ns == other_ticks and self.tzinfo == other.tzinfo
1800-
return False
1824+
self_ticks, other_ticks = self._get_both_normalized_ticks(other,
1825+
strict=False)
1826+
if self_ticks is None:
1827+
return False
1828+
return self_ticks == other_ticks
18011829

18021830
def __ne__(self, other):
18031831
"""`!=` comparison with :class:`.Time` or :class:`datetime.time`."""
18041832
return not self.__eq__(other)
18051833

18061834
def __lt__(self, other):
18071835
"""`<` comparison with :class:`.Time` or :class:`datetime.time`."""
1808-
if isinstance(other, Time):
1809-
return (self.tzinfo == other.tzinfo
1810-
and self.ticks_ns < other.ticks_ns)
1811-
if isinstance(other, time):
1812-
if self.tzinfo != other.tzinfo:
1813-
return False
1814-
other_ticks = 3600 * other.hour + 60 * other.minute + other.second + (other.microsecond / 1000000)
1815-
return self.ticks_ns < other_ticks
1816-
return NotImplemented
1836+
self_ticks, other_ticks = self._get_both_normalized_ticks(other)
1837+
if self_ticks is None:
1838+
return NotImplemented
1839+
return self_ticks < other_ticks
18171840

18181841
def __le__(self, other):
18191842
"""`<=` comparison with :class:`.Time` or :class:`datetime.time`."""
1820-
if isinstance(other, Time):
1821-
return (self.tzinfo == other.tzinfo
1822-
and self.ticks_ns <= other.ticks_ns)
1823-
if isinstance(other, time):
1824-
if self.tzinfo != other.tzinfo:
1825-
return False
1826-
other_ticks = 3600 * other.hour + 60 * other.minute + other.second + (other.microsecond / 1000000)
1827-
return self.ticks_ns <= other_ticks
1828-
return NotImplemented
1843+
self_ticks, other_ticks = self._get_both_normalized_ticks(other)
1844+
if self_ticks is None:
1845+
return NotImplemented
1846+
return self_ticks <= other_ticks
18291847

18301848
def __ge__(self, other):
18311849
"""`>=` comparison with :class:`.Time` or :class:`datetime.time`."""
1832-
if isinstance(other, Time):
1833-
return (self.tzinfo == other.tzinfo
1834-
and self.ticks_ns >= other.ticks_ns)
1835-
if isinstance(other, time):
1836-
if self.tzinfo != other.tzinfo:
1837-
return False
1838-
other_ticks = 3600 * other.hour + 60 * other.minute + other.second + (other.microsecond / 1000000)
1839-
return self.ticks_ns >= other_ticks
1840-
return NotImplemented
1850+
self_ticks, other_ticks = self._get_both_normalized_ticks(other)
1851+
if self_ticks is None:
1852+
return NotImplemented
1853+
return self_ticks >= other_ticks
18411854

18421855
def __gt__(self, other):
18431856
"""`>` comparison with :class:`.Time` or :class:`datetime.time`."""
1844-
if isinstance(other, Time):
1845-
return (self.tzinfo == other.tzinfo
1846-
and self.ticks_ns >= other.ticks_ns)
1847-
if isinstance(other, time):
1848-
if self.tzinfo != other.tzinfo:
1849-
return False
1850-
other_ticks = 3600 * other.hour + 60 * other.minute + other.second + (other.microsecond / 1000000)
1851-
return self.ticks_ns >= other_ticks
1852-
return NotImplemented
1857+
self_ticks, other_ticks = self._get_both_normalized_ticks(other)
1858+
if self_ticks is None:
1859+
return NotImplemented
1860+
return self_ticks > other_ticks
18531861

18541862
def __copy__(self):
18551863
return self.__new(self.__ticks, self.__hour, self.__minute,
@@ -1883,6 +1891,21 @@ def replace(self, **kwargs):
18831891
nanosecond=kwargs.get("nanosecond", self.__nanosecond),
18841892
tzinfo=kwargs.get("tzinfo", self.__tzinfo))
18851893

1894+
def _utc_offset(self, dt=None):
1895+
if self.tzinfo is None:
1896+
return None
1897+
value = self.tzinfo.utcoffset(dt)
1898+
if value is None:
1899+
return None
1900+
if isinstance(value, timedelta):
1901+
s = value.total_seconds()
1902+
if not (-86400 < s < 86400):
1903+
raise ValueError("utcoffset must be less than a day")
1904+
if s % 60 != 0 or value.microseconds != 0:
1905+
raise ValueError("utcoffset must be a whole number of minutes")
1906+
return value
1907+
raise TypeError("utcoffset must be a timedelta")
1908+
18861909
def utc_offset(self):
18871910
"""Return the UTC offset of this time.
18881911
@@ -1896,19 +1919,7 @@ def utc_offset(self):
18961919
:raises TypeError: if `self.tzinfo.utcoffset(self)` does return anything but
18971920
None or a :class:`datetime.timedelta`.
18981921
"""
1899-
if self.tzinfo is None:
1900-
return None
1901-
value = self.tzinfo.utcoffset(self)
1902-
if value is None:
1903-
return None
1904-
if isinstance(value, timedelta):
1905-
s = value.total_seconds()
1906-
if not (-86400 < s < 86400):
1907-
raise ValueError("utcoffset must be less than a day")
1908-
if s % 60 != 0 or value.microseconds != 0:
1909-
raise ValueError("utcoffset must be a whole number of minutes")
1910-
return value
1911-
raise TypeError("utcoffset must be a timedelta")
1922+
return self._utc_offset()
19121923

19131924
def dst(self):
19141925
"""Get the daylight saving time adjustment (DST).
@@ -1997,6 +2008,7 @@ def __format__(self, format_spec):
19972008
""""""
19982009
raise NotImplementedError()
19992010

2011+
20002012
Time.min = Time(hour=0, minute=0, second=0, nanosecond=0)
20012013
Time.max = Time(hour=23, minute=59, second=59, nanosecond=999999999)
20022014
Time.resolution = Duration(nanoseconds=1)
@@ -2330,17 +2342,52 @@ def hour_minute_second_nanosecond(self):
23302342

23312343
# OPERATIONS #
23322344

2345+
def _get_both_normalized(self, other, strict=True):
2346+
if (isinstance(other, (datetime, DateTime))
2347+
and ((self.utc_offset() is None)
2348+
^ (other.utcoffset() is None))):
2349+
if strict:
2350+
raise TypeError("can't compare offset-naive and offset-aware "
2351+
"datetimes")
2352+
else:
2353+
return None, None
2354+
self_norm = self
2355+
utc_offset = self.utc_offset()
2356+
if utc_offset is not None:
2357+
self_norm -= utc_offset
2358+
self_norm = self_norm.replace(tzinfo=None)
2359+
other_norm = other
2360+
if isinstance(other, (datetime, DateTime)):
2361+
utc_offset = other.utcoffset()
2362+
if utc_offset is not None:
2363+
other_norm -= utc_offset
2364+
other_norm = other_norm.replace(tzinfo=None)
2365+
else:
2366+
return None, None
2367+
return self_norm, other_norm
2368+
23332369
def __hash__(self):
23342370
""""""
2335-
return hash(self.date()) ^ hash(self.time())
2371+
if self.nanosecond % 1000 == 0:
2372+
return hash(self.to_native())
2373+
self_norm = self
2374+
utc_offset = self.utc_offset()
2375+
if utc_offset is not None:
2376+
self_norm -= utc_offset
2377+
return hash(self_norm.date()) ^ hash(self_norm.time())
23362378

23372379
def __eq__(self, other):
23382380
"""
23392381
`==` comparison with :class:`.DateTime` or :class:`datetime.datetime`.
23402382
"""
2341-
if isinstance(other, (DateTime, datetime)):
2383+
if not isinstance(other, (datetime, DateTime)):
2384+
return NotImplemented
2385+
if self.utc_offset() == other.utcoffset():
23422386
return self.date() == other.date() and self.time() == other.time()
2343-
return False
2387+
self_norm, other_norm = self._get_both_normalized(other, strict=False)
2388+
if self_norm is None:
2389+
return False
2390+
return self_norm == other_norm
23442391

23452392
def __ne__(self, other):
23462393
"""
@@ -2352,45 +2399,55 @@ def __lt__(self, other):
23522399
"""
23532400
`<` comparison with :class:`.DateTime` or :class:`datetime.datetime`.
23542401
"""
2355-
if isinstance(other, (DateTime, datetime)):
2402+
if not isinstance(other, (datetime, DateTime)):
2403+
return NotImplemented
2404+
if self.utc_offset() == other.utcoffset():
23562405
if self.date() == other.date():
23572406
return self.time() < other.time()
2358-
else:
2359-
return self.date() < other.date()
2360-
return NotImplemented
2407+
return self.date() < other.date()
2408+
self_norm, other_norm = self._get_both_normalized(other)
2409+
return (self_norm.date() < other_norm.date()
2410+
or self_norm.time() < other_norm.time())
23612411

23622412
def __le__(self, other):
23632413
"""
23642414
`<=` comparison with :class:`.DateTime` or :class:`datetime.datetime`.
23652415
"""
2366-
if isinstance(other, (DateTime, datetime)):
2416+
if not isinstance(other, (datetime, DateTime)):
2417+
return NotImplemented
2418+
if self.utc_offset() == other.utcoffset():
23672419
if self.date() == other.date():
23682420
return self.time() <= other.time()
2369-
else:
2370-
return self.date() < other.date()
2371-
return NotImplemented
2421+
return self.date() <= other.date()
2422+
self_norm, other_norm = self._get_both_normalized(other)
2423+
return self_norm <= other_norm
23722424

23732425
def __ge__(self, other):
23742426
"""
23752427
`>=` comparison with :class:`.DateTime` or :class:`datetime.datetime`.
23762428
"""
2377-
if isinstance(other, (DateTime, datetime)):
2429+
if not isinstance(other, (datetime, DateTime)):
2430+
return NotImplemented
2431+
if self.utc_offset() == other.utcoffset():
23782432
if self.date() == other.date():
23792433
return self.time() >= other.time()
2380-
else:
2381-
return self.date() > other.date()
2382-
return NotImplemented
2434+
return self.date() >= other.date()
2435+
self_norm, other_norm = self._get_both_normalized(other)
2436+
return self_norm >= other_norm
23832437

23842438
def __gt__(self, other):
23852439
"""
23862440
`>` comparison with :class:`.DateTime` or :class:`datetime.datetime`.
23872441
"""
2388-
if isinstance(other, (DateTime, datetime)):
2442+
if not isinstance(other, (datetime, DateTime)):
2443+
return NotImplemented
2444+
if self.utc_offset() == other.utcoffset():
23892445
if self.date() == other.date():
23902446
return self.time() > other.time()
2391-
else:
2392-
return self.date() > other.date()
2393-
return NotImplemented
2447+
return self.date() > other.date()
2448+
self_norm, other_norm = self._get_both_normalized(other)
2449+
return (self_norm.date() > other_norm.date()
2450+
or self_norm.time() > other_norm.time())
23942451

23952452
def __add__(self, other):
23962453
"""Add a :class:`datetime.timedelta`.
@@ -2494,7 +2551,7 @@ def as_timezone(self, tz):
24942551
"""
24952552
if self.tzinfo is None:
24962553
return self
2497-
utc = (self - self.utcoffset()).replace(tzinfo=tz)
2554+
utc = (self - self.utc_offset()).replace(tzinfo=tz)
24982555
return tz.fromutc(utc)
24992556

25002557
def utc_offset(self):
@@ -2503,7 +2560,7 @@ def utc_offset(self):
25032560
See :meth:`.Time.utc_offset`.
25042561
"""
25052562

2506-
return self.__time.utc_offset()
2563+
return self.__time._utc_offset(self)
25072564

25082565
def dst(self):
25092566
"""Get the daylight saving time adjustment (DST).

0 commit comments

Comments
 (0)