diff --git a/CHANGELOG.md b/CHANGELOG.md index dd854784a..64d1f3483 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,41 @@ - `trust` has been removed. Use `trusted_certificates` instead which expects `None` or a `list`. See the API documentation for more details. +- `neo4j.time` module: + - `Duration` + - The constructor does not accept `subseconds` anymore. + Use `milliseconds`, `microseconds`, or `nanoseconds` instead. + - The property `subseconds` has been removed. + Use `nanoseconds` instead. + - The property `hours_minutes_seconds` has been removed. + Use `hours_minutes_seconds_nanoseconds` instead. + - For all math operations holds: they are element-wise on + (`months`, `days`, `nanoseconds`). + This affects (i.e., changes) the working of `//`, `%`, `/`, and `*`. + - Years are equal to 12 months. + - Weeks are equal to 7 days. + - `seconds`, `milliseconds`, `microseconds`, and `nanoseconds` are + implicitly converted to `nanoseconds` or `seconds` as fit. + - Multiplication and division allow for floats but will always result in + integer values (round to nearest even). + - `Time` + - The constructor does not accept `float`s for `second` anymore. + Use `nanosecond` instead. + - Ticks are now nanoseconds since midnight (`int`). + - The property `ticks_ns` has been renamed to `ticks`. + The old `ticks` is no longer supported. + - The property`from_ticks_ns` has been renamed to `from_ticks`. + The old `from_ticks` is no longer supported. + - The property `second` returns an `int` instead of a `float`. + Use `nanosecond` to get the sub-second information. + - The property `hour_minute_second` has been removed. + Use `hour_minute_second_nanosecond` instead. + - `DateTime` + - The property `hour_minute_second` has been removed. + Use `hour_minute_second_nanosecond` instead. + - The property `second` returns an `int` instead of a `float`. + Use `nanosecond` to get the sub-second information. + ## Version 4.4 diff --git a/docs/source/temporal_types.rst b/docs/source/temporal_types.rst index 0d7d57e8e..ae5fe6dcd 100644 --- a/docs/source/temporal_types.rst +++ b/docs/source/temporal_types.rst @@ -142,8 +142,6 @@ Class methods .. automethod:: neo4j.time.Time.from_ticks -.. automethod:: neo4j.time.Time.from_ticks_ns - .. automethod:: neo4j.time.Time.from_native .. automethod:: neo4j.time.Time.from_clock_time @@ -164,15 +162,13 @@ Instance attributes .. autoattribute:: neo4j.time.Time.ticks -.. autoattribute:: neo4j.time.Time.ticks_ns - .. autoattribute:: neo4j.time.Time.hour .. autoattribute:: neo4j.time.Time.minute .. autoattribute:: neo4j.time.Time.second -.. autoattribute:: neo4j.time.Time.hour_minute_second +.. autoattribute:: neo4j.time.Time.nanosecond .. autoattribute:: neo4j.time.Time.hour_minute_second_nanosecond @@ -290,8 +286,6 @@ Instance attributes .. autoattribute:: neo4j.time.DateTime.tzinfo -.. autoattribute:: neo4j.time.DateTime.hour_minute_second - .. autoattribute:: neo4j.time.DateTime.hour_minute_second_nanosecond @@ -388,14 +382,10 @@ Instance attributes .. autoattribute:: neo4j.time.Duration.seconds -.. autoattribute:: neo4j.time.Duration.subseconds - .. autoattribute:: neo4j.time.Duration.nanoseconds .. autoattribute:: neo4j.time.Duration.years_months_days -.. autoattribute:: neo4j.time.Duration.hours_minutes_seconds - .. autoattribute:: neo4j.time.Duration.hours_minutes_seconds_nanoseconds @@ -410,6 +400,14 @@ Operations .. automethod:: neo4j.time.Duration.__mul__ +.. automethod:: neo4j.time.Duration.__truediv__ + +.. automethod:: neo4j.time.Duration.__floordiv__ + +.. automethod:: neo4j.time.Duration.__mod__ + +.. automethod:: neo4j.time.Duration.__divmod__ + .. automethod:: neo4j.time.Duration.__pos__ .. automethod:: neo4j.time.Duration.__neg__ diff --git a/neo4j/time/__init__.py b/neo4j/time/__init__.py index 4e070d7a1..50ad96f84 100644 --- a/neo4j/time/__init__.py +++ b/neo4j/time/__init__.py @@ -22,18 +22,12 @@ """ -from contextlib import contextmanager from datetime import ( date, datetime, time, timedelta, ) -from decimal import ( - Decimal, - localcontext, - ROUND_HALF_EVEN, -) from functools import total_ordering from re import compile as re_compile from time import ( @@ -42,10 +36,6 @@ struct_time, ) -from neo4j.meta import ( - deprecated, - deprecation_warn, -) from neo4j.time.arithmetic import ( nano_add, nano_div, @@ -59,24 +49,6 @@ ) -@contextmanager -def _decimal_context(prec=9, rounding=ROUND_HALF_EVEN): - with localcontext() as ctx: - ctx.prec = prec - ctx.rounding = rounding - yield ctx - - -def _decimal_context_decorator(prec=9): - def outer(fn): - def inner(*args, **kwargs): - with _decimal_context(prec=prec): - return fn(*args, **kwargs) - - return inner - return outer - - MIN_INT64 = -(2 ** 63) MAX_INT64 = (2 ** 63) - 1 @@ -331,12 +303,12 @@ class Duration(tuple): Duration objects store a composite value of `months`, `days`, `seconds`, and `nanoseconds`. Unlike :class:`datetime.timedelta` however, days, and seconds/nanoseconds are never interchanged. All values except seconds and - nanoseconds are applied separately in calculations. + nanoseconds are applied separately in calculations (element-wise). A :class:`.Duration` stores four primary instance attributes internally: `months`, `days`, `seconds` and `nanoseconds`. These are maintained as individual values and are immutable. Each of these four attributes can carry - its own siggn, with the exception of `nanoseconds`, which always has the same + its own sign, with the exception of `nanoseconds`, which always has the same sign as `seconds`. The constructor will establish this state, should the duration be initialized with conflicting `seconds` and `nanoseconds` signs. This structure allows the modelling of durations such as @@ -366,12 +338,6 @@ class Duration(tuple): :type minutes: float :param seconds: will be added times 1,000,000,000 to `nanoseconds`` :type seconds: float - :param subseconds: will be added times 1,000,000,000 to `nanosubseconds`` - - .. deprecated:: 4.4 - Will be removed in 5.0. Use `milliseconds`, `microseconds`, or - `nanoseconds` instead or add `subseconds` to `seconds` - :type subseconds: float :param milliseconds: will be added times 1,000,000 to `nanoseconds` :type microseconds: float :param microseconds: will be added times 1,000 to `nanoseconds` @@ -391,15 +357,7 @@ class Duration(tuple): """The highest duration value possible.""" def __new__(cls, years=0, months=0, weeks=0, days=0, hours=0, minutes=0, - seconds=0, subseconds=0, milliseconds=0, microseconds=0, - nanoseconds=0): - - if subseconds: - deprecation_warn("`subseconds` will be removed in 5.0. " - "Use `nanoseconds` instead.") - with _decimal_context(prec=9, rounding=ROUND_HALF_EVEN): - nanoseconds = int(Decimal(subseconds) * NANO_SECONDS) - + seconds=0, milliseconds=0, microseconds=0, nanoseconds=0): mo = int(12 * years + months) if mo < MIN_INT64 or mo > MAX_INT64: raise ValueError("Months value out of range") @@ -468,57 +426,95 @@ def __sub__(self, other): return NotImplemented def __mul__(self, other): - """Multiply by an :class:`int`. + """Multiply by an :class:`int` or :class:`float`. + + The operation is performed element-wise on + ``(months, days, nanaoseconds)`` where + + * years go into months, + * weeks go into days, + * seconds and all sub-second units go into nanoseconds. + + Each element will be rounded to the nearest integer (.5 towards even). :rtype: Duration """ - if isinstance(other, float): - deprecation_warn("Multiplication with float will be deprecated in " - "5.0.") if isinstance(other, (int, float)): return Duration( - months=self[0] * other, days=self[1] * other, - seconds=self[2] * other, nanoseconds=self[3] * other + months=round_half_to_even(self[0] * other), + days=round_half_to_even(self[1] * other), + nanoseconds=round_half_to_even( + self[2] * NANO_SECONDS * other + + self[3] * other + ) ) return NotImplemented - @deprecated("Will be removed in 5.0.") def __floordiv__(self, other): + """Integer division by an :class:`int`. + + The operation is performed element-wise on + ``(months, days, nanaoseconds)`` where + + * years go into months, + * weeks go into days, + * seconds and all sub-second units go into nanoseconds. + + Each element will be rounded towards -inf. + + :rtype: Duration + """ if isinstance(other, int): - # TODO 5.0: new method (floor months, days, nanoseconds) or remove - # return Duration( - # months=self[0] // other, days=self[1] // other, - # nanoseconds=(self[2] * NANO_SECONDS + self[3]) // other - # ) - seconds = self[2] + Decimal(self[3]) / NANO_SECONDS - return Duration(months=int(self[0] // other), - days=int(self[1] // other), - seconds=int(seconds // other)) + return Duration( + months=self[0] // other, days=self[1] // other, + nanoseconds=(self[2] * NANO_SECONDS + self[3]) // other + ) return NotImplemented - @deprecated("Will be removed in 5.0.") def __mod__(self, other): + """Modulo operation by an :class:`int`. + + The operation is performed element-wise on + ``(months, days, nanaoseconds)`` where + + * years go into months, + * weeks go into days, + * seconds and all sub-second units go into nanoseconds. + + :rtype: Duration + """ if isinstance(other, int): - # TODO 5.0: new method (mod months, days, nanoseconds) or remove - # return Duration( - # months=self[0] % other, days=self[1] % other, - # nanoseconds=(self[2] * NANO_SECONDS + self[3]) % other - # ) - seconds = self[2] + Decimal(self[3]) / NANO_SECONDS - seconds, subseconds = symmetric_divmod(seconds % other, 1) - return Duration(months=round_half_to_even(self[0] % other), - days=round_half_to_even(self[1] % other), - seconds=seconds, subseconds=subseconds) + return Duration( + months=self[0] % other, days=self[1] % other, + nanoseconds=(self[2] * NANO_SECONDS + self[3]) % other + ) return NotImplemented - @deprecated("Will be removed in 5.0.") def __divmod__(self, other): + """Division and modulo operation by an :class:`int`. + + See :meth:`__floordiv__` and :meth:`__mod__`. + + :rtype: (Duration, Duration) + """ if isinstance(other, int): return self.__floordiv__(other), self.__mod__(other) return NotImplemented - @deprecated("Will be removed in 5.0.") def __truediv__(self, other): + """Division by an :class:`int` or :class:`float`. + + The operation is performed element-wise on + ``(months, days, nanaoseconds)`` where + + * years go into months, + * weeks go into days, + * seconds and all sub-second units go into nanoseconds. + + Each element will be rounded to the nearest integer (.5 towards even). + + :rtype: Duration + """ if isinstance(other, (int, float)): return Duration( months=round_half_to_even(self[0] / other), @@ -530,8 +526,6 @@ def __truediv__(self, other): ) return NotImplemented - __div__ = __truediv__ - def __pos__(self): """""" return self @@ -676,21 +670,6 @@ def seconds(self): """ return self[2] - @property - @deprecated("Will be removed in 5.0. Use `nanoseconds` instead.") - def subseconds(self): - """The subseconds of the :class:`Duration`. - - .. deprecated:: 4.4 - Will be removed in 5.0. Use :attr:`.nanoseconds` instead. - - :type: decimal.Decimal - """ - if self[3] < 0: - return Decimal(("-0.%09i" % -self[3])[:11]) - else: - return Decimal(("0.%09i" % self[3])[:11]) - @property def nanoseconds(self): """The nanoseconds of the :class:`Duration`. @@ -708,26 +687,6 @@ def years_months_days(self): years, months = symmetric_divmod(self[0], 12) return years, months, self[1] - @property - @deprecated("Will be removed in 5.0. " - "Use `hours_minutes_seconds_nanoseconds` instead.") - def hours_minutes_seconds(self): - """A 3-tuple of (hours, minutes, seconds). - - Where seconds is a :class:`decimal.Decimal` that combines `seconds` and - `nanoseconds`. - - .. deprecated:: 4.4 - Will be removed in 5.0. - Use :attr:`.hours_minutes_seconds_nanoseconds` instead. - - :type: (int, int, decimal.Decimal) - """ - minutes, seconds = symmetric_divmod(self[2], 60) - hours, minutes = symmetric_divmod(minutes, 60) - with _decimal_context(prec=11): - return hours, minutes, seconds + self.subseconds - @property def hours_minutes_seconds_nanoseconds(self): """ A 4-tuple of (hours, minutes, seconds, nanoseconds). @@ -1393,31 +1352,22 @@ class Time(metaclass=TimeType): A high degree of API compatibility with the standard library classes is provided. - :class:`neo4j.time.Time` objects introduce the concept of `ticks`. - This is simply a count of the number of seconds since midnight, + :class:`neo4j.time.Time` objects introduce the concept of ``ticks``. + This is simply a count of the number of nanoseconds since midnight, in many ways analogous to the :class:`neo4j.time.Date` ordinal. - `ticks` values can be fractional, with a minimum value of `0` and a maximum - of `86399.999999999`. + `ticks` values are integers, with a minimum value of `0` and a maximum + of `86_399_999_999_999`. - Local times are represented by :class:`.Time` with no `tzinfo`. - - .. note:: - Staring with version 5.0, :attr:`.ticks` will change to be integers - counting nanoseconds since midnight. Currently available as - :attr:`.ticks_ns`. + Local times are represented by :class:`.Time` with no ``tzinfo``. :param hour: the hour of the time. Must be in range 0 <= hour < 24. :type hour: int :param minute: the minute of the time. Must be in range 0 <= minute < 60. :type minute: int - :param second: the second of the time. Here, a float is accepted to denote - sub-second precision up to nanoseconds. - Must be in range 0 <= second < 60. - - Starting with version 5 0, this parameter will only accept :class:`int`. - :type second: float - :param nanosecond: the nanosecond - .Must be in range 0 <= nanosecond < 999999999. + :param second: the second of the time. Must be in range 0 <= second < 60. + :type second: int + :param nanosecond: the nanosecond of the time. + Must be in range 0 <= nanosecond < 999999999. :type nanosecond: int :param tzinfo: timezone or None to get a local :class:`.Time`. :type tzinfo: datetime.tzinfo or None @@ -1542,30 +1492,6 @@ def from_iso_format(cls, s): @classmethod def from_ticks(cls, ticks, tz=None): - """Create a time from legacy ticks (seconds since midnight). - - .. deprecated:: 4.4 - Staring from 5.0, this method's signature will be replaced with that - of :meth:`.from_ticks_ns`. - - :param ticks: seconds since midnight - :type ticks: float - :param tz: optional timezone - :type tz: datetime.tzinfo - - :rtype: Time - - :raises ValueError: if ticks is out of bounds (0 <= ticks < 86400) - """ - if 0 <= ticks < 86400: - ticks = Decimal(ticks) * NANO_SECONDS - ticks = int(ticks.quantize(Decimal("1."), rounding=ROUND_HALF_EVEN)) - assert 0 <= ticks < 86400000000000 - return cls.from_ticks_ns(ticks, tz=tz) - raise ValueError("Ticks out of range (0..86400)") - - @classmethod - def from_ticks_ns(cls, ticks, tz=None): """Create a time from ticks (nanoseconds since midnight). :param ticks: nanoseconds since midnight @@ -1578,7 +1504,6 @@ def from_ticks_ns(cls, ticks, tz=None): :raises ValueError: if ticks is out of bounds (0 <= ticks < 86400000000000) """ - # TODO 5.0: this will become from_ticks if not isinstance(ticks, int): raise TypeError("Ticks must be int") if 0 <= ticks < 86400000000000: @@ -1618,8 +1543,8 @@ def from_clock_time(cls, clock_time, epoch): clock_time = ClockTime(*clock_time) ts = clock_time.seconds % 86400 nanoseconds = int(NANO_SECONDS * ts + clock_time.nanoseconds) - ticks = (epoch.time().ticks_ns + nanoseconds) % (86400 * NANO_SECONDS) - return Time.from_ticks_ns(ticks) + ticks = (epoch.time().ticks + nanoseconds) % (86400 * NANO_SECONDS) + return Time.from_ticks(ticks) @classmethod def __normalize_hour(cls, hour): @@ -1646,17 +1571,9 @@ def __normalize_second(cls, hour, minute, second): @classmethod def __normalize_nanosecond(cls, hour, minute, second, nanosecond): - # TODO 5.0: remove ----------------------------------------------------- - seconds, extra_ns = divmod(second, 1) - if extra_ns: - deprecation_warn("Float support second will be removed in 5.0. " - "Use `nanosecond` instead.") - # ---------------------------------------------------------------------- hour, minute, second = cls.__normalize_second(hour, minute, second) - nanosecond = int(nanosecond - + round_half_to_even(extra_ns * NANO_SECONDS)) if 0 <= nanosecond < NANO_SECONDS: - return hour, minute, second, nanosecond + extra_ns + return hour, minute, second, nanosecond raise ValueError("Nanosecond out of range (0..%s)" % (NANO_SECONDS - 1)) # CLASS ATTRIBUTES # @@ -1686,25 +1603,10 @@ def __normalize_nanosecond(cls, hour, minute, second, nanosecond): @property def ticks(self): - """The total number of seconds since midnight. - - .. deprecated:: 4.4 - will return :attr:`.ticks_ns` starting with version 5.0. - - :type: float - """ - with _decimal_context(prec=15): - return self.__ticks / NANO_SECONDS - - @property - def ticks_ns(self): """The total number of nanoseconds since midnight. - .. note:: This will be removed in 5.0 and replace :attr:`.ticks`. - :type: int """ - # TODO 5.0: this will replace self.ticks return self.__ticks @property @@ -1727,14 +1629,9 @@ def minute(self): def second(self): """The seconds of the time. - This contains seconds and nanoseconds of the time. - `int(:attr:`.seconds`)` will yield the seconds without nanoseconds. - - :type: float + :type: int """ - # TODO 5.0: return plain self.__second - with _decimal_context(prec=11): - return self.__second + Decimal(("0.%09i" % self.__nanosecond)[:11]) + return self.__second @property def nanosecond(self): @@ -1744,19 +1641,6 @@ def nanosecond(self): """ return self.__nanosecond - @property - @deprecated("hour_minute_second will be removed in 5.0. " - "Use `hour_minute_second_nanosecond` instead.") - def hour_minute_second(self): - """The time as a tuple of (hour, minute, second). - - .. deprecated: 4.4 - Will be removed in 5.0. - Use :attr:`.hour_minute_second_nanosecond` instead. - - :type: (int, int, float)""" - return self.__hour, self.__minute, self.second - @property def hour_minute_second_nanosecond(self): """The time as a tuple of (hour, minute, second, nanosecond). @@ -1786,7 +1670,7 @@ def __eq__(self, other): + 60000000000 * other.minute + NANO_SECONDS * other.second + 1000 * other.microsecond) - return self.ticks_ns == other_ticks and self.tzinfo == other.tzinfo + return self.ticks == other_ticks and self.tzinfo == other.tzinfo return False def __ne__(self, other): @@ -1797,48 +1681,48 @@ def __lt__(self, other): """`<` comparison with :class:`.Time` or :class:`datetime.time`.""" if isinstance(other, Time): return (self.tzinfo == other.tzinfo - and self.ticks_ns < other.ticks_ns) + and self.ticks < other.ticks) if isinstance(other, time): if self.tzinfo != other.tzinfo: return False other_ticks = 3600 * other.hour + 60 * other.minute + other.second + (other.microsecond / 1000000) - return self.ticks_ns < other_ticks + return self.ticks < other_ticks return NotImplemented def __le__(self, other): """`<=` comparison with :class:`.Time` or :class:`datetime.time`.""" if isinstance(other, Time): return (self.tzinfo == other.tzinfo - and self.ticks_ns <= other.ticks_ns) + and self.ticks <= other.ticks) if isinstance(other, time): if self.tzinfo != other.tzinfo: return False other_ticks = 3600 * other.hour + 60 * other.minute + other.second + (other.microsecond / 1000000) - return self.ticks_ns <= other_ticks + return self.ticks <= other_ticks return NotImplemented def __ge__(self, other): """`>=` comparison with :class:`.Time` or :class:`datetime.time`.""" if isinstance(other, Time): return (self.tzinfo == other.tzinfo - and self.ticks_ns >= other.ticks_ns) + and self.ticks >= other.ticks) if isinstance(other, time): if self.tzinfo != other.tzinfo: return False other_ticks = 3600 * other.hour + 60 * other.minute + other.second + (other.microsecond / 1000000) - return self.ticks_ns >= other_ticks + return self.ticks >= other_ticks return NotImplemented def __gt__(self, other): """`>` comparison with :class:`.Time` or :class:`datetime.time`.""" if isinstance(other, Time): return (self.tzinfo == other.tzinfo - and self.ticks_ns >= other.ticks_ns) + and self.ticks >= other.ticks) if isinstance(other, time): if self.tzinfo != other.tzinfo: return False other_ticks = 3600 * other.hour + 60 * other.minute + other.second + (other.microsecond / 1000000) - return self.ticks_ns >= other_ticks + return self.ticks >= other_ticks return NotImplemented def __copy__(self): @@ -1943,7 +1827,7 @@ def to_clock_time(self): :rtype: ClockTime """ - seconds, nanoseconds = divmod(self.ticks_ns, NANO_SECONDS) + seconds, nanoseconds = divmod(self.ticks, NANO_SECONDS) return ClockTime(seconds, nanoseconds) def to_native(self): @@ -1987,16 +1871,17 @@ def __format__(self, format_spec): """""" raise NotImplementedError() + Time.min = Time(hour=0, minute=0, second=0, nanosecond=0) Time.max = Time(hour=23, minute=59, second=59, nanosecond=999999999) Time.resolution = Duration(nanoseconds=1) #: A :class:`.Time` instance set to `00:00:00`. -#: This has a :attr:`.ticks_ns` value of `0`. +#: This has a :attr:`.ticks` value of `0`. Midnight = Time.min #: A :class:`.Time` instance set to `12:00:00`. -#: This has a :attr:`.ticks_ns` value of `43200000000000`. +#: This has a :attr:`.ticks` value of `43200000000000`. Midday = Time(hour=12) @@ -2202,11 +2087,11 @@ def from_clock_time(cls, clock_time, epoch): raise ValueError("Clock time must be a 2-tuple of (s, ns)") else: ordinal, seconds = divmod(seconds, 86400) - ticks = epoch.time().ticks_ns + seconds * NANO_SECONDS + nanoseconds + ticks = epoch.time().ticks + seconds * NANO_SECONDS + nanoseconds days, ticks = divmod(ticks, 86400 * NANO_SECONDS) ordinal += days date_ = Date.from_ordinal(ordinal + epoch.date().to_ordinal()) - time_ = Time.from_ticks_ns(ticks) + time_ = Time.from_ticks(ticks) return cls.combine(date_, time_) # CLASS ATTRIBUTES # @@ -2300,13 +2185,6 @@ def tzinfo(self): See :attr:`.Time.tzinfo`.""" return self.__time.tzinfo - @property - def hour_minute_second(self): - """The hour_minute_second of the :class:`.DateTime`'s time. - - See :attr:`.Time.hour_minute_second`.""" - return self.__time.hour_minute_second - @property def hour_minute_second_nanosecond(self): """The hour_minute_second_nanosecond of the :class:`.DateTime`'s time. @@ -2389,7 +2267,7 @@ def __add__(self, other): other.microseconds * 1000)) days, seconds = symmetric_divmod(t.seconds, 86400) date_ = Date.from_ordinal(days + 1) - time_ = Time.from_ticks_ns(round_half_to_even( + time_ = Time.from_ticks(round_half_to_even( seconds * NANO_SECONDS + t.nanoseconds )) return self.combine(date_, time_) @@ -2529,7 +2407,7 @@ def to_clock_time(self): for month in range(1, self.month): total_seconds += 86400 * Date.days_in_month(self.year, month) total_seconds += 86400 * (self.day - 1) - seconds, nanoseconds = divmod(self.__time.ticks_ns, NANO_SECONDS) + seconds, nanoseconds = divmod(self.__time.ticks, NANO_SECONDS) return ClockTime(total_seconds + seconds, nanoseconds) def to_native(self): diff --git a/neo4j/time/hydration.py b/neo4j/time/hydration.py index f522ebd78..deecbcc09 100644 --- a/neo4j/time/hydration.py +++ b/neo4j/time/hydration.py @@ -90,7 +90,7 @@ def dehydrate_time(value): :return: """ if isinstance(value, Time): - nanoseconds = value.ticks_ns + nanoseconds = value.ticks elif isinstance(value, time): nanoseconds = (3600000000000 * value.hour + 60000000000 * value.minute + 1000000000 * value.second + 1000 * value.microsecond) diff --git a/tests/integration/test_temporal_types.py b/tests/integration/test_temporal_types.py index e9e64fb67..b3f3be995 100644 --- a/tests/integration/test_temporal_types.py +++ b/tests/integration/test_temporal_types.py @@ -86,7 +86,7 @@ def test_whole_second_time_input(cypher_eval): def test_nanosecond_resolution_time_input(cypher_eval): result = cypher_eval("CYPHER runtime=interpreted WITH $x AS x " "RETURN [x.hour, x.minute, x.second, x.nanosecond]", - x=Time(12, 34, 56.789012345)) + x=Time(12, 34, 56, 789012345)) hour, minute, second, nanosecond = result assert hour == 12 assert minute == 34 @@ -98,7 +98,7 @@ def test_time_with_numeric_time_offset_input(cypher_eval): result = cypher_eval("CYPHER runtime=interpreted WITH $x AS x " "RETURN [x.hour, x.minute, x.second, " " x.nanosecond, x.offset]", - x=Time(12, 34, 56.789012345, tzinfo=FixedOffset(90))) + x=Time(12, 34, 56, 789012345, tzinfo=FixedOffset(90))) hour, minute, second, nanosecond, offset = result assert hour == 12 assert minute == 34 @@ -147,7 +147,7 @@ def test_nanosecond_resolution_datetime_input(cypher_eval): result = cypher_eval("CYPHER runtime=interpreted WITH $x AS x " "RETURN [x.year, x.month, x.day, " " x.hour, x.minute, x.second, x.nanosecond]", - x=DateTime(1976, 6, 13, 12, 34, 56.789012345)) + x=DateTime(1976, 6, 13, 12, 34, 56, 789012345)) year, month, day, hour, minute, second, nanosecond = result assert year == 1976 assert month == 6 @@ -163,7 +163,7 @@ def test_datetime_with_numeric_time_offset_input(cypher_eval): "RETURN [x.year, x.month, x.day, " " x.hour, x.minute, x.second, " " x.nanosecond, x.offset]", - x=DateTime(1976, 6, 13, 12, 34, 56.789012345, + x=DateTime(1976, 6, 13, 12, 34, 56, 789012345, tzinfo=FixedOffset(90))) year, month, day, hour, minute, second, nanosecond, offset = result assert year == 1976 @@ -196,7 +196,7 @@ def test_datetime_with_named_time_zone_input(cypher_eval): def test_datetime_array_input(cypher_eval): - data = [DateTime(2018, 4, 6, 13, 4, 42.516120), DateTime(1976, 6, 13)] + data = [DateTime(2018, 4, 6, 13, 4, 42, 516120), DateTime(1976, 6, 13)] value = cypher_eval("CREATE (a {x:$x}) RETURN a.x", x=data) assert value == data @@ -255,13 +255,13 @@ def test_whole_second_time_output(cypher_eval): def test_nanosecond_resolution_time_output(cypher_eval): value = cypher_eval("RETURN time('12:34:56.789012345')") assert isinstance(value, Time) - assert value == Time(12, 34, 56.789012345, tzinfo=FixedOffset(0)) + assert value == Time(12, 34, 56, 789012345, tzinfo=FixedOffset(0)) def test_time_with_numeric_time_offset_output(cypher_eval): value = cypher_eval("RETURN time('12:34:56.789012345+0130')") assert isinstance(value, Time) - assert value == Time(12, 34, 56.789012345, tzinfo=FixedOffset(90)) + assert value == Time(12, 34, 56, 789012345, tzinfo=FixedOffset(90)) def test_whole_second_localtime_output(cypher_eval): @@ -273,7 +273,7 @@ def test_whole_second_localtime_output(cypher_eval): def test_nanosecond_resolution_localtime_output(cypher_eval): value = cypher_eval("RETURN localtime('12:34:56.789012345')") assert isinstance(value, Time) - assert value == Time(12, 34, 56.789012345) + assert value == Time(12, 34, 56, 789012345) def test_whole_second_datetime_output(cypher_eval): @@ -285,14 +285,14 @@ def test_whole_second_datetime_output(cypher_eval): def test_nanosecond_resolution_datetime_output(cypher_eval): value = cypher_eval("RETURN datetime('1976-06-13T12:34:56.789012345')") assert isinstance(value, DateTime) - assert value == DateTime(1976, 6, 13, 12, 34, 56.789012345, tzinfo=utc) + assert value == DateTime(1976, 6, 13, 12, 34, 56, 789012345, tzinfo=utc) def test_datetime_with_numeric_time_offset_output(cypher_eval): value = cypher_eval("RETURN " "datetime('1976-06-13T12:34:56.789012345+01:30')") assert isinstance(value, DateTime) - assert value == DateTime(1976, 6, 13, 12, 34, 56.789012345, + assert value == DateTime(1976, 6, 13, 12, 34, 56, 789012345, tzinfo=FixedOffset(90)) @@ -300,7 +300,7 @@ def test_datetime_with_named_time_zone_output(cypher_eval): value = cypher_eval("RETURN datetime('1976-06-13T12:34:56.789012345" "[Europe/London]')") assert isinstance(value, DateTime) - dt = DateTime(1976, 6, 13, 12, 34, 56.789012345) + dt = DateTime(1976, 6, 13, 12, 34, 56, 789012345) assert value == timezone("Europe/London").localize(dt) @@ -314,7 +314,7 @@ def test_nanosecond_resolution_localdatetime_output(cypher_eval): value = cypher_eval("RETURN " "localdatetime('1976-06-13T12:34:56.789012345')") assert isinstance(value, DateTime) - assert value == DateTime(1976, 6, 13, 12, 34, 56.789012345) + assert value == DateTime(1976, 6, 13, 12, 34, 56, 789012345) def test_duration_output(cypher_eval): @@ -328,7 +328,7 @@ def test_nanosecond_resolution_duration_output(cypher_eval): value = cypher_eval("RETURN duration('P1Y2M3DT4H5M6.789123456S')") assert isinstance(value, Duration) assert value == Duration(years=1, months=2, days=3, hours=4, - minutes=5, seconds=6.789123456) + minutes=5, seconds=6, nanoseconds=789123456) def test_datetime_parameter_case1(session): diff --git a/tests/unit/common/time/test_datetime.py b/tests/unit/common/time/test_datetime.py index ffc551e68..8e2b12e50 100644 --- a/tests/unit/common/time/test_datetime.py +++ b/tests/unit/common/time/test_datetime.py @@ -74,24 +74,27 @@ def utc_time(self): class TestDateTime: - def test_zero(self): - t = DateTime(0, 0, 0, 0, 0, 0) + @pytest.mark.parametrize("args", ( + (0, 0, 0), (0, 0, 0, 0, 0, 0, 0) + )) + def test_zero(self, args): + t = DateTime(*args) assert t.year == 0 assert t.month == 0 assert t.day == 0 assert t.hour == 0 assert t.minute == 0 assert t.second == 0 + assert t.nanosecond == 0 - @pytest.mark.parametrize("seconds_args", [*seconds_options(17, 914390409)]) - def test_non_zero_naive(self, seconds_args): - t = DateTime(2018, 4, 26, 23, 0, *seconds_args) + def test_non_zero_naive(self): + t = DateTime(2018, 4, 26, 23, 0, 17, 914390409) assert t.year == 2018 assert t.month == 4 assert t.day == 26 assert t.hour == 23 assert t.minute == 0 - assert t.second == Decimal("17.914390409") + assert t.second == 17 assert t.nanosecond == 914390409 def test_year_lower_bound(self): @@ -143,7 +146,7 @@ def test_today(self): assert t.day == 1 assert t.hour == 12 assert t.minute == 34 - assert t.second == Decimal("56.789000000") + assert t.second == 56 assert t.nanosecond == 789000000 def test_now_without_tz(self): @@ -153,7 +156,7 @@ def test_now_without_tz(self): assert t.day == 1 assert t.hour == 12 assert t.minute == 34 - assert t.second == Decimal("56.789000000") + assert t.second == 56 assert t.nanosecond == 789000000 assert t.tzinfo is None @@ -164,7 +167,7 @@ def test_now_with_tz(self): assert t.day == 1 assert t.hour == 7 assert t.minute == 34 - assert t.second == Decimal("56.789000000") + assert t.second == 56 assert t.nanosecond == 789000000 assert t.utcoffset() == timedelta(seconds=-18000) assert t.dst() == timedelta() @@ -177,7 +180,7 @@ def test_utc_now(self): assert t.day == 1 assert t.hour == 12 assert t.minute == 34 - assert t.second == Decimal("56.789000000") + assert t.second == 56 assert t.nanosecond == 789000000 assert t.tzinfo is None @@ -188,7 +191,7 @@ def test_from_timestamp(self): assert t.day == 1 assert t.hour == 0 assert t.minute == 0 - assert t.second == Decimal("0.0") + assert t.second == 0 assert t.nanosecond == 0 assert t.tzinfo is None @@ -203,7 +206,7 @@ def test_from_timestamp_with_tz(self): assert t.day == 31 assert t.hour == 19 assert t.minute == 0 - assert t.second == Decimal("0.0") + assert t.second == 0 assert t.nanosecond == 0 assert t.utcoffset() == timedelta(seconds=-18000) assert t.dst() == timedelta() @@ -223,9 +226,8 @@ def test_add_timedelta(self, seconds_args1, seconds_args2): dt2 = dt1 + delta assert dt2, DateTime(2018, 4, 27, 23, 0 == seconds_args2) - @pytest.mark.parametrize("seconds_args", seconds_options(17, 914390409)) - def test_subtract_datetime_1(self, seconds_args): - dt1 = DateTime(2018, 4, 26, 23, 0, *seconds_args) + def test_subtract_datetime_1(self): + dt1 = DateTime(2018, 4, 26, 23, 0, 17, 914390409) dt2 = DateTime(2018, 1, 1, 0, 0, 0) t = dt1 - dt2 @@ -233,25 +235,22 @@ def test_subtract_datetime_1(self, seconds_args): assert t == Duration(months=3, days=25, hours=23, seconds=17, nanoseconds=914390409) - @pytest.mark.parametrize("seconds_args", seconds_options(17, 914390409)) - def test_subtract_datetime_2(self, seconds_args): - dt1 = DateTime(2018, 4, 1, 23, 0, *seconds_args) + def test_subtract_datetime_2(self): + dt1 = DateTime(2018, 4, 1, 23, 0, 17, 914390409) dt2 = DateTime(2018, 1, 26, 0, 0, 0.0) t = dt1 - dt2 assert t == Duration(months=3, days=-25, hours=23, seconds=17.914390409) assert t == Duration(months=3, days=-25, hours=23, seconds=17, nanoseconds=914390409) - @pytest.mark.parametrize("seconds_args", seconds_options(17, 914390409)) - def test_subtract_native_datetime_1(self, seconds_args): - dt1 = DateTime(2018, 4, 26, 23, 0, *seconds_args) + def test_subtract_native_datetime_1(self): + dt1 = DateTime(2018, 4, 26, 23, 0, 17, 914390409) dt2 = datetime(2018, 1, 1, 0, 0, 0) t = dt1 - dt2 assert t == timedelta(days=115, hours=23, seconds=17.914390409) - @pytest.mark.parametrize("seconds_args", seconds_options(17, 914390409)) - def test_subtract_native_datetime_2(self, seconds_args): - dt1 = DateTime(2018, 4, 1, 23, 0, *seconds_args) + def test_subtract_native_datetime_2(self): + dt1 = DateTime(2018, 4, 1, 23, 0, 17, 914390409) dt2 = datetime(2018, 1, 26, 0, 0, 0) t = dt1 - dt2 assert t == timedelta(days=65, hours=23, seconds=17.914390409) @@ -274,9 +273,7 @@ def test_from_native(self): assert dt.day == native.day assert dt.hour == native.hour assert dt.minute == native.minute - assert dt.second == (native.second - + Decimal(native.microsecond) / 1000000) - assert int(dt.second) == native.second + assert dt.second == native.second assert dt.nanosecond == native.microsecond * 1000 def test_to_native(self): @@ -287,22 +284,25 @@ def test_to_native(self): assert dt.day == native.day assert dt.hour == native.hour assert dt.minute == native.minute - assert 56.789123, nano_add(native.second, nano_div(native.microsecond == 1000000)) + assert dt.second == native.second + assert dt.nanosecond == native.microsecond * 1000 def test_iso_format(self): - dt = DateTime(2018, 10, 1, 12, 34, 56.789123456) + dt = DateTime(2018, 10, 1, 12, 34, 56, 789123456) assert "2018-10-01T12:34:56.789123456" == dt.iso_format() def test_iso_format_with_trailing_zeroes(self): - dt = DateTime(2018, 10, 1, 12, 34, 56.789) + dt = DateTime(2018, 10, 1, 12, 34, 56, 789000000) assert "2018-10-01T12:34:56.789000000" == dt.iso_format() def test_iso_format_with_tz(self): - dt = timezone_us_eastern.localize(DateTime(2018, 10, 1, 12, 34, 56.789123456)) + dt = timezone_us_eastern.localize(DateTime(2018, 10, 1, 12, 34, 56, + 789123456)) assert "2018-10-01T12:34:56.789123456-04:00" == dt.iso_format() def test_iso_format_with_tz_and_trailing_zeroes(self): - dt = timezone_us_eastern.localize(DateTime(2018, 10, 1, 12, 34, 56.789)) + dt = timezone_us_eastern.localize(DateTime(2018, 10, 1, 12, 34, 56, + 789000000)) assert "2018-10-01T12:34:56.789000000-04:00" == dt.iso_format() def test_from_iso_format_hour_only(self): @@ -374,7 +374,7 @@ def test_datetime_deep_copy(self): def test_iso_format_with_time_zone_case_1(): # python -m pytest tests/unit/time/test_datetime.py -s -v -k test_iso_format_with_time_zone_case_1 - expected = DateTime(2019, 10, 30, 7, 54, 2.129790999, tzinfo=timezone_utc) + expected = DateTime(2019, 10, 30, 7, 54, 2, 129790999, tzinfo=timezone_utc) assert expected.iso_format() == "2019-10-30T07:54:02.129790999+00:00" assert expected.tzinfo == FixedOffset(0) actual = DateTime.from_iso_format("2019-10-30T07:54:02.129790999+00:00") @@ -431,9 +431,7 @@ def test_from_native_case_1(): assert dt.day == native.day assert dt.hour == native.hour assert dt.minute == native.minute - assert dt.second == (native.second - + Decimal(native.microsecond) / 1000000) - assert int(dt.second) == native.second + assert dt.second == native.second assert dt.nanosecond == native.microsecond * 1000 assert dt.tzinfo is None @@ -447,8 +445,6 @@ def test_from_native_case_2(): assert dt.day == native.day assert dt.hour == native.hour assert dt.minute == native.minute - assert dt.second == (native.second - + Decimal(native.microsecond) / 1000000) - assert int(dt.second) == native.second + assert dt.second == native.second assert dt.nanosecond == native.microsecond * 1000 assert dt.tzinfo == FixedOffset(0) diff --git a/tests/unit/common/time/test_duration.py b/tests/unit/common/time/test_duration.py index 5edc2c9c0..279b287f0 100644 --- a/tests/unit/common/time/test_duration.py +++ b/tests/unit/common/time/test_duration.py @@ -26,12 +26,6 @@ from neo4j.time import Duration -def seconds_options(seconds, nanoseconds): - yield {"seconds": seconds, "nanoseconds": nanoseconds} - yield {"seconds": seconds, "subseconds": nanoseconds / 1000000000} - yield {"seconds": seconds + Decimal(nanoseconds) / 1000000000} - - class TestDuration: def test_zero(self): @@ -41,7 +35,6 @@ def test_zero(self): assert d.seconds == 0 assert d.nanoseconds == 0 assert d.years_months_days == (0, 0, 0) - assert d.hours_minutes_seconds == (0, 0, Decimal("0E-9")) assert d.hours_minutes_seconds_nanoseconds == (0, 0, 0, 0) assert not bool(d) @@ -52,7 +45,6 @@ def test_years_only(self): assert d.seconds == 0 assert d.nanoseconds == 0 assert d.years_months_days == (2, 0, 0) - assert d.hours_minutes_seconds == (0, 0, Decimal("0E-9")) assert d.hours_minutes_seconds_nanoseconds == (0, 0, 0, 0) assert bool(d) @@ -63,7 +55,6 @@ def test_months_only(self): assert d.seconds == 0 assert d.nanoseconds == 0 assert d.years_months_days == (1, 8, 0) - assert d.hours_minutes_seconds == (0, 0, Decimal("0E-9")) assert d.hours_minutes_seconds_nanoseconds == (0, 0, 0, 0) assert bool(d) @@ -78,7 +69,6 @@ def test_weeks_only(self): assert d.seconds == 0 assert d.nanoseconds == 0 assert d.years_months_days == (0, 0, 28) - assert d.hours_minutes_seconds == (0, 0, Decimal("0E-9")) assert d.hours_minutes_seconds_nanoseconds == (0, 0, 0, 0) assert bool(d) @@ -89,7 +79,6 @@ def test_days_only(self): assert d.seconds == 0 assert d.nanoseconds == 0 assert d.years_months_days == (0, 0, 40) - assert d.hours_minutes_seconds == (0, 0, Decimal("0E-9")) assert d.hours_minutes_seconds_nanoseconds == (0, 0, 0, 0) assert bool(d) @@ -104,7 +93,6 @@ def test_hours_only(self): assert d.seconds == 36000 assert d.nanoseconds == 0 assert d.years_months_days == (0, 0, 0) - assert d.hours_minutes_seconds == (10, 0, Decimal("0E-9")) assert d.hours_minutes_seconds_nanoseconds == (10, 0, 0, 0) assert bool(d) @@ -115,20 +103,16 @@ def test_minutes_only(self): assert d.seconds == 5430 assert d.nanoseconds == 0 assert d.years_months_days == (0, 0, 0) - assert d.hours_minutes_seconds == (1, 30, Decimal("30.000000000")) assert d.hours_minutes_seconds_nanoseconds == (1, 30, 30, 0) assert bool(d) - @pytest.mark.parametrize("sec_kwargs", seconds_options(123, 456000000)) - def test_seconds_only(self, sec_kwargs): - d = Duration(**sec_kwargs) + def test_seconds_only(self): + d = Duration(seconds=123, nanoseconds=456000000) assert d.months == 0 assert d.days == 0 assert d.seconds == 123 - assert d.subseconds == Decimal("0.456000000") assert d.nanoseconds == 456000000 assert d.years_months_days == (0, 0, 0) - assert d.hours_minutes_seconds == (0, 2, Decimal("3.456000000")) assert d.hours_minutes_seconds_nanoseconds == (0, 2, 3, 456000000) assert bool(d) @@ -136,27 +120,13 @@ def test_seconds_out_of_range(self): with pytest.raises(ValueError): _ = Duration(seconds=(2**64)) - def test_subseconds_only(self): - d = Duration(subseconds=123.456) - assert d.months == 0 - assert d.days == 0 - assert d.seconds == 123 - assert d.subseconds == Decimal("0.456") - assert d.nanoseconds == 456000000 - assert d.years_months_days == (0, 0, 0) - assert d.hours_minutes_seconds == (0, 2, Decimal("3.456")) - assert d.hours_minutes_seconds_nanoseconds == (0, 2, 3, 456000000) - assert bool(d) - def test_milliseconds_only(self): d = Duration(milliseconds=1234.567) assert d.months == 0 assert d.days == 0 assert d.seconds == 1 - assert d.subseconds == Decimal("0.234567000") assert d.nanoseconds == 234567000 assert d.years_months_days == (0, 0, 0) - assert d.hours_minutes_seconds == (0, 0, Decimal("1.234567000")) assert d.hours_minutes_seconds_nanoseconds == (0, 0, 1, 234567000) assert bool(d) @@ -165,10 +135,8 @@ def test_microseconds_only(self): assert d.months == 0 assert d.days == 0 assert d.seconds == 0 - assert d.subseconds == Decimal("0.001234567") assert d.nanoseconds == 1234567 assert d.years_months_days == (0, 0, 0) - assert d.hours_minutes_seconds == (0, 0, Decimal("0.001234567")) assert d.hours_minutes_seconds_nanoseconds == (0, 0, 0, 1234567) assert bool(d) @@ -177,10 +145,8 @@ def test_nanoseconds_only(self): assert d.months == 0 assert d.days == 0 assert d.seconds == 0 - assert d.subseconds == Decimal("0.000001234") assert d.nanoseconds == 1234 assert d.years_months_days == (0, 0, 0) - assert d.hours_minutes_seconds == (0, 0, Decimal("0.000001234")) assert d.hours_minutes_seconds_nanoseconds == (0, 0, 0, 1234) assert bool(d) @@ -210,7 +176,6 @@ def test_full_positive(self): assert d.seconds == 14706 assert d.nanoseconds == 789000000 assert d.years_months_days == (1, 2, 3) - assert d.hours_minutes_seconds == (4, 5, Decimal("6.789000000")) assert d.hours_minutes_seconds_nanoseconds == (4, 5, 6, 789000000) assert bool(d) @@ -221,7 +186,6 @@ def test_full_negative(self): assert d.seconds == -14706 assert d.nanoseconds == -789000000 assert d.years_months_days == (-1, -2, -3) - assert d.hours_minutes_seconds == (-4, -5, Decimal("-6.789")) assert d.hours_minutes_seconds_nanoseconds == (-4, -5, -6, -789000000) assert bool(d) @@ -230,10 +194,8 @@ def test_negative_positive(self): assert d.months == -14 assert d.days == 3 assert d.seconds == -14706 - assert d.subseconds == Decimal("-0.789") assert d.nanoseconds == -789000000 assert d.years_months_days == (-1, -2, 3) - assert d.hours_minutes_seconds == (-4, -5, Decimal("-6.789")) assert d.hours_minutes_seconds_nanoseconds == (-4, -5, -6, -789000000) def test_positive_negative(self): @@ -241,45 +203,37 @@ def test_positive_negative(self): assert d.months == 14 assert d.days == -3 assert d.seconds == 14706 - assert d.subseconds == Decimal("0.789") assert d.nanoseconds == 789000000 assert d.years_months_days == (1, 2, -3) - assert d.hours_minutes_seconds == (4, 5, Decimal("6.789")) assert d.hours_minutes_seconds_nanoseconds == (4, 5, 6, 789000000) - @pytest.mark.parametrize("sec1_kwargs", seconds_options(5, 700000000)) - @pytest.mark.parametrize("sec2_kwargs", seconds_options(3, 200000000)) - @pytest.mark.parametrize("sec3_kwargs", seconds_options(8, 900000000)) - def test_add_duration(self, sec1_kwargs, sec2_kwargs, sec3_kwargs): - d1 = Duration(months=2, days=3, **sec1_kwargs) - d2 = Duration(months=7, days=5, **sec2_kwargs) - assert d1 + d2 == Duration(months=9, days=8, **sec3_kwargs) - - @pytest.mark.parametrize("sec1_kwargs", seconds_options(5, 700000000)) - @pytest.mark.parametrize("sec2_kwargs", seconds_options(8, 900000000)) - def test_add_timedelta(self, sec1_kwargs, sec2_kwargs): - d1 = Duration(months=2, days=3, **sec1_kwargs) + def test_add_duration(self): + d1 = Duration(months=2, days=3, seconds=5, nanoseconds=700000000) + d2 = Duration(months=7, days=5, seconds=3, nanoseconds=200000000) + d3 = Duration(months=9, days=8, seconds=8, nanoseconds=900000000) + assert d1 + d2 == d3 + + def test_add_timedelta(self): + d1 = Duration(months=2, days=3, seconds=5, nanoseconds=700000000) td = timedelta(days=5, seconds=3.2) - assert d1 + td == Duration(months=2, days=8, **sec2_kwargs) + d3 = Duration(months=2, days=8, seconds=8, nanoseconds=900000000) + assert d1 + td == d3 def test_add_object(self): with pytest.raises(TypeError): _ = Duration(months=2, days=3, seconds=5.7) + object() - @pytest.mark.parametrize("sec1_kwargs", seconds_options(5, 700000000)) - @pytest.mark.parametrize("sec2_kwargs", seconds_options(3, 200000000)) - @pytest.mark.parametrize("sec3_kwargs", seconds_options(2, 500000000)) - def test_subtract_duration(self, sec1_kwargs, sec2_kwargs, sec3_kwargs): - d1 = Duration(months=2, days=3, **sec1_kwargs) - d2 = Duration(months=7, days=5, **sec2_kwargs) - assert d1 - d2 == Duration(months=-5, days=-2, **sec3_kwargs) - - @pytest.mark.parametrize("sec1_kwargs", seconds_options(5, 700000000)) - @pytest.mark.parametrize("sec2_kwargs", seconds_options(2, 500000000)) - def test_subtract_timedelta(self, sec1_kwargs, sec2_kwargs): - d1 = Duration(months=2, days=3, **sec1_kwargs) + def test_subtract_duration(self): + d1 = Duration(months=2, days=3, seconds=5, nanoseconds=700000000) + d2 = Duration(months=7, days=5, seconds=3, nanoseconds=200000000) + d3 = Duration(months=-5, days=-2, seconds=2, nanoseconds=500000000) + assert d1 - d2 == d3 + + def test_subtract_timedelta(self): + d1 = Duration(months=2, days=3, seconds=5, nanoseconds=700000000) td = timedelta(days=5, seconds=3.2) - assert d1 - td == Duration(months=2, days=-2, **sec2_kwargs) + d3 = Duration(months=2, days=-2, seconds=2, nanoseconds=500000000) + assert d1 - td == d3 def test_subtract_object(self): with pytest.raises(TypeError): @@ -299,34 +253,57 @@ def test_multiplication_by_object(self): with pytest.raises(TypeError): _ = Duration(months=2, days=3, seconds=5.7) * object() - def test_floor_division_by_int(self): - d1 = Duration(months=11, days=33, seconds=55.77) + @pytest.mark.parametrize("ns", (0, 1)) + def test_floor_division_by_int(self, ns): + d1 = Duration(months=11, days=33, seconds=55.77, nanoseconds=ns) i = 2 - assert d1 // i == Duration(months=5, days=16, seconds=27) + assert d1 // i == Duration(months=5, days=16, seconds=27, + nanoseconds=885000000) def test_floor_division_by_object(self): with pytest.raises(TypeError): _ = Duration(months=2, days=3, seconds=5.7) // object() - def test_modulus_by_int(self): - d1 = Duration(months=11, days=33, seconds=55.77) + @pytest.mark.parametrize("ns", (0, 1)) + def test_modulus_by_int(self, ns): + d1 = Duration(months=11, days=33, seconds=55.77, nanoseconds=ns) i = 2 - assert d1 % i == Duration(months=1, days=1, seconds=1.77) + assert d1 % i == Duration(months=1, days=1, nanoseconds=ns) def test_modulus_by_object(self): with pytest.raises(TypeError): _ = Duration(months=2, days=3, seconds=5.7) % object() - def test_floor_division_and_modulus_by_int(self): - d1 = Duration(months=11, days=33, seconds=55.77) + @pytest.mark.parametrize("ns", (0, 1)) + def test_floor_division_and_modulus_by_int(self, ns): + d1 = Duration(months=11, days=33, seconds=55.77, nanoseconds=ns) i = 2 - assert divmod(d1, i) == (Duration(months=5, days=16, seconds=27.0), - Duration(months=1, days=1, seconds=1.77)) + assert divmod(d1, i) == (Duration(months=5, days=16, seconds=27, + nanoseconds=885000000), + Duration(months=1, days=1, nanoseconds=ns)) def test_floor_division_and_modulus_by_object(self): with pytest.raises(TypeError): _ = divmod(Duration(months=2, days=3, seconds=5.7), object()) + @pytest.mark.parametrize( + ("year", "month", "day"), + ( + *((i, 0, 0) for i in range(4)), + *((0, i, 0) for i in range(4)), + *((0, 0, i) for i in range(4)), + ) + ) + @pytest.mark.parametrize("second", range(4)) + @pytest.mark.parametrize("ns", range(4)) + @pytest.mark.parametrize("divisor", (*range(1, 3), 1_000_000_000)) + def test_div_mod_is_well_defined(self, year, month, day, second, ns, + divisor): + d1 = Duration(years=year, months=month, days=day, seconds=second, + nanoseconds=ns) + fraction, rest = divmod(d1, divisor) + assert d1 == fraction * divisor + rest + def test_true_division_by_int(self): d1 = Duration(months=11, days=33, seconds=55.77) i = 2 diff --git a/tests/unit/common/time/test_hydration.py b/tests/unit/common/time/test_hydration.py index 0efe868d1..033125dd6 100644 --- a/tests/unit/common/time/test_hydration.py +++ b/tests/unit/common/time/test_hydration.py @@ -16,11 +16,18 @@ # limitations under the License. -from decimal import Decimal from unittest import TestCase +import pytz + from neo4j.data import DataHydrator from neo4j.packstream import Structure +from neo4j.time import ( + Date, + DateTime, + Duration, + Time, +) class TestTemporalHydration(TestCase): @@ -28,12 +35,80 @@ class TestTemporalHydration(TestCase): def setUp(self): self.hydrant = DataHydrator() - def test_can_hydrate_date_time_structure(self): - struct = Structure(b'd', 1539344261, 474716862) + def test_hydrate_date_structure(self): + struct = Structure(b"D", 7905) + d, = self.hydrant.hydrate([struct]) + assert isinstance(d, Date) + assert d.year == 1991 + assert d.month == 8 + assert d.day == 24 + + def test_hydrate_time_structure(self): + struct = Structure(b"T", 3723000000004, 3600) + t, = self.hydrant.hydrate([struct]) + assert isinstance(t, Time) + assert t.hour == 1 + assert t.minute == 2 + assert t.second == 3 + assert t.nanosecond == 4 + assert t.tzinfo == pytz.FixedOffset(60) + + def test_hydrate_local_time_structure(self): + struct = Structure(b"t", 3723000000004) + t, = self.hydrant.hydrate([struct]) + assert isinstance(t, Time) + assert t.hour == 1 + assert t.minute == 2 + assert t.second == 3 + assert t.nanosecond == 4 + assert t.tzinfo is None + + def test_hydrate_date_time_structure(self): + struct = Structure(b"F", 1539344261, 474716862, 3600) dt, = self.hydrant.hydrate([struct]) - self.assertEqual(dt.year, 2018) - self.assertEqual(dt.month, 10) - self.assertEqual(dt.day, 12) - self.assertEqual(dt.hour, 11) - self.assertEqual(dt.minute, 37) - self.assertEqual(dt.second, Decimal("41.474716862")) + assert isinstance(dt, DateTime) + assert dt.year == 2018 + assert dt.month == 10 + assert dt.day == 12 + assert dt.hour == 11 + assert dt.minute == 37 + assert dt.second == 41 + assert dt.nanosecond == 474716862 + assert dt.tzinfo == pytz.FixedOffset(60) + + def test_hydrate_date_time_zone_id_structure(self): + struct = Structure(b"f", 1539344261, 474716862, "Europe/Stockholm") + dt, = self.hydrant.hydrate([struct]) + assert isinstance(dt, DateTime) + assert dt.year == 2018 + assert dt.month == 10 + assert dt.day == 12 + assert dt.hour == 11 + assert dt.minute == 37 + assert dt.second == 41 + assert dt.nanosecond == 474716862 + tz = pytz.timezone("Europe/Stockholm") \ + .localize(dt.replace(tzinfo=None)).tzinfo + assert dt.tzinfo == tz + + def test_hydrate_local_date_time_structure(self): + struct = Structure(b"d", 1539344261, 474716862) + dt, = self.hydrant.hydrate([struct]) + assert isinstance(dt, DateTime) + assert dt.year == 2018 + assert dt.month == 10 + assert dt.day == 12 + assert dt.hour == 11 + assert dt.minute == 37 + assert dt.second == 41 + assert dt.nanosecond == 474716862 + assert dt.tzinfo is None + + def test_hydrate_duration_structure(self): + struct = Structure(b"E", 1, 2, 3, 4) + d, = self.hydrant.hydrate([struct]) + assert isinstance(d, Duration) + assert d.months == 1 + assert d.days == 2 + assert d.seconds == 3 + assert d.nanoseconds == 4 diff --git a/tests/unit/common/time/test_time.py b/tests/unit/common/time/test_time.py index b3bdc9384..60f7b7b8f 100644 --- a/tests/unit/common/time/test_time.py +++ b/tests/unit/common/time/test_time.py @@ -29,6 +29,7 @@ from neo4j.time.arithmetic import ( nano_add, nano_div, + round_half_to_even, ) @@ -36,42 +37,39 @@ timezone_utc = timezone("UTC") -def seconds_options(seconds, nanoseconds): - yield seconds, nanoseconds - yield seconds + nanoseconds / 1000000000, - - class TestTime: def test_bad_attribute(self): - t = Time(12, 34, 56.789) + t = Time(12, 34, 56, 789000000) with pytest.raises(AttributeError): _ = t.x def test_simple_time(self): - t = Time(12, 34, 56.789) - assert t.hour_minute_second == (12, 34, Decimal("56.789")) + t = Time(12, 34, 56, 789000000) assert t.hour_minute_second_nanosecond == (12, 34, 56, 789000000) - assert t.ticks == 45296.789 + assert t.ticks == 45296789000000 assert t.hour == 12 assert t.minute == 34 - assert t.second == Decimal("56.789") + assert t.second == 56 + assert t.nanosecond == 789000000 def test_midnight(self): t = Time(0, 0, 0) - assert t.hour_minute_second == (0, 0, 0) + assert t.hour_minute_second_nanosecond == (0, 0, 0, 0) assert t.ticks == 0 assert t.hour == 0 assert t.minute == 0 assert t.second == 0 + assert t.nanosecond == 0 def test_nanosecond_precision(self): - t = Time(12, 34, 56.789123456) - assert t.hour_minute_second == (12, 34, Decimal("56.789123456")) - assert t.ticks == 45296.789123456 + t = Time(12, 34, 56, 789123456) + assert t.hour_minute_second_nanosecond == (12, 34, 56, 789123456) + assert t.ticks == 45296789123456 assert t.hour == 12 assert t.minute == 34 - assert t.second == Decimal("56.789123456") + assert t.second == 56 + assert t.nanosecond == 789123456 def test_str(self): t = Time(12, 34, 56, 789123456) @@ -95,15 +93,16 @@ def test_from_native(self): t = Time.from_native(native) assert t.hour == native.hour assert t.minute == native.minute - assert t.second == \ - Decimal(native.second) + Decimal(native.microsecond) / 1000000 + assert t.second == native.second + assert t.nanosecond == native.microsecond * 1000 def test_to_native(self): - t = Time(12, 34, 56.789123456) + t = Time(12, 34, 56, 789123456) native = t.to_native() assert t.hour == native.hour assert t.minute == native.minute - assert 56.789123 == nano_add(native.second, nano_div(native.microsecond, 1000000)) + assert t.second == native.second + assert round_half_to_even(t.nanosecond / 1000) == native.microsecond def test_iso_format(self): t = Time(12, 34, 56, 789123456) @@ -224,7 +223,8 @@ def test_from_native_case_1(self): t = Time.from_native(native) assert t.hour == native.hour assert t.minute == native.minute - assert t.second == Decimal(native.microsecond) / 1000000 + native.second + assert t.second == native.second + assert t.nanosecond == native.microsecond * 1000 assert t.tzinfo is None def test_from_native_case_2(self): @@ -233,5 +233,6 @@ def test_from_native_case_2(self): t = Time.from_native(native) assert t.hour == native.hour assert t.minute == native.minute - assert t.second == Decimal(native.microsecond) / 1000000 + native.second + assert t.second == native.second + assert t.nanosecond == native.microsecond * 1000 assert t.tzinfo == FixedOffset(0)