Repository: neo4j/neo4j-python-driver

Issue:
Microsecond bug in neo4j.time.DateTime.to_native() 
Converting a `neo4j.time.DateTime` to the built-in `datetime.datetime` using your `to_native` method will sometimes give wrong microseconds. I suspect this is a rounding error related somehow to this line: https://github.com/neo4j/neo4j-python-driver/blob/f6b0254a1f0deea3e93cda4c7ae960e52c4a5426/neo4j/time/__init__.py#L1549 


**Driver version**: Python driver 4.2.1
**Operating System:** CentOS Linux 7

### Steps to reproduce

```
from datetime import datetime
from neo4j.time import DateTime as Neo4jDateTime

timestamps = [
    "2021-04-06T00:00:00.400009+00:00",
    "2021-04-06T00:00:00.400008+00:00",
    "2021-04-06T00:00:00.400007+00:00",
    "2021-04-06T00:00:00.400006+00:00",
    "2021-04-06T00:00:00.400005+00:00",
    "2021-04-06T00:00:00.400004+00:00",
    "2021-04-06T00:00:00.400003+00:00",
    "2021-04-06T00:00:00.400002+00:00",
    "2021-04-06T00:00:00.400001+00:00",
    "2021-04-06T00:00:00.600009+00:00",
    "2021-04-06T00:00:00.600008+00:00",
    "2021-04-06T00:00:00.600007+00:00",
    "2021-04-06T00:00:00.600006+00:00",
    "2021-04-06T00:00:00.600005+00:00",
    "2021-04-06T00:00:00.600004+00:00",
    "2021-04-06T00:00:00.600003+00:00",
    "2021-04-06T00:00:00.600002+00:00",
    "2021-04-06T00:00:00.600001+00:00",
    "2021-04-06T00:00:00.500009+00:00",
    "2021-04-06T00:00:00.500008+00:00",
    "2021-04-06T00:00:00.500007+00:00",
    "2021-04-06T00:00:00.500006+00:00",  # Fails by 1 microsecond
    "2021-04-06T00:00:00.500005+00:00",
    "2021-04-06T00:00:00.500004+00:00",
    "2021-04-06T00:00:00.500003+00:00",
    "2021-04-06T00:00:00.500002+00:00", # Fails by 1 microsecond
    "2021-04-06T00:00:00.500001+00:00",
]

for timestamp in timestamps:
    neo4j_datetime = Neo4jDateTime.from_iso_format(timestamp)
    native_from_neo4j = neo4j_datetime.to_native()
    native_from_datetime = datetime.fromisoformat(timestamp)

    print("neo4j_datetime: ", neo4j_datetime)
    print("native_from_neo4j: ", native_from_neo4j)
    print("native_from_datetime: ", native_from_datetime)
    print()

    assert neo4j_datetime == native_from_datetime
    assert native_from_neo4j == native_from_datetime
```

#### Result:
```
...

neo4j_datetime: 2021-04-06T00:00:00.500007000+00:00
native_from_neo4j: 2021-04-06T00:00:00.500007+00:00
native_from_datetime: 2021-04-06T00:00:00.500007+00:00

neo4j_datetime: 2021-04-06T00:00:00.500006000+00:00
native_from_neo4j: 2021-04-06T00:00:00.500005+00:00
native_from_datetime: 2021-04-06T00:00:00.500006+00:00

assert native_from_neo4j == native_from_datetime
AssertionError
```

### Expected behavior

`native_from_neo4j ` should always be the same as `native_from_datetime` in my example.

### Actual behavior

`native_from_neo4j ` is sometimes off by 1 microsecond compared to `native_from_datetime`

Discussion:

Patch:
diff --git a/neo4j/meta.py b/neo4j/meta.py
index d9b0ae87..127caf70 100644
--- a/neo4j/meta.py
+++ b/neo4j/meta.py
@@ -34,6 +34,11 @@ def get_user_agent():
     return template.format(*fields)
 
 
+def deprecation_warn(message):
+    from warnings import warn
+    warn(message, category=DeprecationWarning, stacklevel=2)
+
+
 def deprecated(message):
     """ Decorator for deprecating functions and methods.
 
@@ -46,8 +51,7 @@ def deprecated(message):
     """
     def f__(f):
         def f_(*args, **kwargs):
-            from warnings import warn
-            warn(message, category=DeprecationWarning, stacklevel=2)
+            deprecation_warn(message)
             return f(*args, **kwargs)
         f_.__name__ = f.__name__
         f_.__doc__ = f.__doc__
diff --git a/neo4j/time/__init__.py b/neo4j/time/__init__.py
index 54460f31..dee63eaf 100644
--- a/neo4j/time/__init__.py
+++ b/neo4j/time/__init__.py
@@ -23,12 +23,20 @@
 a number of utility functions.
 """
 
+from contextlib import contextmanager
 from datetime import (
     timedelta,
     date,
     time,
     datetime,
 )
+from decimal import (
+    Decimal,
+    localcontext,
+    ROUND_DOWN,
+    ROUND_HALF_EVEN,
+    ROUND_HALF_UP,
+)
 from functools import total_ordering
 from re import compile as re_compile
 from time import (
@@ -36,15 +44,14 @@ from time import (
     mktime,
     struct_time,
 )
-from decimal import Decimal
 
+from neo4j.meta import (
+    deprecated,
+    deprecation_warn
+)
 from neo4j.time.arithmetic import (
     nano_add,
-    nano_sub,
-    nano_mul,
     nano_div,
-    nano_mod,
-    nano_divmod,
     symmetric_divmod,
     round_half_to_even,
 )
@@ -54,6 +61,25 @@ from neo4j.time.metaclasses import (
     DateTimeType,
 )
 
+
+@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
 
@@ -64,10 +90,14 @@ MAX_YEAR = 9999
 """The largest year number allowed in a :class:`neo4j.time.Date` or :class:`neo4j.time.DateTime` object to be compatible with :class:`python:datetime.date` and :class:`python:datetime.datetime`."""
 
 DATE_ISO_PATTERN = re_compile(r"^(\d{4})-(\d{2})-(\d{2})$")
-TIME_ISO_PATTERN = re_compile(r"^(\d{2})(:(\d{2})(:((\d{2})"
-                              r"(\.\d*)?))?)?(([+-])(\d{2}):(\d{2})(:((\d{2})(\.\d*)?))?)?$")
-DURATION_ISO_PATTERN = re_compile(r"^P((\d+)Y)?((\d+)M)?((\d+)D)?"
-                                  r"(T((\d+)H)?((\d+)M)?((\d+(\.\d+)?)?S)?)?$")
+TIME_ISO_PATTERN = re_compile(
+    r"^(\d{2})(:(\d{2})(:((\d{2})"
+    r"(\.\d*)?))?)?(([+-])(\d{2}):(\d{2})(:((\d{2})(\.\d*)?))?)?$"
+)
+DURATION_ISO_PATTERN = re_compile(
+    r"^P((\d+)Y)?((\d+)M)?((\d+)D)?"
+    r"(T((\d+)H)?((\d+)M)?(((\d+)(\.\d+)?)?S)?)?$"
+)
 
 NANO_SECONDS = 1000000000
 
@@ -173,7 +203,9 @@ class ClockTime(tuple):
     """
 
     def __new__(cls, seconds=0, nanoseconds=0):
-        seconds, nanoseconds = nano_divmod(int(1000000000 * seconds) + int(nanoseconds), 1000000000)
+        seconds, nanoseconds = divmod(
+            int(NANO_SECONDS * seconds) + int(nanoseconds), NANO_SECONDS
+        )
         return tuple.__new__(cls, (seconds, nanoseconds))
 
     def __add__(self, other):
@@ -185,7 +217,7 @@ class ClockTime(tuple):
             if other.months or other.days:
                 raise ValueError("Cannot add Duration with months or days")
             return ClockTime(self.seconds + other.seconds, self.nanoseconds +
-                             int(other.subseconds * 1000000000))
+                             int(other.nanoseconds))
         return NotImplemented
 
     def __sub__(self, other):
@@ -196,7 +228,7 @@ class ClockTime(tuple):
         if isinstance(other, Duration):
             if other.months or other.days:
                 raise ValueError("Cannot subtract Duration with months or days")
-            return ClockTime(self.seconds - other.seconds, self.nanoseconds - int(other.subseconds * 1000000000))
+            return ClockTime(self.seconds - other.seconds, self.nanoseconds - int(other.nanoseconds))
         return NotImplemented
 
     def __repr__(self):
@@ -266,7 +298,10 @@ class Clock:
 
         :raises OverflowError:
         """
-        return ClockTime(-int(mktime(gmtime(0))))
+        # Adding and subtracting two days to avoid passing a pre-epoch time to
+        # `mktime`, which can cause a `OverflowError` on some platforms (e.g.,
+        # Windows).
+        return ClockTime(-int(mktime(gmtime(172800))) + 172800)
 
     def local_time(self):
         """ Read and return the current local time from this clock, measured relative to the Unix Epoch.
@@ -297,25 +332,34 @@ class Duration(tuple):
     min = None
     max = None
 
-    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):
+    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)
+
         mo = int(12 * years + months)
         if mo < MIN_INT64 or mo > MAX_INT64:
             raise ValueError("Months value out of range")
         d = int(7 * weeks + days)
+        ns = (int(3600000000000 * hours) +
+              int(60000000000 * minutes) +
+              int(1000000000 * seconds) +
+              int(1000000 * milliseconds) +
+              int(1000 * microseconds) +
+              int(nanoseconds))
+        s, ns = symmetric_divmod(ns, NANO_SECONDS)
         if d < MIN_INT64 or d > MAX_INT64:
             raise ValueError("Days value out of range")
-        s = (int(3600000000000 * hours) +
-             int(60000000000 * minutes) +
-             int(1000000000 * seconds) +
-             int(1000000000 * subseconds) +
-             int(1000000 * milliseconds) +
-             int(1000 * microseconds) +
-             int(nanoseconds))
-        s, ss = symmetric_divmod(s, 1000000000)
         if s < MIN_INT64 or s > MAX_INT64:
             raise ValueError("Seconds value out of range")
-        return tuple.__new__(cls, (mo, d, s, ss / 1000000000))
+        if s < MIN_INT64 or s > MAX_INT64:
+            raise ValueError("Seconds value out of range")
+        return tuple.__new__(cls, (mo, d, s, ns))
 
     def __bool__(self):
         return any(map(bool, self))
@@ -324,52 +368,94 @@ class Duration(tuple):
 
     def __add__(self, other):
         if isinstance(other, Duration):
-            return Duration(months=self[0] + int(other[0]), days=self[1] + int(other[1]),
-                            seconds=self[2] + int(other[2]), subseconds=nano_add(self[3], other[3]))
+            return Duration(
+                months=self[0] + int(other.months),
+                days=self[1] + int(other.days),
+                seconds=self[2] + int(other.seconds),
+                nanoseconds=self[3] + int(other.nanoseconds)
+            )
         if isinstance(other, timedelta):
-            return Duration(months=self[0], days=self[1] + int(other.days),
-                            seconds=self[2] + int(other.seconds),
-                            subseconds=nano_add(self[3], other.microseconds / 1000000))
+            return Duration(
+                months=self[0], days=self[1] + other.days,
+                seconds=self[2] + other.seconds,
+                nanoseconds=self[3] + other.microseconds * 1000
+            )
         return NotImplemented
 
     def __sub__(self, other):
         if isinstance(other, Duration):
-            return Duration(months=self[0] - int(other[0]), days=self[1] - int(other[1]),
-                            seconds=self[2] - int(other[2]), subseconds=nano_sub(self[3], other[3]))
+            return Duration(
+                months=self[0] - int(other.months),
+                days=self[1] - int(other.days),
+                seconds=self[2] - int(other.seconds),
+                nanoseconds=self[3] - int(other.nanoseconds)
+            )
         if isinstance(other, timedelta):
-            return Duration(months=self[0], days=self[1] - int(other.days),
-                            seconds=self[2] - int(other.seconds),
-                            subseconds=nano_sub(self[3], other.microseconds / 1000000))
+            return Duration(
+                months=self[0],
+                days=self[1] - other.days,
+                seconds=self[2] - other.seconds,
+                nanoseconds=self[3] - other.microseconds * 1000
+            )
         return NotImplemented
 
     def __mul__(self, other):
+        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, subseconds=nano_mul(self[3], other))
+            return Duration(
+                months=self[0] * other, days=self[1] * other,
+                seconds=self[2] * other, nanoseconds=self[3] * other
+            )
         return NotImplemented
 
+    @deprecated("Will be removed in 5.0.")
     def __floordiv__(self, other):
         if isinstance(other, int):
-            return Duration(months=int(self[0] // other), days=int(self[1] // other),
-                            seconds=int(nano_add(self[2], self[3]) // other), subseconds=0)
+            # 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 NotImplemented
 
+    @deprecated("Will be removed in 5.0.")
     def __mod__(self, other):
         if isinstance(other, int):
-            seconds, subseconds = symmetric_divmod(nano_add(self[2], self[3]) % other, 1)
-            return Duration(months=round_half_to_even(self[0] % other), days=round_half_to_even(self[1] % other),
+            # 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 NotImplemented
 
+    @deprecated("Will be removed in 5.0.")
     def __divmod__(self, other):
         if isinstance(other, int):
             return self.__floordiv__(other), self.__mod__(other)
         return NotImplemented
 
+    @deprecated("Will be removed in 5.0.")
     def __truediv__(self, other):
         if isinstance(other, (int, float)):
-            return Duration(months=round_half_to_even(float(self[0]) / other), days=round_half_to_even(float(self[1]) / other),
-                            seconds=float(self[2]) / other, subseconds=nano_div(self[3], other))
+            return Duration(
+                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
 
     __div__ = __truediv__
@@ -378,34 +464,41 @@ class Duration(tuple):
         return self
 
     def __neg__(self):
-        return Duration(months=-self[0], days=-self[1], seconds=-self[2], subseconds=-self[3])
+        return Duration(months=-self[0], days=-self[1], seconds=-self[2],
+                        nanoseconds=-self[3])
 
     def __abs__(self):
-        return Duration(months=abs(self[0]), days=abs(self[1]), seconds=abs(self[2]), subseconds=abs(self[3]))
+        return Duration(months=abs(self[0]), days=abs(self[1]),
+                        seconds=abs(self[2]), nanoseconds=abs(self[3]))
 
     def __repr__(self):
-        return "Duration(months=%r, days=%r, seconds=%r, subseconds=%r)" % self
+        return "Duration(months=%r, days=%r, seconds=%r, nanoseconds=%r)" % self
 
     def __str__(self):
         return self.iso_format()
 
     def __copy__(self):
-        return self.__new(self.ticks, self.hour, self.minute, self.second, self.tzinfo)
+        return self.__new__(self.__class__, months=self[0], days=self[1],
+                            seconds=self[2], nanoseconds=self[3])
 
     def __deepcopy__(self, memodict={}):
         return self.__copy__()
 
     @classmethod
     def from_iso_format(cls, s):
-        m = DURATION_ISO_PATTERN.match(s)
-        if m:
+        match = DURATION_ISO_PATTERN.match(s)
+        if match:
+            ns = 0
+            if match.group(15):
+                ns = int(match.group(15)[1:10].ljust(9, "0"))
             return cls(
-                years=int(m.group(2) or 0),
-                months=int(m.group(4) or 0),
-                days=int(m.group(6) or 0),
-                hours=int(m.group(9) or 0),
-                minutes=int(m.group(11) or 0),
-                seconds=float(m.group(13) or 0.0),
+                years=int(match.group(2) or 0),
+                months=int(match.group(4) or 0),
+                days=int(match.group(6) or 0),
+                hours=int(match.group(9) or 0),
+                minutes=int(match.group(11) or 0),
+                seconds=int(match.group(14) or 0),
+                nanoseconds=ns
             )
         raise ValueError("Duration string must be in ISO format")
 
@@ -419,16 +512,26 @@ class Duration(tuple):
         :rtype: str
         """
         parts = []
-        hours, minutes, seconds = self.hours_minutes_seconds
+        hours, minutes, seconds, nanoseconds = \
+            self.hours_minutes_seconds_nanoseconds
         if hours:
             parts.append("%dH" % hours)
         if minutes:
             parts.append("%dM" % minutes)
-        if seconds:
-            if seconds == seconds // 1:
-                parts.append("%dS" % seconds)
+        if nanoseconds:
+            if seconds >= 0 and nanoseconds >= 0:
+                parts.append("%d.%sS" %
+                             (seconds,
+                              str(nanoseconds).rjust(9, "0").rstrip("0")))
+            elif seconds <= 0 and nanoseconds <= 0:
+                parts.append("-%d.%sS" %
+                             (abs(seconds),
+                              str(abs(nanoseconds)).rjust(9, "0").rstrip("0")))
+
             else:
-                parts.append("%rS" % seconds)
+                assert False and "Please report this issue"
+        elif seconds:
+            parts.append("%dS" % seconds)
         if parts:
             parts.insert(0, sep)
         years, months, days = self.years_months_days
@@ -469,9 +572,21 @@ class Duration(tuple):
         return self[2]
 
     @property
+    @deprecated("Will be removed in 5.0. Use `nanoseconds` instead.")
     def subseconds(self):
         """
 
+        :return:
+        """
+        if self[3] < 0:
+            return Decimal(("-0.%09i" % -self[3])[:11])
+        else:
+            return Decimal(("0.%09i" % self[3])[:11])
+
+    @property
+    def nanoseconds(self):
+        """
+
         :return:
         """
         return self[3]
@@ -486,16 +601,29 @@ class Duration(tuple):
         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).
         """
         minutes, seconds = symmetric_divmod(self[2], 60)
         hours, minutes = symmetric_divmod(minutes, 60)
-        return hours, minutes, float(seconds) + self[3]
+        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).
+        """
+        minutes, seconds = symmetric_divmod(self[2], 60)
+        hours, minutes = symmetric_divmod(minutes, 60)
+        return hours, minutes, seconds, self[3]
 
-Duration.min = Duration(months=MIN_INT64, days=MIN_INT64, seconds=MIN_INT64, subseconds=-0.999999999)
-Duration.max = Duration(months=MAX_INT64, days=MAX_INT64, seconds=MAX_INT64, subseconds=+0.999999999)
+
+Duration.min = Duration(months=MIN_INT64, days=MIN_INT64, seconds=MIN_INT64,
+                        nanoseconds=-999999999)
+Duration.max = Duration(months=MAX_INT64, days=MAX_INT64, seconds=MAX_INT64,
+                        nanoseconds=999999999)
 
 
 class Date(metaclass=DateType):
@@ -828,8 +956,8 @@ class Date(metaclass=DateType):
             d.__year, d.__month, d.__day = d0.__year, d0.__month, d0.__day
 
         if isinstance(other, Duration):
-            if other.seconds or other.subseconds:
-                raise ValueError("Cannot add a Duration with seconds or subseconds to a Date")
+            if other.seconds or other.nanoseconds:
+                raise ValueError("Cannot add a Duration with seconds or nanoseconds to a Date")
             if other.months == other.days == 0:
                 return self
             new_date = self.replace()
@@ -928,18 +1056,24 @@ class Time(metaclass=TimeType):
 
     # CONSTRUCTOR #
 
-    def __new__(cls, hour, minute, second, tzinfo=None):
-        hour, minute, second = cls.__normalize_second(hour, minute, second)
-        ticks = 3600 * hour + 60 * minute + second
-        return cls.__new(ticks, hour, minute, second, tzinfo)
+    def __new__(cls, hour=0, minute=0, second=0, nanosecond=0, tzinfo=None):
+        hour, minute, second, nanosecond = cls.__normalize_nanosecond(
+            hour, minute, second, nanosecond
+        )
+        ticks = (3600000000000 * hour
+                 + 60000000000 * minute
+                 + 1000000000 * second
+                 + nanosecond)
+        return cls.__new(ticks, hour, minute, second, nanosecond, tzinfo)
 
     @classmethod
-    def __new(cls, ticks, hour, minute, second, tzinfo):
+    def __new(cls, ticks, hour, minute, second, nanosecond, tzinfo):
         instance = object.__new__(cls)
-        instance.__ticks = float(ticks)
+        instance.__ticks = int(ticks)
         instance.__hour = int(hour)
         instance.__minute = int(minute)
-        instance.__second = float(second)
+        instance.__second = int(second)
+        instance.__nanosecond = int(nanosecond)
         instance.__tzinfo = tzinfo
         return instance
 
@@ -983,9 +1117,14 @@ class Time(metaclass=TimeType):
         if m:
             hour = int(m.group(1))
             minute = int(m.group(3) or 0)
-            second = float(m.group(5) or 0.0)
+            second = int(m.group(6) or 0)
+            nanosecond = m.group(7)
+            if nanosecond:
+                nanosecond = int(nanosecond[1:10].ljust(9, "0"))
+            else:
+                nanosecond = 0
             if m.group(8) is None:
-                return cls(hour, minute, second)
+                return cls(hour, minute, second, nanosecond)
             else:
                 offset_multiplier = 1 if m.group(9) == "+" else -1
                 offset_hour = int(m.group(10))
@@ -994,52 +1133,88 @@ class Time(metaclass=TimeType):
                 # so we can ignore this part
                 # offset_second = float(m.group(13) or 0.0)
                 offset = 60 * offset_hour + offset_minute
-                return cls(hour, minute, second, tzinfo=FixedOffset(offset_multiplier * offset))
+                return cls(hour, minute, second, nanosecond,
+                           tzinfo=FixedOffset(offset_multiplier * offset))
         raise ValueError("Time string is not in ISO format")
 
     @classmethod
     def from_ticks(cls, ticks, tz=None):
         if 0 <= ticks < 86400:
-            minute, second = nano_divmod(ticks, 60)
-            hour, minute = divmod(minute, 60)
-            return cls.__new(ticks, hour, minute, second, tz)
+            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):
+        # TODO 5.0: this will become from_ticks
+        if not isinstance(ticks, int):
+            raise TypeError("Ticks must be int")
+        if 0 <= ticks < 86400000000000:
+            second, nanosecond = divmod(ticks, NANO_SECONDS)
+            minute, second = divmod(second, 60)
+            hour, minute = divmod(minute, 60)
+            return cls.__new(ticks, hour, minute, second, nanosecond, tz)
+        raise ValueError("Ticks out of range (0..86400000000000)")
+
     @classmethod
     def from_native(cls, t):
         """ Convert from a native Python `datetime.time` value.
         """
-        second = (1000000 * t.second + t.microsecond) / 1000000
-        return Time(t.hour, t.minute, second, t.tzinfo)
+        nanosecond = t.microsecond * 1000
+        return Time(t.hour, t.minute, t.second, nanosecond, t.tzinfo)
 
     @classmethod
     def from_clock_time(cls, clock_time, epoch):
         """ Convert from a `.ClockTime` relative to a given epoch.
+
+        This method, in contrast to most others of this package, assumes days of
+         exactly 24 hours.
         """
         clock_time = ClockTime(*clock_time)
         ts = clock_time.seconds % 86400
-        nanoseconds = int(1000000000 * ts + clock_time.nanoseconds)
-        return Time.from_ticks(epoch.time().ticks + nanoseconds / 1000000000)
+        nanoseconds = int(NANO_SECONDS * ts + clock_time.nanoseconds)
+        ticks = (epoch.time().ticks_ns + nanoseconds) % (86400 * NANO_SECONDS)
+        return Time.from_ticks_ns(ticks)
 
     @classmethod
     def __normalize_hour(cls, hour):
+        hour = int(hour)
         if 0 <= hour < 24:
-            return int(hour)
+            return hour
         raise ValueError("Hour out of range (0..23)")
 
     @classmethod
     def __normalize_minute(cls, hour, minute):
         hour = cls.__normalize_hour(hour)
+        minute = int(minute)
         if 0 <= minute < 60:
-            return hour, int(minute)
+            return hour, minute
         raise ValueError("Minute out of range (0..59)")
 
     @classmethod
     def __normalize_second(cls, hour, minute, second):
         hour, minute = cls.__normalize_minute(hour, minute)
+        second = int(second)
         if 0 <= second < 60:
-            return hour, minute, float(second)
-        raise ValueError("Second out of range (0..<60)")
+            return hour, minute, second
+        raise ValueError("Second out of range (0..59)")
+
+    @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
+        raise ValueError("Nanosecond out of range (0..%s)" % (NANO_SECONDS - 1))
 
     # CLASS ATTRIBUTES #
 
@@ -1059,12 +1234,22 @@ class Time(metaclass=TimeType):
 
     __second = 0
 
+    __nanosecond = 0
+
     __tzinfo = None
 
     @property
     def ticks(self):
         """ Return the total number of seconds since midnight.
         """
+        with _decimal_context(prec=15):
+            return self.__ticks / NANO_SECONDS
+
+    @property
+    def ticks_ns(self):
+        """ Return the total number of seconds since midnight.
+        """
+        # TODO 5.0: this will replace self.ticks
         return self.__ticks
 
     @property
@@ -1077,11 +1262,23 @@ class Time(metaclass=TimeType):
 
     @property
     def second(self):
-        return self.__second
+        # TODO 5.0: return plain self.__second
+        with _decimal_context(prec=11):
+            return self.__second + Decimal(("0.%09i" % self.__nanosecond)[:11])
 
     @property
+    def nanosecond(self):
+        return self.__nanosecond
+
+    @property
+    @deprecated("hour_minute_second will be removed in 5.0. "
+                "Use `hour_minute_second_nanoseconds` instead.")
     def hour_minute_second(self):
-        return self.__hour, self.__minute, self.__second
+        return self.__hour, self.__minute, self.second
+
+    @property
+    def hour_minute_second_nanoseconds(self):
+        return self.__hour, self.__minute, self.__second, self.__nanosecond
 
     @property
     def tzinfo(self):
@@ -1090,14 +1287,17 @@ class Time(metaclass=TimeType):
     # OPERATIONS #
 
     def __hash__(self):
-        return hash(self.ticks) ^ hash(self.tzinfo)
+        return hash(self.__ticks) ^ hash(self.tzinfo)
 
     def __eq__(self, other):
         if isinstance(other, Time):
-            return self.ticks == other.ticks and self.tzinfo == other.tzinfo
+            return self.__ticks == other.__ticks and self.tzinfo == other.tzinfo
         if isinstance(other, time):
-            other_ticks = 3600 * other.hour + 60 * other.minute + other.second + (other.microsecond / 1000000)
-            return self.ticks == other_ticks and self.tzinfo == other.tzinfo
+            other_ticks = (3600000000000 * other.hour
+                           + 60000000000 * other.minute
+                           + NANO_SECONDS * other.second
+                           + 1000 * other.microsecond)
+            return self.ticks_ns == other_ticks and self.tzinfo == other.tzinfo
         return False
 
     def __ne__(self, other):
@@ -1105,48 +1305,51 @@ class Time(metaclass=TimeType):
 
     def __lt__(self, other):
         if isinstance(other, Time):
-            return self.ticks < other.ticks
+            return (self.tzinfo == other.tzinfo
+                    and self.ticks_ns < other.ticks_ns)
         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 < other_ticks
-        raise TypeError("'<' not supported between instances of 'Time' and %r" % type(other).__name__)
+            return self.ticks_ns < other_ticks
+        return NotImplemented
 
     def __le__(self, other):
         if isinstance(other, Time):
-            return self.ticks <= other.ticks
+            return (self.tzinfo == other.tzinfo
+                    and self.ticks_ns <= other.ticks_ns)
         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 <= other_ticks
-        raise TypeError("'<=' not supported between instances of 'Time' and %r" % type(other).__name__)
+            return self.ticks_ns <= other_ticks
+        return NotImplemented
 
     def __ge__(self, other):
         if isinstance(other, Time):
-            return self.ticks >= other.ticks
+            return (self.tzinfo == other.tzinfo
+                    and self.ticks_ns >= other.ticks_ns)
         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 >= other_ticks
-        raise TypeError("'>=' not supported between instances of 'Time' and %r" % type(other).__name__)
+            return self.ticks_ns >= other_ticks
+        return NotImplemented
 
     def __gt__(self, other):
         if isinstance(other, Time):
-            return self.ticks >= other.ticks
+            return (self.tzinfo == other.tzinfo
+                    and self.ticks_ns >= other.ticks_ns)
         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 >= other_ticks
-        raise TypeError("'>' not supported between instances of 'Time' and %r" % type(other).__name__)
-
-    def __add__(self, other):
-        if isinstance(other, Duration):
-            return NotImplemented
-        if isinstance(other, timedelta):
-            return NotImplemented
-        return NotImplemented
-
-    def __sub__(self, other):
+            return self.ticks_ns >= other_ticks
         return NotImplemented
 
     def __copy__(self):
-        return self.__new(self.__ticks, self.__hour, self.__minute, self.__second, self.__tzinfo)
+        return self.__new(self.__ticks, self.__hour, self.__minute,
+                          self.__second, self.__nanosecond, self.__tzinfo)
 
     def __deepcopy__(self, *args, **kwargs):
         return self.__copy__()
@@ -1157,10 +1360,11 @@ class Time(metaclass=TimeType):
         """ Return a :class:`.Time` with one or more components replaced
         with new values.
         """
-        return Time(kwargs.get("hour", self.__hour),
-                    kwargs.get("minute", self.__minute),
-                    kwargs.get("second", self.__second),
-                    kwargs.get("tzinfo", self.__tzinfo))
+        return Time(hour=kwargs.get("hour", self.__hour),
+                    minute=kwargs.get("minute", self.__minute),
+                    second=kwargs.get("second", self.__second),
+                    nanosecond=kwargs.get("nanosecond", self.__nanosecond),
+                    tzinfo=kwargs.get("tzinfo", self.__tzinfo))
 
     def utc_offset(self):
         if self.tzinfo is None:
@@ -1197,21 +1401,19 @@ class Time(metaclass=TimeType):
         return self.tzinfo.tzname(self)
 
     def to_clock_time(self):
-        seconds, nanoseconds = nano_divmod(self.ticks, 1)  # int, float
-        nanoseconds_int = int(Decimal(str(nanoseconds)) * NANO_SECONDS)  # Convert fractions to an integer without losing precision
-        return ClockTime(seconds, nanoseconds_int)
+        seconds, nanoseconds = divmod(self.ticks_ns, NANO_SECONDS)
+        return ClockTime(seconds, nanoseconds)
 
     def to_native(self):
         """ Convert to a native Python `datetime.time` value.
         """
-        h, m, s = self.hour_minute_second
-        s, ns = nano_divmod(s, 1)
-        ms = int(nano_mul(ns, 1000000))
+        h, m, s, ns = self.hour_minute_second_nanoseconds
+        µs = round_half_to_even(ns / 1000)
         tz = self.tzinfo
-        return time(h, m, s, ms, tz)
+        return time(h, m, s, µs, tz)
 
     def iso_format(self):
-        s = "%02d:%02d:%012.9f" % self.hour_minute_second
+        s = "%02d:%02d:%02d.%09d" % self.hour_minute_second_nanoseconds
         if self.tzinfo is not None:
             offset = self.tzinfo.utcoffset(self)
             s += "%+03d:%02d" % divmod(offset.total_seconds() // 60, 60)
@@ -1219,9 +1421,11 @@ class Time(metaclass=TimeType):
 
     def __repr__(self):
         if self.tzinfo is None:
-            return "neo4j.time.Time(%r, %r, %r)" % self.hour_minute_second
+            return "neo4j.time.Time(%r, %r, %r, %r)" % \
+                   self.hour_minute_second_nanoseconds
         else:
-            return "neo4j.time.Time(%r, %r, %r, tzinfo=%r)" % (self.hour_minute_second + (self.tzinfo,))
+            return "neo4j.time.Time(%r, %r, %r, %r, tzinfo=%r)" % \
+                   (self.hour_minute_second_nanoseconds + (self.tzinfo,))
 
     def __str__(self):
         return self.iso_format()
@@ -1230,11 +1434,11 @@ class Time(metaclass=TimeType):
         raise NotImplementedError()
 
 
-Time.min = Time(0, 0, 0)
-Time.max = Time(23, 59, 59.999999999)
+Time.min = Time(hour=0, minute=0, second=0, nanosecond=0)
+Time.max = Time(hour=23, minute=59, second=59, nanosecond=999999999)
 
 Midnight = Time.min
-Midday = Time(12, 0, 0)
+Midday = Time(hour=12)
 
 
 @total_ordering
@@ -1249,8 +1453,8 @@ class DateTime(metaclass=DateTimeType):
     sub-second values to be passed, with up to nine decimal places of
     precision held by the object within the `second` attribute.
 
-        >>> dt = DateTime(2018, 4, 30, 12, 34, 56.789123456); dt
-        neo4j.time.DateTime(2018, 4, 30, 12, 34, 56.789123456)
+        >>> dt = DateTime(2018, 4, 30, 12, 34, 56, 789123456); dt
+        neo4j.time.DateTime(2018, 4, 30, 12, 34, 56, 789123456)
         >>> dt.second
         56.789123456
 
@@ -1258,8 +1462,10 @@ class DateTime(metaclass=DateTimeType):
 
     # CONSTRUCTOR #
 
-    def __new__(cls, year, month, day, hour=0, minute=0, second=0.0, tzinfo=None):
-        return cls.combine(Date(year, month, day), Time(hour, minute, second, tzinfo))
+    def __new__(cls, year, month, day, hour=0, minute=0, second=0, nanosecond=0,
+                tzinfo=None):
+        return cls.combine(Date(year, month, day),
+                           Time(hour, minute, second, nanosecond, tzinfo))
 
     def __getattr__(self, name):
         """ Map standard library attribute names to local attribute names,
@@ -1360,10 +1566,12 @@ class DateTime(metaclass=DateTimeType):
         except (TypeError, ValueError):
             raise ValueError("Clock time must be a 2-tuple of (s, ns)")
         else:
-            ordinal, ticks = divmod(seconds, 86400)
+            ordinal, seconds = divmod(seconds, 86400)
+            ticks = epoch.time().ticks_ns + seconds * NANO_SECONDS + nanoseconds
+            days, ticks = divmod(ticks, 86400 * NANO_SECONDS)
+            ordinal += days
             date_ = Date.from_ordinal(ordinal + epoch.date().to_ordinal())
-            nanoseconds = int(1000000000 * ticks + nanoseconds)
-            time_ = Time.from_ticks(epoch.time().ticks + (nanoseconds / 1000000000))
+            time_ = Time.from_ticks_ns(ticks)
             return cls.combine(date_, time_)
 
     # CLASS ATTRIBUTES #
@@ -1412,6 +1620,10 @@ class DateTime(metaclass=DateTimeType):
     def second(self):
         return self.__time.second
 
+    @property
+    def nanosecond(self):
+        return self.__time.nanosecond
+
     @property
     def tzinfo(self):
         return self.__time.tzinfo
@@ -1420,6 +1632,10 @@ class DateTime(metaclass=DateTimeType):
     def hour_minute_second(self):
         return self.__time.hour_minute_second
 
+    @property
+    def hour_minute_second_nanoseconds(self):
+        return self.__time.hour_minute_second_nanoseconds
+
     # OPERATIONS #
 
     def __hash__(self):
@@ -1470,7 +1686,9 @@ class DateTime(metaclass=DateTimeType):
             t = self.to_clock_time() + ClockTime(86400 * other.days + other.seconds, other.microseconds * 1000)
             days, seconds = symmetric_divmod(t.seconds, 86400)
             date_ = Date.from_ordinal(days + 1)
-            time_ = Time.from_ticks(seconds + (t.nanoseconds / 1000000000))
+            time_ = Time.from_ticks_ns(round_half_to_even(
+                seconds * NANO_SECONDS + t.nanoseconds
+            ))
             return self.combine(date_, time_)
         return NotImplemented
 
@@ -1560,17 +1778,15 @@ class DateTime(metaclass=DateTimeType):
         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 = nano_divmod(self.__time.ticks, 1)  # int, float
-        nanoseconds_int = int(Decimal(str(nanoseconds)) * NANO_SECONDS)  # Convert fractions to an integer without losing precision
-        return ClockTime(total_seconds + seconds, nanoseconds_int)
+        seconds, nanoseconds = divmod(self.__time.ticks_ns, NANO_SECONDS)
+        return ClockTime(total_seconds + seconds, nanoseconds)
 
     def to_native(self):
         """ Convert to a native Python `datetime.datetime` value.
         """
         y, mo, d = self.year_month_day
-        h, m, s = self.hour_minute_second
-        s, ns = nano_divmod(s, 1)
-        ms = int(nano_mul(ns, 1000000))
+        h, m, s, ns = self.hour_minute_second_nanoseconds
+        ms = int(ns / 1000)
         tz = self.tzinfo
         return datetime(y, mo, d, h, m, s, ms, tz)
 
@@ -1588,11 +1804,14 @@ class DateTime(metaclass=DateTimeType):
 
     def __repr__(self):
         if self.tzinfo is None:
-            fields = self.year_month_day + self.hour_minute_second
-            return "neo4j.time.DateTime(%r, %r, %r, %r, %r, %r)" % fields
+            fields = (*self.year_month_day,
+                      *self.hour_minute_second_nanoseconds)
+            return "neo4j.time.DateTime(%r, %r, %r, %r, %r, %r, %r)" % fields
         else:
-            fields = self.year_month_day + self.hour_minute_second + (self.tzinfo,)
-            return "neo4j.time.DateTime(%r, %r, %r, %r, %r, %r, tzinfo=%r)" % fields
+            fields = (*self.year_month_day,
+                      *self.hour_minute_second_nanoseconds, self.tzinfo)
+            return ("neo4j.time.DateTime(%r, %r, %r, %r, %r, %r, %r, tzinfo=%r)"
+                    % fields)
 
     def __str__(self):
         return self.iso_format()
diff --git a/neo4j/time/arithmetic.py b/neo4j/time/arithmetic.py
index d9b37419..c39ea2c2 100644
--- a/neo4j/time/arithmetic.py
+++ b/neo4j/time/arithmetic.py
@@ -41,44 +41,6 @@ def nano_add(x, y):
     return (int(1000000000 * x) + int(1000000000 * y)) / 1000000000
 
 
-def nano_sub(x, y):
-    """
-
-        >>> 0.7 - 0.2
-        0.49999999999999994
-        >>> -0.7 - 0.2
-        -0.8999999999999999
-        >>> nano_sub(0.7, 0.2)
-        0.5
-        >>> nano_sub(-0.7, 0.2)
-        -0.9
-
-    :param x:
-    :param y:
-    :return:
-    """
-    return (int(1000000000 * x) - int(1000000000 * y)) / 1000000000
-
-
-def nano_mul(x, y):
-    """
-
-        >>> 0.7 * 0.2
-        0.13999999999999999
-        >>> -0.7 * 0.2
-        -0.13999999999999999
-        >>> nano_mul(0.7, 0.2)
-        0.14
-        >>> nano_mul(-0.7, 0.2)
-        -0.14
-
-    :param x:
-    :param y:
-    :return:
-    """
-    return int(1000000000 * x) * int(1000000000 * y) / 1000000000000000000
-
-
 def nano_div(x, y):
     """
 
@@ -98,29 +60,6 @@ def nano_div(x, y):
     return float(1000000000 * x) / int(1000000000 * y)
 
 
-def nano_mod(x, y):
-    """
-
-        >>> 0.7 % 0.2
-        0.09999999999999992
-        >>> -0.7 % 0.2
-        0.10000000000000009
-        >>> nano_mod(0.7, 0.2)
-        0.1
-        >>> nano_mod(-0.7, 0.2)
-        0.1
-
-    :param x:
-    :param y:
-    :return:
-    """
-    number = type(x)
-    nx = int(1000000000 * x)
-    ny = int(1000000000 * y)
-    q, r = divmod(nx, ny)
-    return number(r / 1000000000)
-
-
 def nano_divmod(x, y):
     """
 
@@ -140,19 +79,6 @@ def nano_divmod(x, y):
     return int(q), number(r / 1000000000)
 
 
-def signum(n):
-    try:
-        if isnan(n):
-            return float("nan")
-        if n > 0 or n == float("inf"):
-            return 1
-        if n < 0 or n == float("-inf"):
-            return -1
-        return 0
-    except TypeError:
-        raise TypeError(n)
-
-
 def symmetric_divmod(dividend, divisor):
     number = type(dividend)
     if dividend >= 0:
diff --git a/neo4j/time/clock_implementations.py b/neo4j/time/clock_implementations.py
index cda32cd9..fccbd433 100644
--- a/neo4j/time/clock_implementations.py
+++ b/neo4j/time/clock_implementations.py
@@ -45,6 +45,31 @@ class SafeClock(Clock):
         return ClockTime(seconds, nanoseconds * 1000)
 
 
+class PEP564Clock(Clock):
+    """ Clock implementation based on the PEP564 additions to Python 3.7.
+    This clock is guaranteed nanosecond precision.
+    """
+
+    @classmethod
+    def precision(cls):
+        return 9
+
+    @classmethod
+    def available(cls):
+        try:
+            from time import time_ns
+        except ImportError:
+            return False
+        else:
+            return True
+
+    def utc_time(self):
+        from time import time_ns
+        t = time_ns()
+        seconds, nanoseconds = divmod(t, 1000000000)
+        return ClockTime(seconds, nanoseconds)
+
+
 class LibCClock(Clock):
     """ Clock implementation that works only on platforms that provide
     libc. This clock is guaranteed nanosecond precision.
@@ -79,28 +104,3 @@ class LibCClock(Clock):
             return ClockTime(ts.seconds, ts.nanoseconds)
         else:
             raise RuntimeError("clock_gettime failed with status %d" % status)
-
-
-class PEP564Clock(Clock):
-    """ Clock implementation based on the PEP564 additions to Python 3.7.
-    This clock is guaranteed nanosecond precision.
-    """
-
-    @classmethod
-    def precision(cls):
-        return 9
-
-    @classmethod
-    def available(cls):
-        try:
-            from time import time_ns
-        except ImportError:
-            return False
-        else:
-            return True
-
-    def utc_time(self):
-        from time import time_ns
-        t = time_ns()
-        seconds, nanoseconds = divmod(t, 1000000000)
-        return ClockTime(seconds, nanoseconds)
diff --git a/neo4j/time/hydration.py b/neo4j/time/hydration.py
index 313ed161..c31e7a53 100644
--- a/neo4j/time/hydration.py
+++ b/neo4j/time/hydration.py
@@ -77,8 +77,7 @@ def hydrate_time(nanoseconds, tz=None):
     seconds, nanoseconds = map(int, divmod(nanoseconds, 1000000000))
     minutes, seconds = map(int, divmod(seconds, 60))
     hours, minutes = map(int, divmod(minutes, 60))
-    seconds = (1000000000 * seconds + nanoseconds) / 1000000000
-    t = Time(hours, minutes, seconds)
+    t = Time(hours, minutes, seconds, nanoseconds)
     if tz is None:
         return t
     tz_offset_minutes, tz_offset_seconds = divmod(tz, 60)
@@ -94,7 +93,7 @@ def dehydrate_time(value):
     :return:
     """
     if isinstance(value, Time):
-        nanoseconds = int(value.ticks * 1000000000)
+        nanoseconds = value.ticks_ns
     elif isinstance(value, time):
         nanoseconds = (3600000000000 * value.hour + 60000000000 * value.minute +
                        1000000000 * value.second + 1000 * value.microsecond)
@@ -118,8 +117,10 @@ def hydrate_datetime(seconds, nanoseconds, tz=None):
     minutes, seconds = map(int, divmod(seconds, 60))
     hours, minutes = map(int, divmod(minutes, 60))
     days, hours = map(int, divmod(hours, 24))
-    seconds = (1000000000 * seconds + nanoseconds) / 1000000000
-    t = DateTime.combine(Date.from_ordinal(get_date_unix_epoch_ordinal() + days), Time(hours, minutes, seconds))
+    t = DateTime.combine(
+        Date.from_ordinal(get_date_unix_epoch_ordinal() + days),
+        Time(hours, minutes, seconds, nanoseconds)
+    )
     if tz is None:
         return t
     if isinstance(tz, int):
@@ -183,7 +184,7 @@ def dehydrate_duration(value):
     :type value: Duration
     :return:
     """
-    return Structure(b"E", value.months, value.days, value.seconds, int(1000000000 * value.subseconds))
+    return Structure(b"E", value.months, value.days, value.seconds, value.nanoseconds)
 
 
 def dehydrate_timedelta(value):


Test patch:
diff --git a/tests/unit/time/test_clock.py b/tests/unit/time/test_clock.py
index fd182bcd..996dbef7 100644
--- a/tests/unit/time/test_clock.py
+++ b/tests/unit/time/test_clock.py
@@ -53,3 +53,19 @@ class ClockTestCase(TestCase):
         clock = object.__new__(Clock)
         offset = clock.local_offset()
         self.assertIsInstance(offset, ClockTime)
+
+    def test_local_time(self):
+        _ = Clock()
+        for impl in Clock._Clock__implementations:
+            self.assert_(issubclass(impl, Clock))
+            clock = object.__new__(impl)
+            time = clock.local_time()
+            self.assertIsInstance(time, ClockTime)
+
+    def test_utc_time(self):
+        _ = Clock()
+        for impl in Clock._Clock__implementations:
+            self.assert_(issubclass(impl, Clock))
+            clock = object.__new__(impl)
+            time = clock.utc_time()
+            self.assertIsInstance(time, ClockTime)
diff --git a/tests/unit/time/test_datetime.py b/tests/unit/time/test_datetime.py
index 42c9d676..64d14199 100644
--- a/tests/unit/time/test_datetime.py
+++ b/tests/unit/time/test_datetime.py
@@ -19,11 +19,13 @@
 # limitations under the License.
 
 import copy
-from unittest import TestCase
+from decimal import Decimal
 from datetime import (
     datetime,
     timedelta,
 )
+
+import pytest
 from pytz import (
     timezone,
     FixedOffset,
@@ -59,6 +61,11 @@ timezone_us_eastern = timezone("US/Eastern")
 timezone_utc = timezone("UTC")
 
 
+def seconds_options(seconds, nanoseconds):
+    yield seconds, nanoseconds
+    yield seconds + nanoseconds / 1000000000,
+
+
 class FixedClock(Clock):
 
     @classmethod
@@ -77,277 +84,304 @@ class FixedClock(Clock):
         return ClockTime(45296, 789000000)
 
 
-class DateTimeTestCase(TestCase):
+class TestDateTime:
 
     def test_zero(self):
         t = DateTime(0, 0, 0, 0, 0, 0)
-        self.assertEqual(t.year, 0)
-        self.assertEqual(t.month, 0)
-        self.assertEqual(t.day, 0)
-        self.assertEqual(t.hour, 0)
-        self.assertEqual(t.minute, 0)
-        self.assertEqual(t.second, 0)
-
-    def test_non_zero_naive(self):
-        t = DateTime(2018, 4, 26, 23, 0, 17.914390409)
-        self.assertEqual(t.year, 2018)
-        self.assertEqual(t.month, 4)
-        self.assertEqual(t.day, 26)
-        self.assertEqual(t.hour, 23)
-        self.assertEqual(t.minute, 0)
-        self.assertEqual(t.second, 17.914390409)
+        assert t.year == 0
+        assert t.month == 0
+        assert t.day == 0
+        assert t.hour == 0
+        assert t.minute == 0
+        assert t.second == 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)
+        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.nanosecond == 914390409
 
     def test_year_lower_bound(self):
-        with self.assertRaises(ValueError):
+        with pytest.raises(ValueError):
             _ = DateTime(MIN_YEAR - 1, 1, 1, 0, 0, 0)
 
     def test_year_upper_bound(self):
-        with self.assertRaises(ValueError):
+        with pytest.raises(ValueError):
             _ = DateTime(MAX_YEAR + 1, 1, 1, 0, 0, 0)
 
     def test_month_lower_bound(self):
-        with self.assertRaises(ValueError):
+        with pytest.raises(ValueError):
             _ = DateTime(2000, 0, 1, 0, 0, 0)
 
     def test_month_upper_bound(self):
-        with self.assertRaises(ValueError):
+        with pytest.raises(ValueError):
             _ = DateTime(2000, 13, 1, 0, 0, 0)
 
     def test_day_zero(self):
-        with self.assertRaises(ValueError):
+        with pytest.raises(ValueError):
             _ = DateTime(2000, 1, 0, 0, 0, 0)
 
     def test_day_30_of_29_day_month(self):
-        with self.assertRaises(ValueError):
+        with pytest.raises(ValueError):
             _ = DateTime(2000, 2, 30, 0, 0, 0)
 
     def test_day_32_of_31_day_month(self):
-        with self.assertRaises(ValueError):
+        with pytest.raises(ValueError):
             _ = DateTime(2000, 3, 32, 0, 0, 0)
 
     def test_day_31_of_30_day_month(self):
-        with self.assertRaises(ValueError):
+        with pytest.raises(ValueError):
             _ = DateTime(2000, 4, 31, 0, 0, 0)
 
     def test_day_29_of_28_day_month(self):
-        with self.assertRaises(ValueError):
+        with pytest.raises(ValueError):
             _ = DateTime(1999, 2, 29, 0, 0, 0)
 
     def test_last_day_of_month(self):
         t = DateTime(2000, 1, -1, 0, 0, 0)
-        self.assertEqual(t.year, 2000)
-        self.assertEqual(t.month, 1)
-        self.assertEqual(t.day, 31)
+        assert t.year == 2000
+        assert t.month == 1
+        assert t.day == 31
 
     def test_today(self):
         t = DateTime.today()
-        self.assertEqual(t.year, 1970)
-        self.assertEqual(t.month, 1)
-        self.assertEqual(t.day, 1)
-        self.assertEqual(t.hour, 12)
-        self.assertEqual(t.minute, 34)
-        self.assertEqual(t.second, 56.789)
+        assert t.year == 1970
+        assert t.month == 1
+        assert t.day == 1
+        assert t.hour == 12
+        assert t.minute == 34
+        assert t.second == Decimal("56.789000000")
+        assert t.nanosecond == 789000000
 
     def test_now_without_tz(self):
         t = DateTime.now()
-        self.assertEqual(t.year, 1970)
-        self.assertEqual(t.month, 1)
-        self.assertEqual(t.day, 1)
-        self.assertEqual(t.hour, 12)
-        self.assertEqual(t.minute, 34)
-        self.assertEqual(t.second, 56.789)
-        self.assertIsNone(t.tzinfo)
+        assert t.year == 1970
+        assert t.month == 1
+        assert t.day == 1
+        assert t.hour == 12
+        assert t.minute == 34
+        assert t.second == Decimal("56.789000000")
+        assert t.nanosecond == 789000000
+        assert t.tzinfo is None
 
     def test_now_with_tz(self):
         t = DateTime.now(timezone_us_eastern)
-        self.assertEqual(t.year, 1970)
-        self.assertEqual(t.month, 1)
-        self.assertEqual(t.day, 1)
-        self.assertEqual(t.hour, 7)
-        self.assertEqual(t.minute, 34)
-        self.assertEqual(t.second, 56.789)
-        self.assertEqual(t.utcoffset(), timedelta(seconds=-18000))
-        self.assertEqual(t.dst(), timedelta())
-        self.assertEqual(t.tzname(), "EST")
+        assert t.year == 1970
+        assert t.month == 1
+        assert t.day == 1
+        assert t.hour == 7
+        assert t.minute == 34
+        assert t.second == Decimal("56.789000000")
+        assert t.nanosecond == 789000000
+        assert t.utcoffset() == timedelta(seconds=-18000)
+        assert t.dst() == timedelta()
+        assert t.tzname() == "EST"
 
     def test_utc_now(self):
         t = DateTime.utc_now()
-        self.assertEqual(t.year, 1970)
-        self.assertEqual(t.month, 1)
-        self.assertEqual(t.day, 1)
-        self.assertEqual(t.hour, 12)
-        self.assertEqual(t.minute, 34)
-        self.assertEqual(t.second, 56.789)
-        self.assertIsNone(t.tzinfo)
+        assert t.year == 1970
+        assert t.month == 1
+        assert t.day == 1
+        assert t.hour == 12
+        assert t.minute == 34
+        assert t.second == Decimal("56.789000000")
+        assert t.nanosecond == 789000000
+        assert t.tzinfo is None
 
     def test_from_timestamp(self):
         t = DateTime.from_timestamp(0)
-        self.assertEqual(t.year, 1970)
-        self.assertEqual(t.month, 1)
-        self.assertEqual(t.day, 1)
-        self.assertEqual(t.hour, 0)
-        self.assertEqual(t.minute, 0)
-        self.assertEqual(t.second, 0.0)
-        self.assertIsNone(t.tzinfo)
+        assert t.year == 1970
+        assert t.month == 1
+        assert t.day == 1
+        assert t.hour == 0
+        assert t.minute == 0
+        assert t.second == Decimal("0.0")
+        assert t.nanosecond == 0
+        assert t.tzinfo is None
 
     def test_from_overflowing_timestamp(self):
-        with self.assertRaises(ValueError):
+        with pytest.raises(ValueError):
             _ = DateTime.from_timestamp(999999999999999999)
 
     def test_from_timestamp_with_tz(self):
         t = DateTime.from_timestamp(0, timezone_us_eastern)
-        self.assertEqual(t.year, 1969)
-        self.assertEqual(t.month, 12)
-        self.assertEqual(t.day, 31)
-        self.assertEqual(t.hour, 19)
-        self.assertEqual(t.minute, 0)
-        self.assertEqual(t.second, 0.0)
-        self.assertEqual(t.utcoffset(), timedelta(seconds=-18000))
-        self.assertEqual(t.dst(), timedelta())
-        self.assertEqual(t.tzname(), "EST")
-
-    def test_conversion_to_t(self):
-        dt = DateTime(2018, 4, 26, 23, 0, 17.914390409)
+        assert t.year == 1969
+        assert t.month == 12
+        assert t.day == 31
+        assert t.hour == 19
+        assert t.minute == 0
+        assert t.second == Decimal("0.0")
+        assert t.nanosecond == 0
+        assert t.utcoffset() == timedelta(seconds=-18000)
+        assert t.dst() == timedelta()
+        assert t.tzname() == "EST"
+
+    @pytest.mark.parametrize("seconds_args", seconds_options(17, 914390409))
+    def test_conversion_to_t(self, seconds_args):
+        dt = DateTime(2018, 4, 26, 23, 0, *seconds_args)
         t = dt.to_clock_time()
-        self.assertEqual(t, ClockTime(63660380417, 914390409))
+        assert t, ClockTime(63660380417 == 914390409)
 
-    def test_add_timedelta(self):
-        dt1 = DateTime(2018, 4, 26, 23, 0, 17.914390409)
+    @pytest.mark.parametrize("seconds_args1", seconds_options(17, 914390409))
+    @pytest.mark.parametrize("seconds_args2", seconds_options(17, 914390409))
+    def test_add_timedelta(self, seconds_args1, seconds_args2):
+        dt1 = DateTime(2018, 4, 26, 23, 0, *seconds_args1)
         delta = timedelta(days=1)
         dt2 = dt1 + delta
-        self.assertEqual(dt2, DateTime(2018, 4, 27, 23, 0, 17.914390409))
+        assert dt2, DateTime(2018, 4, 27, 23, 0 == seconds_args2)
 
-    def test_subtract_datetime_1(self):
-        dt1 = DateTime(2018, 4, 26, 23, 0, 17.914390409)
-        dt2 = DateTime(2018, 1, 1, 0, 0, 0.0)
+    @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)
+        dt2 = DateTime(2018, 1, 1, 0, 0, 0)
         t = dt1 - dt2
-        self.assertEqual(t, Duration(months=3, days=25, hours=23, seconds=17.914390409))
 
-    def test_subtract_datetime_2(self):
-        dt1 = DateTime(2018, 4, 1, 23, 0, 17.914390409)
+        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_datetime_2(self, seconds_args):
+        dt1 = DateTime(2018, 4, 1, 23, 0, *seconds_args)
         dt2 = DateTime(2018, 1, 26, 0, 0, 0.0)
         t = dt1 - dt2
-        self.assertEqual(t, Duration(months=3, days=-25, hours=23, seconds=17.914390409))
+        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)
 
-    def test_subtract_native_datetime_1(self):
-        dt1 = DateTime(2018, 4, 26, 23, 0, 17.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)
         dt2 = datetime(2018, 1, 1, 0, 0, 0)
         t = dt1 - dt2
-        self.assertEqual(t, timedelta(days=115, hours=23, seconds=17.914390409))
+        assert t == timedelta(days=115, hours=23, seconds=17.914390409)
 
-    def test_subtract_native_datetime_2(self):
-        dt1 = DateTime(2018, 4, 1, 23, 0, 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)
         dt2 = datetime(2018, 1, 26, 0, 0, 0)
         t = dt1 - dt2
-        self.assertEqual(t, timedelta(days=65, hours=23, seconds=17.914390409))
+        assert t == timedelta(days=65, hours=23, seconds=17.914390409)
 
     def test_normalization(self):
         ndt1 = timezone_us_eastern.normalize(DateTime(2018, 4, 27, 23, 0, 17, tzinfo=timezone_us_eastern))
         ndt2 = timezone_us_eastern.normalize(datetime(2018, 4, 27, 23, 0, 17, tzinfo=timezone_us_eastern))
-        self.assertEqual(ndt1, ndt2)
+        assert ndt1 == ndt2
 
     def test_localization(self):
         ldt1 = timezone_us_eastern.localize(datetime(2018, 4, 27, 23, 0, 17))
         ldt2 = timezone_us_eastern.localize(DateTime(2018, 4, 27, 23, 0, 17))
-        self.assertEqual(ldt1, ldt2)
+        assert ldt1 == ldt2
 
     def test_from_native(self):
         native = datetime(2018, 10, 1, 12, 34, 56, 789123)
         dt = DateTime.from_native(native)
-        self.assertEqual(dt.year, native.year)
-        self.assertEqual(dt.month, native.month)
-        self.assertEqual(dt.day, native.day)
-        self.assertEqual(dt.hour, native.hour)
-        self.assertEqual(dt.minute, native.minute)
-        self.assertEqual(dt.second, nano_add(native.second, nano_div(native.microsecond, 1000000)))
+        assert dt.year == native.year
+        assert dt.month == native.month
+        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.nanosecond == native.microsecond * 1000
 
     def test_to_native(self):
         dt = DateTime(2018, 10, 1, 12, 34, 56.789123456)
         native = dt.to_native()
-        self.assertEqual(dt.year, native.year)
-        self.assertEqual(dt.month, native.month)
-        self.assertEqual(dt.day, native.day)
-        self.assertEqual(dt.hour, native.hour)
-        self.assertEqual(dt.minute, native.minute)
-        self.assertEqual(56.789123, nano_add(native.second, nano_div(native.microsecond, 1000000)))
+        assert dt.year == native.year
+        assert dt.month == native.month
+        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))
 
     def test_iso_format(self):
         dt = DateTime(2018, 10, 1, 12, 34, 56.789123456)
-        self.assertEqual("2018-10-01T12:34:56.789123456", dt.iso_format())
+        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)
-        self.assertEqual("2018-10-01T12:34:56.789000000", dt.iso_format())
+        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))
-        self.assertEqual("2018-10-01T12:34:56.789123456-04:00", dt.iso_format())
+        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))
-        self.assertEqual("2018-10-01T12:34:56.789000000-04:00", dt.iso_format())
+        assert "2018-10-01T12:34:56.789000000-04:00" == dt.iso_format()
 
     def test_from_iso_format_hour_only(self):
         expected = DateTime(2018, 10, 1, 12, 0, 0)
         actual = DateTime.from_iso_format("2018-10-01T12")
-        self.assertEqual(expected, actual)
+        assert expected == actual
 
     def test_from_iso_format_hour_and_minute(self):
         expected = DateTime(2018, 10, 1, 12, 34, 0)
         actual = DateTime.from_iso_format("2018-10-01T12:34")
-        self.assertEqual(expected, actual)
+        assert expected == actual
 
     def test_from_iso_format_hour_minute_second(self):
         expected = DateTime(2018, 10, 1, 12, 34, 56)
         actual = DateTime.from_iso_format("2018-10-01T12:34:56")
-        self.assertEqual(expected, actual)
+        assert expected == actual
 
     def test_from_iso_format_hour_minute_second_milliseconds(self):
-        expected = DateTime(2018, 10, 1, 12, 34, 56.123)
+        expected = DateTime(2018, 10, 1, 12, 34, 56, 123000000)
         actual = DateTime.from_iso_format("2018-10-01T12:34:56.123")
-        self.assertEqual(expected, actual)
+        assert expected == actual
 
     def test_from_iso_format_hour_minute_second_microseconds(self):
-        expected = DateTime(2018, 10, 1, 12, 34, 56.123456)
+        expected = DateTime(2018, 10, 1, 12, 34, 56, 123456000)
         actual = DateTime.from_iso_format("2018-10-01T12:34:56.123456")
-        self.assertEqual(expected, actual)
+        assert expected == actual
 
     def test_from_iso_format_hour_minute_second_nanoseconds(self):
-        expected = DateTime(2018, 10, 1, 12, 34, 56.123456789)
+        expected = DateTime(2018, 10, 1, 12, 34, 56, 123456789)
         actual = DateTime.from_iso_format("2018-10-01T12:34:56.123456789")
-        self.assertEqual(expected, actual)
+        assert expected == actual
 
     def test_from_iso_format_with_positive_tz(self):
-        expected = DateTime(2018, 10, 1, 12, 34, 56.123456789, tzinfo=FixedOffset(754))
+        expected = DateTime(2018, 10, 1, 12, 34, 56, 123456789,
+                            tzinfo=FixedOffset(754))
         actual = DateTime.from_iso_format("2018-10-01T12:34:56.123456789+12:34")
-        self.assertEqual(expected, actual)
+        assert expected == actual
 
     def test_from_iso_format_with_negative_tz(self):
-        expected = DateTime(2018, 10, 1, 12, 34, 56.123456789, tzinfo=FixedOffset(-754))
+        expected = DateTime(2018, 10, 1, 12, 34, 56, 123456789,
+                            tzinfo=FixedOffset(-754))
         actual = DateTime.from_iso_format("2018-10-01T12:34:56.123456789-12:34")
-        self.assertEqual(expected, actual)
+        assert expected == actual
 
     def test_from_iso_format_with_positive_long_tz(self):
-        expected = DateTime(2018, 10, 1, 12, 34, 56.123456789, tzinfo=FixedOffset(754))
+        expected = DateTime(2018, 10, 1, 12, 34, 56, 123456789,
+                            tzinfo=FixedOffset(754))
         actual = DateTime.from_iso_format("2018-10-01T12:34:56.123456789+12:34:56.123456")
-        self.assertEqual(expected, actual)
+        assert expected == actual
 
     def test_from_iso_format_with_negative_long_tz(self):
-        expected = DateTime(2018, 10, 1, 12, 34, 56.123456789, tzinfo=FixedOffset(-754))
+        expected = DateTime(2018, 10, 1, 12, 34, 56, 123456789,
+                            tzinfo=FixedOffset(-754))
         actual = DateTime.from_iso_format("2018-10-01T12:34:56.123456789-12:34:56.123456")
-        self.assertEqual(expected, actual)
+        assert expected == actual
 
     def test_datetime_copy(self):
         d = DateTime(2010, 10, 1, 10, 0, 10)
         d2 = copy.copy(d)
-        self.assertIsNot(d, d2)
-        self.assertEqual(d, d2)
+        assert d is not d2
+        assert d == d2
 
     def test_datetime_deep_copy(self):
         d = DateTime(2010, 10, 1, 10, 0, 12)
         d2 = copy.deepcopy(d)
-        self.assertIsNot(d, d2)
-        self.assertEqual(d, d2)
+        assert d is not d2
+        assert d == d2
 
 
 def test_iso_format_with_time_zone_case_1():
@@ -388,6 +422,18 @@ def test_to_native_case_2():
     assert native.isoformat() == "2019-10-30T12:34:56.789123+00:00"
 
 
+def test_to_native_case_3():
+    # python -m pytest tests/unit/time/test_datetime.py -s -v -k test_to_native_case_3
+    timestamp = "2021-04-06T00:00:00.500006+00:00"
+    neo4j_datetime = DateTime.from_iso_format(timestamp)
+    native_from_neo4j = neo4j_datetime.to_native()
+    native_from_datetime = datetime(2021, 4, 6, 0, 0, 0, 500006,
+                                    tzinfo=timezone_utc)
+
+    assert neo4j_datetime == native_from_datetime
+    assert native_from_neo4j == native_from_datetime
+
+
 def test_from_native_case_1():
     # python -m pytest tests/unit/time/test_datetime.py -s -v -k test_from_native_case_1
     native = datetime(2018, 10, 1, 12, 34, 56, 789123)
@@ -397,7 +443,10 @@ def test_from_native_case_1():
     assert dt.day == native.day
     assert dt.hour == native.hour
     assert dt.minute == native.minute
-    assert dt.second == nano_add(native.second, nano_div(native.microsecond, 1000000))
+    assert dt.second == (native.second
+                         + Decimal(native.microsecond) / 1000000)
+    assert int(dt.second) == native.second
+    assert dt.nanosecond == native.microsecond * 1000
     assert dt.tzinfo is None
 
 
@@ -410,5 +459,8 @@ def test_from_native_case_2():
     assert dt.day == native.day
     assert dt.hour == native.hour
     assert dt.minute == native.minute
-    assert dt.second == nano_add(native.second, nano_div(native.microsecond, 1000000))
+    assert dt.second == (native.second
+                         + Decimal(native.microsecond) / 1000000)
+    assert int(dt.second) == native.second
+    assert dt.nanosecond == native.microsecond * 1000
     assert dt.tzinfo == FixedOffset(0)
diff --git a/tests/unit/time/test_duration.py b/tests/unit/time/test_duration.py
index 4195a942..2a80d0ac 100644
--- a/tests/unit/time/test_duration.py
+++ b/tests/unit/time/test_duration.py
@@ -20,316 +20,398 @@
 
 
 from datetime import timedelta
-from unittest import TestCase
+from decimal import Decimal
+import copy
+
+import pytest
 
 from neo4j.time import Duration
 
 
-class DurationTestCase(TestCase):
+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):
         d = Duration()
-        self.assertEqual(d.months, 0)
-        self.assertEqual(d.days, 0)
-        self.assertEqual(d.seconds, 0)
-        self.assertEqual(d.subseconds, 0.0)
-        self.assertEqual(d.years_months_days, (0, 0, 0))
-        self.assertEqual(d.hours_minutes_seconds, (0, 0, 0.0))
-        self.assertFalse(bool(d))
+        assert d.months == 0
+        assert d.days == 0
+        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)
 
     def test_years_only(self):
         d = Duration(years=2)
-        self.assertEqual(d.months, 24)
-        self.assertEqual(d.days, 0)
-        self.assertEqual(d.seconds, 0)
-        self.assertEqual(d.subseconds, 0.0)
-        self.assertEqual(d.years_months_days, (2, 0, 0))
-        self.assertEqual(d.hours_minutes_seconds, (0, 0, 0.0))
+        assert d.months == 24
+        assert d.days == 0
+        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)
 
     def test_months_only(self):
         d = Duration(months=20)
-        self.assertEqual(d.months, 20)
-        self.assertEqual(d.days, 0)
-        self.assertEqual(d.seconds, 0)
-        self.assertEqual(d.subseconds, 0.0)
-        self.assertEqual(d.years_months_days, (1, 8, 0))
-        self.assertEqual(d.hours_minutes_seconds, (0, 0, 0.0))
+        assert d.months == 20
+        assert d.days == 0
+        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)
 
     def test_months_out_of_range(self):
-        with self.assertRaises(ValueError):
+        with pytest.raises(ValueError):
             _ = Duration(months=(2**64))
 
     def test_weeks_only(self):
         d = Duration(weeks=4)
-        self.assertEqual(d.months, 0)
-        self.assertEqual(d.days, 28)
-        self.assertEqual(d.seconds, 0)
-        self.assertEqual(d.subseconds, 0.0)
-        self.assertEqual(d.years_months_days, (0, 0, 28))
-        self.assertEqual(d.hours_minutes_seconds, (0, 0, 0.0))
+        assert d.months == 0
+        assert d.days == 28
+        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)
 
     def test_days_only(self):
         d = Duration(days=40)
-        self.assertEqual(d.months, 0)
-        self.assertEqual(d.days, 40)
-        self.assertEqual(d.seconds, 0)
-        self.assertEqual(d.subseconds, 0.0)
-        self.assertEqual(d.years_months_days, (0, 0, 40))
-        self.assertEqual(d.hours_minutes_seconds, (0, 0, 0.0))
+        assert d.months == 0
+        assert d.days == 40
+        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)
 
     def test_days_out_of_range(self):
-        with self.assertRaises(ValueError):
+        with pytest.raises(ValueError):
             _ = Duration(days=(2**64))
 
     def test_hours_only(self):
         d = Duration(hours=10)
-        self.assertEqual(d.months, 0)
-        self.assertEqual(d.days, 0)
-        self.assertEqual(d.seconds, 36000)
-        self.assertEqual(d.subseconds, 0.0)
-        self.assertEqual(d.years_months_days, (0, 0, 0))
-        self.assertEqual(d.hours_minutes_seconds, (10, 0, 0.0))
+        assert d.months == 0
+        assert d.days == 0
+        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)
 
     def test_minutes_only(self):
         d = Duration(minutes=90.5)
-        self.assertEqual(d.months, 0)
-        self.assertEqual(d.days, 0)
-        self.assertEqual(d.seconds, 5430)
-        self.assertEqual(d.subseconds, 0.0)
-        self.assertEqual(d.years_months_days, (0, 0, 0))
-        self.assertEqual(d.hours_minutes_seconds, (1, 30, 30.0))
-
-    def test_seconds_only(self):
-        d = Duration(seconds=123.456)
-        self.assertEqual(d.months, 0)
-        self.assertEqual(d.days, 0)
-        self.assertEqual(d.seconds, 123)
-        self.assertEqual(d.subseconds, 0.456)
-        self.assertEqual(d.years_months_days, (0, 0, 0))
-        self.assertEqual(d.hours_minutes_seconds, (0, 2, 3.456))
+        assert d.months == 0
+        assert d.days == 0
+        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)
+        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)
 
     def test_seconds_out_of_range(self):
-        with self.assertRaises(ValueError):
+        with pytest.raises(ValueError):
             _ = Duration(seconds=(2**64))
 
     def test_subseconds_only(self):
         d = Duration(subseconds=123.456)
-        self.assertEqual(d.months, 0)
-        self.assertEqual(d.days, 0)
-        self.assertEqual(d.seconds, 123)
-        self.assertEqual(d.subseconds, 0.456)
-        self.assertEqual(d.years_months_days, (0, 0, 0))
-        self.assertEqual(d.hours_minutes_seconds, (0, 2, 3.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)
-        self.assertEqual(d.months, 0)
-        self.assertEqual(d.days, 0)
-        self.assertEqual(d.seconds, 1)
-        self.assertEqual(d.subseconds, 0.234567)
-        self.assertEqual(d.years_months_days, (0, 0, 0))
-        self.assertEqual(d.hours_minutes_seconds, (0, 0, 1.234567))
+        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)
 
     def test_microseconds_only(self):
         d = Duration(microseconds=1234.567)
-        self.assertEqual(d.months, 0)
-        self.assertEqual(d.days, 0)
-        self.assertEqual(d.seconds, 0)
-        self.assertEqual(d.subseconds, 0.001234567)
-        self.assertEqual(d.years_months_days, (0, 0, 0))
-        self.assertEqual(d.hours_minutes_seconds, (0, 0, 0.001234567))
+        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)
 
     def test_nanoseconds_only(self):
         d = Duration(nanoseconds=1234.567)
-        self.assertEqual(d.months, 0)
-        self.assertEqual(d.days, 0)
-        self.assertEqual(d.seconds, 0)
-        self.assertEqual(d.subseconds, 0.000001234)
-        self.assertEqual(d.years_months_days, (0, 0, 0))
-        self.assertEqual(d.hours_minutes_seconds, (0, 0, 0.000001234))
+        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)
 
     def test_can_combine_years_months(self):
         t = Duration(years=5, months=3)
-        self.assertEqual(t.months, 63)
+        assert t.months == 63
 
     def test_can_combine_weeks_and_days(self):
         t = Duration(weeks=5, days=3)
-        self.assertEqual(t.days, 38)
+        assert t.days == 38
 
     def test_can_combine_hours_minutes_seconds(self):
         t = Duration(hours=5, minutes=4, seconds=3)
-        self.assertEqual(t.seconds, 18243)
+        assert t.seconds == 18243
 
-    def test_can_combine_seconds_and_subseconds(self):
-        t = Duration(seconds=123.456, subseconds=0.321)
-        self.assertEqual(t.seconds, 123)
-        self.assertEqual(t.subseconds, 0.777)
+    def test_can_combine_seconds_and_nanoseconds(self):
+        t = Duration(seconds=123.456, nanoseconds=321000000)
+        assert t.seconds == 123
+        assert t.nanoseconds == 777000000
+        assert t == Duration(seconds=123, nanoseconds=777000000)
+        assert t == Duration(seconds=123.777)
 
     def test_full_positive(self):
         d = Duration(years=1, months=2, days=3, hours=4, minutes=5, seconds=6.789)
-        self.assertEqual(d.months, 14)
-        self.assertEqual(d.days, 3)
-        self.assertEqual(d.seconds, 14706)
-        self.assertEqual(d.subseconds, 0.789)
-        self.assertEqual(d.years_months_days, (1, 2, 3))
-        self.assertEqual(d.hours_minutes_seconds, (4, 5, 6.789))
-        self.assertTrue(bool(d))
+        assert d.months == 14
+        assert d.days == 3
+        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)
 
     def test_full_negative(self):
         d = Duration(years=-1, months=-2, days=-3, hours=-4, minutes=-5, seconds=-6.789)
-        self.assertEqual(d.months, -14)
-        self.assertEqual(d.days, -3)
-        self.assertEqual(d.seconds, -14706)
-        self.assertEqual(d.subseconds, -0.789)
-        self.assertEqual(d.years_months_days, (-1, -2, -3))
-        self.assertEqual(d.hours_minutes_seconds, (-4, -5, -6.789))
-        self.assertTrue(bool(d))
-
-    def test_negative_positive_negative(self):
+        assert d.months == -14
+        assert d.days == -3
+        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)
+
+    def test_negative_positive(self):
         d = Duration(years=-1, months=-2, days=3, hours=-4, minutes=-5, seconds=-6.789)
-        self.assertEqual(d.months, -14)
-        self.assertEqual(d.days, 3)
-        self.assertEqual(d.seconds, -14706)
-        self.assertEqual(d.subseconds, -0.789)
-        self.assertEqual(d.years_months_days, (-1, -2, 3))
-        self.assertEqual(d.hours_minutes_seconds, (-4, -5, -6.789))
-
-    def test_positive_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):
         d = Duration(years=1, months=2, days=-3, hours=4, minutes=5, seconds=6.789)
-        self.assertEqual(d.months, 14)
-        self.assertEqual(d.days, -3)
-        self.assertEqual(d.seconds, 14706)
-        self.assertEqual(d.subseconds, 0.789)
-        self.assertEqual(d.years_months_days, (1, 2, -3))
-        self.assertEqual(d.hours_minutes_seconds, (4, 5, 6.789))
-
-    def test_add_duration(self):
-        d1 = Duration(months=2, days=3, seconds=5.7)
-        d2 = Duration(months=7, days=5, seconds=3.2)
-        self.assertEqual(d1 + d2, Duration(months=9, days=8, seconds=8.9))
-
-    def test_add_timedelta(self):
-        d1 = Duration(months=2, days=3, seconds=5.7)
+        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)
         td = timedelta(days=5, seconds=3.2)
-        self.assertEqual(d1 + td, Duration(months=2, days=8, seconds=8.9))
+        assert d1 + td == Duration(months=2, days=8, **sec2_kwargs)
 
     def test_add_object(self):
-        with self.assertRaises(TypeError):
+        with pytest.raises(TypeError):
             _ = Duration(months=2, days=3, seconds=5.7) + object()
 
-    def test_subtract_duration(self):
-        d1 = Duration(months=2, days=3, seconds=5.7)
-        d2 = Duration(months=7, days=5, seconds=3.2)
-        self.assertEqual(d1 - d2, Duration(months=-5, days=-2, seconds=2.5))
-
-    def test_subtract_timedelta(self):
-        d1 = Duration(months=2, days=3, seconds=5.7)
+    @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)
         td = timedelta(days=5, seconds=3.2)
-        self.assertEqual(d1 - td, Duration(months=2, days=-2, seconds=2.5))
+        assert d1 - td == Duration(months=2, days=-2, **sec2_kwargs)
 
     def test_subtract_object(self):
-        with self.assertRaises(TypeError):
+        with pytest.raises(TypeError):
             _ = Duration(months=2, days=3, seconds=5.7) - object()
 
     def test_multiplication_by_int(self):
         d1 = Duration(months=2, days=3, seconds=5.7)
         i = 11
-        self.assertEqual(d1 * i, Duration(months=22, days=33, seconds=62.7))
+        assert d1 * i == Duration(months=22, days=33, seconds=62.7)
 
     def test_multiplication_by_float(self):
         d1 = Duration(months=2, days=3, seconds=5.7)
         f = 5.5
-        self.assertEqual(d1 * f, Duration(months=11, days=16, seconds=31.35))
+        assert d1 * f == Duration(months=11, days=16, seconds=31.35)
 
     def test_multiplication_by_object(self):
-        with self.assertRaises(TypeError):
+        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)
         i = 2
-        self.assertEqual(d1 // i, Duration(months=5, days=16, seconds=27.0))
+        assert d1 // i == Duration(months=5, days=16, seconds=27)
 
     def test_floor_division_by_object(self):
-        with self.assertRaises(TypeError):
+        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)
         i = 2
-        self.assertEqual(d1 % i, Duration(months=1, days=1, seconds=1.77))
+        assert d1 % i == Duration(months=1, days=1, seconds=1.77)
 
     def test_modulus_by_object(self):
-        with self.assertRaises(TypeError):
+        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)
         i = 2
-        self.assertEqual(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.0),
+                                 Duration(months=1, days=1, seconds=1.77))
 
     def test_floor_division_and_modulus_by_object(self):
-        with self.assertRaises(TypeError):
+        with pytest.raises(TypeError):
             _ = divmod(Duration(months=2, days=3, seconds=5.7), object())
 
     def test_true_division_by_int(self):
         d1 = Duration(months=11, days=33, seconds=55.77)
         i = 2
-        self.assertEqual(d1 / i, Duration(months=6, days=16, seconds=27.885))
+        assert d1 / i == Duration(months=6, days=16, seconds=27.885)
 
     def test_true_division_by_float(self):
         d1 = Duration(months=11, days=33, seconds=55.77)
         f = 2.5
-        self.assertEqual(d1 / f, Duration(months=4, days=13, seconds=22.308))
+        assert d1 / f == Duration(months=4, days=13, seconds=22.308)
 
     def test_true_division_by_object(self):
-        with self.assertRaises(TypeError):
+        with pytest.raises(TypeError):
             _ = Duration(months=2, days=3, seconds=5.7) / object()
 
     def test_unary_plus(self):
         d = Duration(months=11, days=33, seconds=55.77)
-        self.assertEqual(+d, Duration(months=11, days=33, seconds=55.77))
+        assert +d == Duration(months=11, days=33, seconds=55.77)
 
     def test_unary_minus(self):
         d = Duration(months=11, days=33, seconds=55.77)
-        self.assertEqual(-d, Duration(months=-11, days=-33, seconds=-55.77))
+        assert -d == Duration(months=-11, days=-33, seconds=-55.77)
 
     def test_absolute(self):
         d = Duration(months=-11, days=-33, seconds=-55.77)
-        self.assertEqual(abs(d), Duration(months=11, days=33, seconds=55.77))
+        assert abs(d) == Duration(months=11, days=33, seconds=55.77)
 
     def test_str(self):
-        self.assertEqual(str(Duration()), "PT0S")
-        self.assertEqual(str(Duration(years=1, months=2)), "P1Y2M")
-        self.assertEqual(str(Duration(years=-1, months=2)), "P-10M")
-        self.assertEqual(str(Duration(months=-13)), "P-1Y-1M")
-        self.assertEqual(str(Duration(months=2, days=3, seconds=5.7)), "P2M3DT5.7S")
-        self.assertEqual(str(Duration(hours=12, minutes=34)), "PT12H34M")
-        self.assertEqual(str(Duration(seconds=59)), "PT59S")
-        self.assertEqual(str(Duration(seconds=0.123456789)), "PT0.123456789S")
-        self.assertEqual(str(Duration(seconds=-0.123456789)), "PT-0.123456789S")
+        assert str(Duration()) == "PT0S"
+        assert str(Duration(years=1, months=2)) == "P1Y2M"
+        assert str(Duration(years=-1, months=2)) == "P-10M"
+        assert str(Duration(months=-13)) == "P-1Y-1M"
+        assert str(Duration(months=2, days=3, seconds=5.7)) == "P2M3DT5.7S"
+        assert str(Duration(hours=12, minutes=34)) == "PT12H34M"
+        assert str(Duration(seconds=59)) == "PT59S"
+        assert str(Duration(seconds=0.123456789)) == "PT0.123456789S"
+        assert str(Duration(seconds=-0.123456789)) == "PT-0.123456789S"
+        assert str(Duration(seconds=-2, nanoseconds=1)) == "PT-1.999999999S"
 
     def test_repr(self):
         d = Duration(months=2, days=3, seconds=5.7)
-        self.assertEqual(repr(d), "Duration(months=2, days=3, seconds=5, subseconds=0.7)")
+        assert repr(d) == "Duration(months=2, days=3, seconds=5, nanoseconds=700000000)"
 
     def test_iso_format(self):
-        self.assertEqual(Duration().iso_format(), "PT0S")
-        self.assertEqual(Duration(years=1, months=2).iso_format(), "P1Y2M")
-        self.assertEqual(Duration(years=-1, months=2).iso_format(), "P-10M")
-        self.assertEqual(Duration(months=-13).iso_format(), "P-1Y-1M")
-        self.assertEqual(Duration(months=2, days=3, seconds=5.7).iso_format(), "P2M3DT5.7S")
-        self.assertEqual(Duration(hours=12, minutes=34).iso_format(), "PT12H34M")
-        self.assertEqual(Duration(seconds=59).iso_format(), "PT59S")
-        self.assertEqual(Duration(seconds=0.123456789).iso_format(), "PT0.123456789S")
-        self.assertEqual(Duration(seconds=-0.123456789).iso_format(), "PT-0.123456789S")
+        assert Duration().iso_format() == "PT0S"
+        assert Duration(years=1, months=2).iso_format() == "P1Y2M"
+        assert Duration(years=-1, months=2).iso_format() == "P-10M"
+        assert Duration(months=-13).iso_format() == "P-1Y-1M"
+        assert Duration(months=2, days=3, seconds=5.7).iso_format() == "P2M3DT5.7S"
+        assert Duration(hours=12, minutes=34).iso_format() == "PT12H34M"
+        assert Duration(seconds=59).iso_format() == "PT59S"
+        assert Duration(seconds=0.123456789).iso_format() == "PT0.123456789S"
+        assert Duration(seconds=-0.123456789).iso_format() == "PT-0.123456789S"
+
+    def test_copy(self):
+        d = Duration(years=1, months=2, days=3, hours=4, minutes=5, seconds=6,
+                     milliseconds=7, microseconds=8, nanoseconds=9)
+        d2 = copy.copy(d)
+        assert d is not d2
+        assert d == d2
+
+    def test_deep_copy(self):
+        d = Duration(years=1, months=2, days=3, hours=4, minutes=5, seconds=6,
+                     milliseconds=7, microseconds=8, nanoseconds=9)
+        d2 = copy.deepcopy(d)
+        assert d is not d2
+        assert d == d2
 
     def test_from_iso_format(self):
-        self.assertEqual(Duration(), Duration.from_iso_format("PT0S"))
-        self.assertEqual(Duration(hours=12, minutes=34, seconds=56.789),
-                         Duration.from_iso_format("PT12H34M56.789S"))
-        self.assertEqual(Duration(years=1, months=2, days=3),
-                         Duration.from_iso_format("P1Y2M3D"))
-        self.assertEqual(Duration(years=1, months=2, days=3, hours=12, minutes=34, seconds=56.789),
-                         Duration.from_iso_format("P1Y2M3DT12H34M56.789S"))
+        assert Duration() == Duration.from_iso_format("PT0S")
+        assert Duration(
+            hours=12, minutes=34, seconds=56.789
+        ) == Duration.from_iso_format("PT12H34M56.789S")
+        assert Duration(
+            years=1, months=2, days=3
+        ) == Duration.from_iso_format("P1Y2M3D")
+        assert Duration(
+            years=1, months=2, days=3, hours=12, minutes=34, seconds=56.789
+        ) == Duration.from_iso_format("P1Y2M3DT12H34M56.789S")
+        # test for float precision issues
+        for i in range(500006000, 500010000, 1000):
+            assert Duration(
+                years=1, months=2, days=3, hours=12, minutes=34, nanoseconds=i
+            ) == Duration.from_iso_format("P1Y2M3DT12H34M00.%sS" % str(i))
+            assert Duration(
+                years=1, months=2, days=3, hours=12, minutes=34, nanoseconds=i
+            ) == Duration.from_iso_format("P1Y2M3DT12H34M00.%sS" % str(i)[:-3])
diff --git a/tests/unit/time/test_hydration.py b/tests/unit/time/test_hydration.py
index fbe86a29..06cffa78 100644
--- a/tests/unit/time/test_hydration.py
+++ b/tests/unit/time/test_hydration.py
@@ -19,6 +19,7 @@
 # limitations under the License.
 
 
+from decimal import Decimal
 from unittest import TestCase
 
 from neo4j.data import DataHydrator
@@ -38,4 +39,4 @@ class TemporalHydrationTestCase(TestCase):
         self.assertEqual(dt.day, 12)
         self.assertEqual(dt.hour, 11)
         self.assertEqual(dt.minute, 37)
-        self.assertEqual(dt.second, 41.474716862)
+        self.assertEqual(dt.second, Decimal("41.474716862"))
diff --git a/tests/unit/time/test_time.py b/tests/unit/time/test_time.py
index f6e8dce0..bb5203f6 100644
--- a/tests/unit/time/test_time.py
+++ b/tests/unit/time/test_time.py
@@ -20,9 +20,11 @@
 
 
 from datetime import time
-from unittest import TestCase
+from decimal import Decimal
 
+import pytest
 from pytz import (
+    build_tzinfo,
     timezone,
     FixedOffset,
 )
@@ -38,130 +40,147 @@ timezone_us_eastern = timezone("US/Eastern")
 timezone_utc = timezone("UTC")
 
 
-class TimeTestCase(TestCase):
+def seconds_options(seconds, nanoseconds):
+    yield seconds, nanoseconds
+    yield seconds + nanoseconds / 1000000000,
+
+
+class TimeTestCase:
 
     def test_bad_attribute(self):
         t = Time(12, 34, 56.789)
-        with self.assertRaises(AttributeError):
+        with pytest.raises(AttributeError):
             _ = t.x
 
     def test_simple_time(self):
         t = Time(12, 34, 56.789)
-        self.assertEqual(t.hour_minute_second, (12, 34, 56.789))
-        self.assertEqual(t.ticks, 45296.789)
-        self.assertEqual(t.hour, 12)
-        self.assertEqual(t.minute, 34)
-        self.assertEqual(t.second, 56.789)
+        assert t.hour_minute_second == (12, 34, 56.789)
+        assert t.hour_minute_second_nanoseconds == (12, 34, 56, 789000000)
+        assert t.ticks == 45296.789
+        assert t.hour == 12
+        assert t.minute == 34
+        assert t.second == 56.789
 
     def test_midnight(self):
         t = Time(0, 0, 0)
-        self.assertEqual(t.hour_minute_second, (0, 0, 0))
-        self.assertEqual(t.ticks, 0)
-        self.assertEqual(t.hour, 0)
-        self.assertEqual(t.minute, 0)
-        self.assertEqual(t.second, 0)
+        assert t.hour_minute_second == (0, 0, 0)
+        assert t.ticks == 0
+        assert t.hour == 0
+        assert t.minute == 0
+        assert t.second == 0
 
     def test_nanosecond_precision(self):
         t = Time(12, 34, 56.789123456)
-        self.assertEqual(t.hour_minute_second, (12, 34, 56.789123456))
-        self.assertEqual(t.ticks, 45296.789123456)
-        self.assertEqual(t.hour, 12)
-        self.assertEqual(t.minute, 34)
-        self.assertEqual(t.second, 56.789123456)
+        assert t.hour_minute_second == (12, 34, 56.789123456)
+        assert t.ticks == 45296.789123456
+        assert t.hour == 12
+        assert t.minute == 34
+        assert t.second == 56.789123456
 
     def test_str(self):
-        t = Time(12, 34, 56.789123456)
-        self.assertEqual(str(t), "12:34:56.789123456")
+        t = Time(12, 34, 56, 789123456)
+        assert str(t) == "12:34:56.789123456"
 
     def test_now_without_tz(self):
         t = Time.now()
-        self.assertIsInstance(t, Time)
+        assert isinstance(t, Time)
 
     def test_now_with_tz(self):
         t = Time.now(tz=timezone_us_eastern)
-        self.assertIsInstance(t, Time)
-        self.assertEqual(t.tzinfo, timezone_us_eastern)
+        assert isinstance(t, Time)
+        assert t.tzinfo == timezone_us_eastern
 
     def test_utc_now(self):
         t = Time.utc_now()
-        self.assertIsInstance(t, Time)
+        assert isinstance(t, Time)
 
     def test_from_native(self):
         native = time(12, 34, 56, 789123)
         t = Time.from_native(native)
-        self.assertEqual(t.hour, native.hour)
-        self.assertEqual(t.minute, native.minute)
-        self.assertEqual(t.second, nano_add(native.second, nano_div(native.microsecond, 1000000)))
+        assert t.hour == native.hour
+        assert t.minute == native.minute
+        assert t.second == nano_add(native.second, nano_div(native.microsecond, 1000000))
 
     def test_to_native(self):
         t = Time(12, 34, 56.789123456)
         native = t.to_native()
-        self.assertEqual(t.hour, native.hour)
-        self.assertEqual(t.minute, native.minute)
-        self.assertEqual(56.789123, nano_add(native.second, nano_div(native.microsecond, 1000000)))
+        assert t.hour == native.hour
+        assert t.minute == native.minute
+        assert 56.789123 == nano_add(native.second, nano_div(native.microsecond, 1000000))
 
     def test_iso_format(self):
-        t = Time(12, 34, 56.789123456)
-        self.assertEqual("12:34:56.789123456", t.iso_format())
+        t = Time(12, 34, 56, 789123456)
+        assert "12:34:56.789123456" == t.iso_format()
 
     def test_iso_format_with_trailing_zeroes(self):
-        t = Time(12, 34, 56.789)
-        self.assertEqual("12:34:56.789000000", t.iso_format())
+        t = Time(12, 34, 56, 789)
+        assert "12:34:56.789000000" == t.iso_format()
 
     def test_from_iso_format_hour_only(self):
         expected = Time(12, 0, 0)
         actual = Time.from_iso_format("12")
-        self.assertEqual(expected, actual)
+        assert expected == actual
 
     def test_from_iso_format_hour_and_minute(self):
         expected = Time(12, 34, 0)
         actual = Time.from_iso_format("12:34")
-        self.assertEqual(expected, actual)
+        assert expected == actual
 
     def test_from_iso_format_hour_minute_second(self):
         expected = Time(12, 34, 56)
         actual = Time.from_iso_format("12:34:56")
-        self.assertEqual(expected, actual)
+        assert expected == actual
 
     def test_from_iso_format_hour_minute_second_milliseconds(self):
-        expected = Time(12, 34, 56.123)
+        expected = Time(12, 34, 56, 123)
         actual = Time.from_iso_format("12:34:56.123")
-        self.assertEqual(expected, actual)
+        assert expected == actual
 
     def test_from_iso_format_hour_minute_second_microseconds(self):
-        expected = Time(12, 34, 56.123456)
+        expected = Time(12, 34, 56, 12345600)
         actual = Time.from_iso_format("12:34:56.123456")
-        self.assertEqual(expected, actual)
+        assert expected == actual
 
     def test_from_iso_format_hour_minute_second_nanoseconds(self):
-        expected = Time(12, 34, 56.123456789)
+        expected = Time(12, 34, 56, 123456789)
         actual = Time.from_iso_format("12:34:56.123456789")
-        self.assertEqual(expected, actual)
+        assert expected == actual
 
     def test_from_iso_format_with_positive_tz(self):
-        expected = Time(12, 34, 56.123456789, tzinfo=FixedOffset(754))
+        expected = Time(12, 34, 56, 123456789, tzinfo=FixedOffset(754))
         actual = Time.from_iso_format("12:34:56.123456789+12:34")
-        self.assertEqual(expected, actual)
+        assert expected == actual
 
     def test_from_iso_format_with_negative_tz(self):
-        expected = Time(12, 34, 56.123456789, tzinfo=FixedOffset(-754))
+        expected = Time(12, 34, 56, 123456789, tzinfo=FixedOffset(-754))
         actual = Time.from_iso_format("12:34:56.123456789-12:34")
-        self.assertEqual(expected, actual)
+        assert expected == actual
 
     def test_from_iso_format_with_positive_long_tz(self):
-        expected = Time(12, 34, 56.123456789, tzinfo=FixedOffset(754))
+        expected = Time(12, 34, 56, 123456789, tzinfo=FixedOffset(754))
         actual = Time.from_iso_format("12:34:56.123456789+12:34:56.123456")
-        self.assertEqual(expected, actual)
+        assert expected == actual
 
     def test_from_iso_format_with_negative_long_tz(self):
-        expected = Time(12, 34, 56.123456789, tzinfo=FixedOffset(-754))
+        expected = Time(12, 34, 56, 123456789, tzinfo=FixedOffset(-754))
         actual = Time.from_iso_format("12:34:56.123456789-12:34:56.123456")
-        self.assertEqual(expected, actual)
+        assert expected == actual
+
+    def test_utc_offset_fixed(self):
+        expected = Time(12, 34, 56, 123456789, tzinfo=FixedOffset(-754))
+        actual = -754 * 60
+        assert expected.utc_offset().total_seconds() == actual
 
+    def test_utc_offset_variable(self):
+        expected = Time(12, 34, 56, 123456789,
+                        tzinfo=build_tzinfo()
+                        )
+        actual = -754 * 60
+        assert expected.utc_offset().total_seconds() == actual
 
 def test_iso_format_with_time_zone_case_1():
     # python -m pytest tests/unit/time/test_time.py -s -v -k test_iso_format_with_time_zone_case_1
-    expected = Time(7, 54, 2.129790999, tzinfo=timezone_utc)
+    expected = Time(7, 54, 2, 129790999, tzinfo=timezone_utc)
     assert expected.iso_format() == "07:54:02.129790999+00:00"
     assert expected.tzinfo == FixedOffset(0)
     actual = Time.from_iso_format("07:54:02.129790999+00:00")
@@ -177,7 +196,7 @@ def test_iso_format_with_time_zone_case_2():
 
 def test_to_native_case_1():
     # python -m pytest tests/unit/time/test_time.py -s -v -k test_to_native_case_1
-    t = Time(12, 34, 56.789123456)
+    t = Time(12, 34, 56, 789123456)
     native = t.to_native()
     assert native.hour == t.hour
     assert native.minute == t.minute
@@ -188,7 +207,7 @@ def test_to_native_case_1():
 
 def test_to_native_case_2():
     # python -m pytest tests/unit/time/test_time.py -s -v -k test_to_native_case_2
-    t = Time(12, 34, 56.789123456, tzinfo=timezone_utc)
+    t = Time(12, 34, 56, 789123456, tzinfo=timezone_utc)
     native = t.to_native()
     assert native.hour == t.hour
     assert native.minute == t.minute
@@ -203,7 +222,7 @@ def test_from_native_case_1():
     t = Time.from_native(native)
     assert t.hour == native.hour
     assert t.minute == native.minute
-    assert t.second == nano_add(native.second, nano_div(native.microsecond, 1000000))
+    assert t.second == Decimal(native.microsecond) / 1000000 + native.second
     assert t.tzinfo is None
 
 
@@ -213,5 +232,5 @@ def test_from_native_case_2():
     t = Time.from_native(native)
     assert t.hour == native.hour
     assert t.minute == native.minute
-    assert t.second == nano_add(native.second, nano_div(native.microsecond, 1000000))
+    assert t.second == Decimal(native.microsecond) / 1000000 + native.second
     assert t.tzinfo == FixedOffset(0)