From 489abe81f6c35c04bbf0f3d178a017594b2abb2d Mon Sep 17 00:00:00 2001 From: Prychantovskyi <75383953+Prychantovskyi@users.noreply.github.com> Date: Fri, 22 Jul 2022 09:27:22 +0100 Subject: [PATCH 01/19] Add files via upload --- __init__.py | 2624 +++++++++++++++++++++++++++++++++++++++ test_cartesian_point.py | 44 + test_clock.py | 72 ++ test_clocktime.py | 103 ++ test_date.py | 562 +++++++++ test_point.py | 52 + test_wgs84_point.py | 62 + 7 files changed, 3519 insertions(+) create mode 100644 __init__.py create mode 100644 test_cartesian_point.py create mode 100644 test_clock.py create mode 100644 test_clocktime.py create mode 100644 test_date.py create mode 100644 test_point.py create mode 100644 test_wgs84_point.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 000000000..5d0edf244 --- /dev/null +++ b/__init__.py @@ -0,0 +1,2624 @@ +# Copyright (c) "Neo4j" +# Neo4j Sweden AB [https://neo4j.com] +# +# This file is part of Neo4j. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +""" +This module contains the fundamental types used for temporal accounting as well +as a number of utility functions. +""" + + +from datetime import ( + date, + datetime, + time, + timedelta, + timezone, +) +from functools import total_ordering +from re import compile as re_compile +from time import ( + gmtime, + mktime, + struct_time, +) + +from ._arithmetic import ( + nano_add, + nano_div, + round_half_to_even, + symmetric_divmod, +) +from ._metaclasses import ( + DateTimeType, + DateType, + TimeType, +) + + +__all__ = [ + "MIN_INT64", + "MAX_INT64", + "MIN_YEAR", + "MAX_YEAR", + "Duration", + "Date", + "ZeroDate", + "Time", + "Midnight", + "Midday", + "DateTime", + "Never", + "UnixEpoch", +] + + +MIN_INT64 = -(2 ** 63) +MAX_INT64 = (2 ** 63) - 1 + +#: The smallest year number allowed in a :class:`.Date` or :class:`.DateTime` +#: object to be compatible with :class:`datetime.date` and +#: :class:`datetime.datetime`. +MIN_YEAR = 1 + +#: The largest year number allowed in a :class:`.Date` or :class:`.DateTime` +#: object to be compatible with :class:`datetime.date` and +#: :class:`datetime.datetime`. +MAX_YEAR = 9999 + +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)?)?$" +) + +NANO_SECONDS = 1000000000 +AVERAGE_SECONDS_IN_MONTH = 2629746 +AVERAGE_SECONDS_IN_DAY = 86400 + + +def _is_leap_year(year): + if year % 4 != 0: + return False + if year % 100 != 0: + return True + return year % 400 == 0 + + +IS_LEAP_YEAR = {year: _is_leap_year(year) for year in range(MIN_YEAR, MAX_YEAR + 1)} + + +def _days_in_year(year): + return 366 if IS_LEAP_YEAR[year] else 365 + + +DAYS_IN_YEAR = {year: _days_in_year(year) for year in range(MIN_YEAR, MAX_YEAR + 1)} + + +def _days_in_month(year, month): + if month in (9, 4, 6, 11): + return 30 + elif month != 2: + return 31 + else: + return 29 if IS_LEAP_YEAR[year] else 28 + + +DAYS_IN_MONTH = {(year, month): _days_in_month(year, month) + for year in range(MIN_YEAR, MAX_YEAR + 1) for month in range(1, 13)} + + +def _normalize_day(year, month, day): + """ Coerce the day of the month to an internal value that may or + may not match the "public" value. + + With the exception of the last three days of every month, all + days are stored as-is. The last three days are instead stored + as -1 (the last), -2 (the second to last) and -3 (the third to + last). + + Therefore, for a 28-day month, the last week is as follows: + + Day | 22 23 24 25 26 27 28 + Value | 22 23 24 25 -3 -2 -1 + + For a 29-day month, the last week is as follows: + + Day | 23 24 25 26 27 28 29 + Value | 23 24 25 26 -3 -2 -1 + + For a 30-day month, the last week is as follows: + + Day | 24 25 26 27 28 29 30 + Value | 24 25 26 27 -3 -2 -1 + + For a 31-day month, the last week is as follows: + + Day | 25 26 27 28 29 30 31 + Value | 25 26 27 28 -3 -2 -1 + + This slightly unintuitive system makes some temporal arithmetic + produce a more desirable outcome. + + :param year: + :param month: + :param day: + :return: + """ + if year < MIN_YEAR or year > MAX_YEAR: + raise ValueError("Year out of range (%d..%d)" % (MIN_YEAR, MAX_YEAR)) + if month < 1 or month > 12: + raise ValueError("Month out of range (1..12)") + days_in_month = DAYS_IN_MONTH[(year, month)] + if day in (days_in_month, -1): + return year, month, -1 + if day in (days_in_month - 1, -2): + return year, month, -2 + if day in (days_in_month - 2, -3): + return year, month, -3 + if 1 <= day <= days_in_month - 3: + return year, month, int(day) + # TODO improve this error message + raise ValueError("Day %d out of range (1..%d, -1, -2 ,-3)" % (day, days_in_month)) + + +class ClockTime(tuple): + """ A count of `seconds` and `nanoseconds`. This class can be used to + mark a particular point in time, relative to an externally-specified + epoch. + + The `seconds` and `nanoseconds` values provided to the constructor can + can have any sign but will be normalized internally into a positive or + negative `seconds` value along with a positive `nanoseconds` value + between `0` and `999,999,999`. Therefore ``ClockTime(-1, -1)`` is + normalized to ``ClockTime(-2, 999999999)``. + + Note that the structure of a :class:`.ClockTime` object is similar to + the ``timespec`` struct in C. + """ + + def __new__(cls, seconds=0, nanoseconds=0): + seconds, nanoseconds = divmod( + int(NANO_SECONDS * seconds) + int(nanoseconds), NANO_SECONDS + ) + return tuple.__new__(cls, (seconds, nanoseconds)) + + def __add__(self, other): + if isinstance(other, (int, float)): + other = ClockTime(other) + if isinstance(other, ClockTime): + return ClockTime(self.seconds + other.seconds, self.nanoseconds + other.nanoseconds) + if isinstance(other, Duration): + 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.nanoseconds)) + return NotImplemented + + def __sub__(self, other): + if isinstance(other, (int, float)): + other = ClockTime(other) + if isinstance(other, ClockTime): + return ClockTime(self.seconds - other.seconds, self.nanoseconds - other.nanoseconds) + 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.nanoseconds)) + return NotImplemented + + def __repr__(self): + return "ClockTime(seconds=%r, nanoseconds=%r)" % self + + @property + def seconds(self): + return self[0] + + @property + def nanoseconds(self): + return self[1] + + +class Clock: + """ Accessor for time values. This class is fulfilled by implementations + that subclass :class:`.Clock`. These implementations are contained within + the ``neo4j.time.clock_implementations`` module, and are not intended to be + accessed directly. + + Creating a new :class:`.Clock` instance will produce the highest + precision clock implementation available. + + >>> clock = Clock() + >>> type(clock) # doctest: +SKIP + neo4j.time.clock_implementations.LibCClock + >>> clock.local_time() # doctest: +SKIP + ClockTime(seconds=1525265942, nanoseconds=506844026) + + """ + + _Clock__implementations = None + __implementations = None + + def __new__(cls): + if cls.__implementations is None: + # Find an available clock with the best precision + import neo4j.time._clock_implementations + cls.__implementations = sorted((clock for clock in Clock.__subclasses__() if clock.available()), + key=lambda clock: clock.precision(), reverse=True) + if not cls.__implementations: + raise RuntimeError("No clock implementations available") + instance = object.__new__(cls.__implementations[0]) + return instance + + @classmethod + def precision(cls): + """ The precision of this clock implementation, represented as a + number of decimal places. Therefore, for a nanosecond precision + clock, this function returns `9`. + """ + raise NotImplementedError("No clock implementation selected") + + @classmethod + def available(cls): + """ A boolean flag to indicate whether or not this clock + implementation is available on this platform. + """ + raise NotImplementedError("No clock implementation selected") + + @classmethod + def local_offset(cls): + """The offset from UTC for local time read from this clock. + This may raise OverflowError if not supported, because of platform depending C libraries. + + :returns: + :rtype: + + :raises OverflowError: + """ + # 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. + This may raise OverflowError if not supported, because of platform depending C libraries. + + :returns: + :rtype: + + :raises OverflowError: + """ + return self.utc_time() + self.local_offset() + + def utc_time(self): + """ Read and return the current UTC time from this clock, measured + relative to the Unix Epoch. + """ + raise NotImplementedError("No clock implementation selected") + + +class Duration(tuple): + """A difference between two points in time. + + A :class:`.Duration` represents the difference between two points in time. + Duration objects store a composite value of `months`, `days`, `seconds`, + and `nanoseconds`. Unlike :class:`datetime.timedelta` however, days, and + seconds/nanoseconds are never interchanged. All values except seconds and + nanoseconds are applied separately in calculations (element-wise). + + A :class:`.Duration` stores four primary instance attributes internally: + `months`, `days`, `seconds` and `nanoseconds`. These are maintained as + individual values and are immutable. Each of these four attributes can carry + its own sign, with the exception of `nanoseconds`, which always has the same + sign as `seconds`. The constructor will establish this state, should the + duration be initialized with conflicting `seconds` and `nanoseconds` signs. + This structure allows the modelling of durations such as + `3 months minus 2 days`. + + To determine if a :class:`Duration` `d` is overflowing the accepted values + of the database, first, all `nanoseconds` outside the range -999_999_999 and + 999_999_999 are transferred into the seconds field. Then, `months`, `days`, + and `seconds` are summed up like so: + `months * 2629746 + days * 86400 + d.seconds + d.nanoseconds // 1000000000`. + (Like the integer division in Python, this one is to be understood as + rounding down rather than towards 0.) + This value must be between -(2\\ :sup:`63`) and (2\\ :sup:`63` - 1) + inclusive. + + :param years: will be added times 12 to `months` + :type years: float + :param months: will be truncated to :class:`int` (`int(months)`) + :type months: float + :param weeks: will be added times 7 to `days` + :type weeks: float + :param days: will be truncated to :class:`int` (`int(days)`) + :type days: float + :param hours: will be added times 3,600,000,000,000 to `nanoseconds` + :type hours: float + :param minutes: will be added times 60,000,000,000 to `nanoseconds` + :type minutes: float + :param seconds: will be added times 1,000,000,000 to `nanoseconds`` + :type seconds: float + :param milliseconds: will be added times 1,000,000 to `nanoseconds` + :type microseconds: float + :param microseconds: will be added times 1,000 to `nanoseconds` + :type milliseconds: float + :param nanoseconds: will be truncated to :class:`int` (`int(nanoseconds)`) + :type nanoseconds: float + + :raises ValueError: the components exceed the limits as described above. + """ + + # i64: i64:i64: i32 + + min = None + """The lowest duration value possible.""" + + max = None + """The highest duration value possible.""" + + def __new__(cls, years=0, months=0, weeks=0, days=0, hours=0, minutes=0, + seconds=0, milliseconds=0, microseconds=0, nanoseconds=0): + mo = int(12 * years + months) + if mo < MIN_INT64 or mo > MAX_INT64: + raise ValueError("Months value out of range") + 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) + avg_total_seconds = (mo * AVERAGE_SECONDS_IN_MONTH + + d * AVERAGE_SECONDS_IN_DAY + + s + - (1 if ns < 0 else 0)) + if avg_total_seconds < MIN_INT64 or avg_total_seconds > MAX_INT64: + raise ValueError("Duration value out of range: %r", + cls.__repr__((mo, d, s, ns))) + return tuple.__new__(cls, (mo, d, s, ns)) + + def __bool__(self): + """Falsy if all primary instance attributes are.""" + return any(map(bool, self)) + + __nonzero__ = __bool__ + + def __add__(self, other): + """Add a :class:`.Duration` or :class:`datetime.timedelta`. + + :rtype: Duration + """ + if isinstance(other, Duration): + 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] + other.days, + seconds=self[2] + other.seconds, + nanoseconds=self[3] + other.microseconds * 1000 + ) + return NotImplemented + + def __sub__(self, other): + """Subtract a :class:`.Duration` or :class:`datetime.timedelta`. + + :rtype: Duration + """ + if isinstance(other, Duration): + 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] - other.days, + seconds=self[2] - other.seconds, + nanoseconds=self[3] - other.microseconds * 1000 + ) + return NotImplemented + + def __mul__(self, other): + """Multiply by an :class:`int` or :class:`float`. + + The operation is performed element-wise on + ``(months, days, nanaoseconds)`` where + + * years go into months, + * weeks go into days, + * seconds and all sub-second units go into nanoseconds. + + Each element will be rounded to the nearest integer (.5 towards even). + + :rtype: Duration + """ + if isinstance(other, (int, float)): + 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 + + def __floordiv__(self, other): + """Integer division by an :class:`int`. + + The operation is performed element-wise on + ``(months, days, nanaoseconds)`` where + + * years go into months, + * weeks go into days, + * seconds and all sub-second units go into nanoseconds. + + Each element will be rounded towards -inf. + + :rtype: Duration + """ + if isinstance(other, int): + return Duration( + months=self[0] // other, days=self[1] // other, + nanoseconds=(self[2] * NANO_SECONDS + self[3]) // other + ) + return NotImplemented + + def __mod__(self, other): + """Modulo operation by an :class:`int`. + + The operation is performed element-wise on + ``(months, days, nanaoseconds)`` where + + * years go into months, + * weeks go into days, + * seconds and all sub-second units go into nanoseconds. + + :rtype: Duration + """ + if isinstance(other, int): + return Duration( + months=self[0] % other, days=self[1] % other, + nanoseconds=(self[2] * NANO_SECONDS + self[3]) % other + ) + return NotImplemented + + def __divmod__(self, other): + """Division and modulo operation by an :class:`int`. + + See :meth:`__floordiv__` and :meth:`__mod__`. + + :rtype: (Duration, Duration) + """ + if isinstance(other, int): + return self.__floordiv__(other), self.__mod__(other) + return NotImplemented + + def __truediv__(self, other): + """Division by an :class:`int` or :class:`float`. + + The operation is performed element-wise on + ``(months, days, nanaoseconds)`` where + + * years go into months, + * weeks go into days, + * seconds and all sub-second units go into nanoseconds. + + Each element will be rounded to the nearest integer (.5 towards even). + + :rtype: Duration + """ + if isinstance(other, (int, float)): + return Duration( + months=round_half_to_even(self[0] / other), + days=round_half_to_even(self[1] / other), + nanoseconds=round_half_to_even( + self[2] * NANO_SECONDS / other + + self[3] / other + ) + ) + return NotImplemented + + def __pos__(self): + """""" + return self + + def __neg__(self): + """""" + 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]), nanoseconds=abs(self[3])) + + def __repr__(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.__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): + """Parse a ISO formatted duration string. + + Accepted formats (all lowercase letters are placeholders): + 'P', a zero length duration + 'PyY', y being a number of years + 'PmM', m being a number of months + 'PdD', d being a number of days + + Any combination of the above, e.g., 'P25Y1D' for 25 years and 1 day. + + 'PThH', h being a number of hours + 'PTmM', h being a number of minutes + 'PTsS', h being a number of seconds + 'PTs.sss...S', h being a fractional number of seconds + + Any combination of the above, e.g. 'PT5H1.2S' for 5 hours and 1.2 + seconds. + Any combination of all options, e.g. 'P13MT100M' for 13 months and + 100 minutes. + + :param s: String to parse + :type s: str + + :rtype: Duration + + :raises ValueError: if the string does not match the required format. + """ + 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(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") + + fromisoformat = from_iso_format + + def iso_format(self, sep="T"): + """Return the :class:`Duration` as ISO formatted string. + + :param sep: the separator before the time components. + :type sep: str + + :rtype: str + """ + parts = [] + hours, minutes, seconds, nanoseconds = \ + self.hours_minutes_seconds_nanoseconds + if hours: + parts.append("%dH" % hours) + if minutes: + parts.append("%dM" % minutes) + 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: + 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 + if days: + parts.insert(0, "%dD" % days) + if months: + parts.insert(0, "%dM" % months) + if years: + parts.insert(0, "%dY" % years) + if parts: + parts.insert(0, "P") + return "".join(parts) + else: + return "PT0S" + + @property + def months(self): + """The months of the :class:`Duration`. + + :type: int + """ + return self[0] + + @property + def days(self): + """The days of the :class:`Duration`. + + :type: int + """ + return self[1] + + @property + def seconds(self): + """The seconds of the :class:`Duration`. + + :type: int + """ + return self[2] + + @property + def nanoseconds(self): + """The nanoseconds of the :class:`Duration`. + + :type: int + """ + return self[3] + + @property + def years_months_days(self): + """ + + :return: + """ + years, months = symmetric_divmod(self[0], 12) + return years, months, self[1] + + @property + def hours_minutes_seconds_nanoseconds(self): + """ A 4-tuple of (hours, minutes, seconds, nanoseconds). + + :type: (int, int, int, int) + """ + minutes, seconds = symmetric_divmod(self[2], 60) + hours, minutes = symmetric_divmod(minutes, 60) + return hours, minutes, seconds, self[3] + + +Duration.min = Duration(seconds=MIN_INT64, nanoseconds=0) +Duration.max = Duration(seconds=MAX_INT64, nanoseconds=999999999) + + +class Date(metaclass=DateType): + """Idealized date representation. + + A :class:`.Date` object represents a date (year, month, and day) in the + `proleptic Gregorian Calendar + `_. + + Years between `0001` and `9999` are supported, with additional support for + the "zero date" used in some contexts. + + Each date is based on a proleptic Gregorian ordinal, which models + 1 Jan 0001 as `day 1` and counts each subsequent day up to, and including, + 31 Dec 9999. The standard `year`, `month` and `day` value of each date is + also available. + + Internally, the day of the month is always stored as-is, with the exception + of the last three days of that month. These are always stored as + -1, -2 and -3 (counting from the last day). This system allows some temporal + arithmetic (particularly adding or subtracting months) to produce a more + desirable outcome than would otherwise be produced. Externally, the day + number is always the same as would be written on a calendar. + + :param year: the year. Minimum :attr:`.MIN_YEAR` (0001), maximum + :attr:`.MAX_YEAR` (9999). + :type year: int + :param month: the month. Minimum 1, maximum 12. + :type month: int + :param day: the day. Minimum 1, maximum + :attr:`Date.days_in_month(year, month) `. + :type day: int + + A zero date can also be acquired by passing all zeroes to the + :class:`neo4j.time.Date` constructor or by using the :attr:`ZeroDate` + constant. + """ + + # CONSTRUCTOR # + + def __new__(cls, year, month, day): + if year == month == day == 0: + return ZeroDate + year, month, day = _normalize_day(year, month, day) + ordinal = cls.__calc_ordinal(year, month, day) + return cls.__new(ordinal, year, month, day) + + @classmethod + def __new(cls, ordinal, year, month, day): + instance = object.__new__(cls) + instance.__ordinal = int(ordinal) + instance.__year = int(year) + instance.__month = int(month) + instance.__day = int(day) + return instance + + def __getattr__(self, name): + """ Map standard library attribute names to local attribute names, + for compatibility. + """ + try: + return { + "isocalendar": self.iso_calendar, + "isoformat": self.iso_format, + "isoweekday": self.iso_weekday, + "strftime": self.__format__, + "toordinal": self.to_ordinal, + "timetuple": self.time_tuple, + }[name] + except KeyError: + raise AttributeError("Date has no attribute %r" % name) + + # CLASS METHODS # + + @classmethod + def today(cls, tz=None): + """Get the current date. + + :param tz: timezone or None to get the local :class:`.Date`. + :type tz: datetime.tzinfo or None + + :rtype: Date + + :raises OverflowError: if the timestamp is out of the range of values + supported by the platform C localtime() function. It’s common for + this to be restricted to years from 1970 through 2038. + """ + if tz is None: + return cls.from_clock_time(Clock().local_time(), UnixEpoch) + else: + return ( + DateTime.utc_now() + .replace(tzinfo=timezone.utc).astimezone(tz) + .date() + ) + + @classmethod + def utc_today(cls): + """Get the current date as UTC local date. + + :rtype: Date + """ + return cls.from_clock_time(Clock().utc_time(), UnixEpoch) + + @classmethod + def from_timestamp(cls, timestamp, tz=None): + """:class:`.Date` from a time stamp (seconds since unix epoch). + + :param timestamp: the unix timestamp (seconds since unix epoch). + :type timestamp: float + :param tz: timezone. Set to None to create a local :class:`.Date`. + :type tz: datetime.tzinfo or None + + :rtype: Date + + :raises OverflowError: if the timestamp is out of the range of values + supported by the platform C localtime() function. It’s common for + this to be restricted to years from 1970 through 2038. + """ + return cls.from_native(datetime.fromtimestamp(timestamp, tz)) + + @classmethod + def utc_from_timestamp(cls, timestamp): + """:class:`.Date` from a time stamp (seconds since unix epoch). + + Returns the `Date` as local date `Date` in UTC. + + :rtype: Date + """ + return cls.from_clock_time((timestamp, 0), UnixEpoch) + + @classmethod + def from_ordinal(cls, ordinal): + """ + The :class:`.Date` that corresponds to the proleptic Gregorian ordinal. + + `0001-01-01` has ordinal 1 and `9999-12-31` has ordinal 3,652,059. + Values outside of this range trigger a :exc:`ValueError`. + The corresponding instance method for the reverse date-to-ordinal + transformation is :meth:`.to_ordinal`. + The ordinal 0 has a special semantic and will return :attr:`ZeroDate`. + + :rtype: Date + + :raises ValueError: if the ordinal is outside the range [0, 3652059] + (both values included). + """ + if ordinal == 0: + return ZeroDate + if ordinal >= 736695: + year = 2018 # Project release year + month = 1 + day = int(ordinal - 736694) + elif ordinal >= 719163: + year = 1970 # Unix epoch + month = 1 + day = int(ordinal - 719162) + else: + year = 1 + month = 1 + day = int(ordinal) + if day < 1 or day > 3652059: + # Note: this requires a maximum of 22 bits for storage + # Could be transferred in 3 bytes. + raise ValueError("Ordinal out of range (1..3652059)") + if year < MIN_YEAR or year > MAX_YEAR: + raise ValueError("Year out of range (%d..%d)" % (MIN_YEAR, MAX_YEAR)) + days_in_year = DAYS_IN_YEAR[year] + while day > days_in_year: + day -= days_in_year + year += 1 + days_in_year = DAYS_IN_YEAR[year] + days_in_month = DAYS_IN_MONTH[(year, month)] + while day > days_in_month: + day -= days_in_month + month += 1 + days_in_month = DAYS_IN_MONTH[(year, month)] + year, month, day = _normalize_day(year, month, day) + return cls.__new(ordinal, year, month, day) + + @classmethod + def parse(cls, s): + """Parse a string to produce a :class:`.Date`. + + Accepted formats: + 'Y-M-D' + + :param s: the string to be parsed. + :type s: str + + :rtype: Date + + :raises ValueError: if the string could not be parsed. + """ + try: + numbers = map(int, s.split("-")) + except (ValueError, AttributeError): + raise ValueError("Date string must be in format YYYY-MM-DD") + else: + numbers = list(numbers) + if len(numbers) == 3: + return cls(*numbers) + raise ValueError("Date string must be in format YYYY-MM-DD") + + @classmethod + def from_iso_format(cls, s): + """Parse a ISO formatted Date string. + + Accepted formats: + 'YYYY-MM-DD' + + :param s: the string to be parsed. + :type s: str + + :rtype: Date + + :raises ValueError: if the string could not be parsed. + """ + m = DATE_ISO_PATTERN.match(s) + if m: + year = int(m.group(1)) + month = int(m.group(2)) + day = int(m.group(3)) + return cls(year, month, day) + raise ValueError("Date string must be in format YYYY-MM-DD") + + @classmethod + def from_native(cls, d): + """Convert from a native Python `datetime.date` value. + + :param d: the date to convert. + :type d: datetime.date + + :rtype: Date + """ + return Date.from_ordinal(d.toordinal()) + + @classmethod + def from_clock_time(cls, clock_time, epoch): + """Convert from a ClockTime relative to a given epoch. + + :param clock_time: the clock time as :class:`.ClockTime` or as tuple of + (seconds, nanoseconds) + :type clock_time: ClockTime or (float, int) + :param epoch: the epoch to which `clock_time` is relative + :type epoch: DateTime + + :rtype: Date + """ + try: + clock_time = ClockTime(*clock_time) + except (TypeError, ValueError): + raise ValueError("Clock time must be a 2-tuple of (s, ns)") + else: + ordinal = clock_time.seconds // 86400 + return Date.from_ordinal(ordinal + epoch.date().to_ordinal()) + + @classmethod + def is_leap_year(cls, year): + """Indicates whether or not `year` is a leap year. + + :param year: the year to look up + :type year: int + + :rtype: bool + + :raises ValueError: if `year` is out of range: + :attr:`MIN_YEAR` <= year <= :attr:`MAX_YEAR` + """ + if year < MIN_YEAR or year > MAX_YEAR: + raise ValueError("Year out of range (%d..%d)" % (MIN_YEAR, MAX_YEAR)) + return IS_LEAP_YEAR[year] + + @classmethod + def days_in_year(cls, year): + """Return the number of days in `year`. + + :param year: the year to look up + :type year: int + + :rtype: int + + :raises ValueError: if `year` is out of range: + :attr:`MIN_YEAR` <= year <= :attr:`MAX_YEAR` + """ + if year < MIN_YEAR or year > MAX_YEAR: + raise ValueError("Year out of range (%d..%d)" % (MIN_YEAR, MAX_YEAR)) + return DAYS_IN_YEAR[year] + + @classmethod + def days_in_month(cls, year, month): + """Return the number of days in `month` of `year`. + + :param year: the year to look up + :type year: int + :param year: the month to look up + :type year: int + + :rtype: int + + :raises ValueError: if `year` or `month` is out of range: + :attr:`MIN_YEAR` <= year <= :attr:`MAX_YEAR`; + 1 <= year <= 12 + """ + if year < MIN_YEAR or year > MAX_YEAR: + raise ValueError("Year out of range (%d..%d)" % (MIN_YEAR, MAX_YEAR)) + if month < 1 or month > 12: + raise ValueError("Month out of range (1..12)") + return DAYS_IN_MONTH[(year, month)] + + @classmethod + def __calc_ordinal(cls, year, month, day): + if day < 0: + day = cls.days_in_month(year, month) + int(day) + 1 + # The built-in date class does this faster than a + # long-hand pure Python algorithm could + return date(year, month, day).toordinal() + + # CLASS ATTRIBUTES # + + min = None + """The earliest date value possible.""" + + max = None + """The latest date value possible.""" + + resolution = None + """The minimum resolution supported.""" + + # INSTANCE ATTRIBUTES # + + __ordinal = 0 + + __year = 0 + + __month = 0 + + __day = 0 + + @property + def year(self): + """The year of the date. + + :type: int + """ + return self.__year + + @property + def month(self): + """The month of the date. + + :type: int + """ + return self.__month + + @property + def day(self): + """The day of the date. + + :type: int + """ + if self.__day == 0: + return 0 + if self.__day >= 1: + return self.__day + return self.days_in_month(self.__year, self.__month) + self.__day + 1 + + @property + def year_month_day(self): + """3-tuple of (year, month, day) describing the date. + + :rtype: (int, int, int) + """ + return self.year, self.month, self.day + + @property + def year_week_day(self): + """3-tuple of (year, week_of_year, day_of_week) describing the date. + + `day_of_week` will be 1 for Monday and 7 for Sunday. + + :rtype: (int, int, int) + """ + ordinal = self.__ordinal + year = self.__year + + def day_of_week(o): + return ((o - 1) % 7) + 1 + + def iso_week_1(y): + j4 = Date(y, 1, 4) + return j4 + Duration(days=(1 - day_of_week(j4.to_ordinal()))) + + if ordinal >= Date(year, 12, 29).to_ordinal(): + week1 = iso_week_1(year + 1) + if ordinal < week1.to_ordinal(): + week1 = iso_week_1(year) + else: + year += 1 + else: + week1 = iso_week_1(year) + if ordinal < week1.to_ordinal(): + year -= 1 + week1 = iso_week_1(year) + return (year, int((ordinal - week1.to_ordinal()) / 7 + 1), + day_of_week(ordinal)) + + @property + def year_day(self): + """2-tuple of (year, day_of_the_year) describing the date. + + This is the number of the day relative to the start of the year, + with `1 Jan` corresponding to `1`. + + :rtype: (int, int) + """ + return (self.__year, + self.toordinal() - Date(self.__year, 1, 1).toordinal() + 1) + + # OPERATIONS # + + def __hash__(self): + """""" + return hash(self.toordinal()) + + def __eq__(self, other): + """`==` comparison with :class:`.Date` or :class:`datetime.date`.""" + if isinstance(other, (Date, date)): + return self.toordinal() == other.toordinal() + return False + + def __ne__(self, other): + """`!=` comparison with :class:`.Date` or :class:`datetime.date`.""" + return not self.__eq__(other) + + def __lt__(self, other): + """`<` comparison with :class:`.Date` or :class:`datetime.date`.""" + if isinstance(other, (Date, date)): + return self.toordinal() < other.toordinal() + raise TypeError("'<' not supported between instances of 'Date' and %r" % type(other).__name__) + + def __le__(self, other): + """`<=` comparison with :class:`.Date` or :class:`datetime.date`.""" + if isinstance(other, (Date, date)): + return self.toordinal() <= other.toordinal() + raise TypeError("'<=' not supported between instances of 'Date' and %r" % type(other).__name__) + + def __ge__(self, other): + """`>=` comparison with :class:`.Date` or :class:`datetime.date`.""" + if isinstance(other, (Date, date)): + return self.toordinal() >= other.toordinal() + raise TypeError("'>=' not supported between instances of 'Date' and %r" % type(other).__name__) + + def __gt__(self, other): + """`>` comparison with :class:`.Date` or :class:`datetime.date`.""" + if isinstance(other, (Date, date)): + return self.toordinal() > other.toordinal() + raise TypeError("'>' not supported between instances of 'Date' and %r" % type(other).__name__) + + def __add__(self, other): + """Add a :class:`.Duration`. + + :rtype: Date + + :raises ValueError: if the added duration has a time component. + """ + def add_months(d, months): + years, months = symmetric_divmod(months, 12) + year = d.__year + years + month = d.__month + months + while month > 12: + year += 1 + month -= 12 + while month < 1: + year -= 1 + month += 12 + d.__year = year + d.__month = month + + def add_days(d, days): + assert 1 <= d.__day <= 28 or -28 <= d.__day <= -1 + if d.__day >= 1: + new_days = d.__day + days + if 1 <= new_days <= 27: + d.__day = new_days + return + d0 = Date.from_ordinal(d.__ordinal + days) + d.__year, d.__month, d.__day = d0.__year, d0.__month, d0.__day + + if isinstance(other, Duration): + 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() + # Add days before months as the former sometimes + # requires the current ordinal to be correct. + if other.days: + add_days(new_date, other.days) + if other.months: + add_months(new_date, other.months) + new_date.__ordinal = self.__calc_ordinal(new_date.year, new_date.month, new_date.day) + return new_date + return NotImplemented + + def __sub__(self, other): + """Subtract a :class:`.Date` or :class:`.Duration`. + + :returns: If a :class:`.Date` is subtracted, the time between the two + dates is returned as :class:`.Duration`. If a :class:`.Duration` is + subtracted, a new :class:`.Date` is returned. + :rtype: Date or Duration + + :raises ValueError: if the added duration has a time component. + """ + if isinstance(other, (Date, date)): + return Duration(days=(self.toordinal() - other.toordinal())) + try: + return self.__add__(-other) + except TypeError: + return NotImplemented + + def __copy__(self): + return self.__new(self.__ordinal, self.__year, self.__month, self.__day) + + def __deepcopy__(self, *args, **kwargs): + return self.__copy__() + + # INSTANCE METHODS # + + def replace(self, **kwargs): + """Return a :class:`.Date` with one or more components replaced. + + :Keyword Arguments: + * **year** (`int`): overwrite the year - + default: `self.year` + * **month** (`int`): overwrite the month - + default: `self.month` + * **day** (`int`): overwrite the day - + default: `self.day` + """ + return Date(kwargs.get("year", self.__year), + kwargs.get("month", self.__month), + kwargs.get("day", self.__day)) + + def time_tuple(self): + """Convert the date to :class:`time.struct_time`. + + :rtype: time.struct_time + """ + _, _, day_of_week = self.year_week_day + _, day_of_year = self.year_day + return struct_time((self.year, self.month, self.day, 0, 0, 0, day_of_week - 1, day_of_year, -1)) + + def to_ordinal(self): + """The date's proleptic Gregorian ordinal. + + The corresponding class method for the reverse ordinal-to-date + transformation is :meth:`.Date.from_ordinal`. + + :rtype: int + """ + return self.__ordinal + + def to_clock_time(self, epoch): + """Convert the date to :class:`ClockTime` relative to `epoch`. + + :param epoch: the epoch to which the date is relative + :type epoch: Date + + :rtype: ClockTime + """ + try: + return ClockTime(86400 * (self.to_ordinal() - epoch.to_ordinal())) + except AttributeError: + raise TypeError("Epoch has no ordinal value") + + def to_native(self): + """Convert to a native Python :class:`datetime.date` value. + + :rtype: datetime.date + """ + return date.fromordinal(self.to_ordinal()) + + def weekday(self): + """The day of the week where Monday is 0 and Sunday is 6. + + :rtype: int + """ + return self.year_week_day[2] - 1 + + def iso_weekday(self): + """The day of the week where Monday is 1 and Sunday is 7. + + :rtype: int + """ + return self.year_week_day[2] + + def iso_calendar(self): + """Alias for :attr:`.year_week_day`""" + return self.year_week_day + + def iso_format(self): + """Return the :class:`.Date` as ISO formatted string. + + :rtype: str + """ + if self.__ordinal == 0: + return "0000-00-00" + return "%04d-%02d-%02d" % self.year_month_day + + def __repr__(self): + """""" + if self.__ordinal == 0: + return "neo4j.time.ZeroDate" + return "neo4j.time.Date(%r, %r, %r)" % self.year_month_day + + def __str__(self): + """""" + return self.iso_format() + + def __format__(self, format_spec): + """""" + raise NotImplementedError() + + +Date.min = Date.from_ordinal(1) +Date.max = Date.from_ordinal(3652059) +Date.resolution = Duration(days=1) + +#: A :class:`neo4j.time.Date` instance set to `0000-00-00`. +#: This has an ordinal value of `0`. +ZeroDate = object.__new__(Date) + + +class Time(metaclass=TimeType): + """Time of day. + + The :class:`.Time` class is a nanosecond-precision drop-in replacement for + the standard library :class:`datetime.time` class. + + A high degree of API compatibility with the standard library classes is + provided. + + :class:`neo4j.time.Time` objects introduce the concept of ``ticks``. + This is simply a count of the number of nanoseconds since midnight, + in many ways analogous to the :class:`neo4j.time.Date` ordinal. + `ticks` values are integers, with a minimum value of `0` and a maximum + of `86_399_999_999_999`. + + Local times are represented by :class:`.Time` with no ``tzinfo``. + + :param hour: the hour of the time. Must be in range 0 <= hour < 24. + :type hour: int + :param minute: the minute of the time. Must be in range 0 <= minute < 60. + :type minute: int + :param second: the second of the time. Must be in range 0 <= second < 60. + :type second: int + :param nanosecond: the nanosecond of the time. + Must be in range 0 <= nanosecond < 999999999. + :type nanosecond: int + :param tzinfo: timezone or None to get a local :class:`.Time`. + :type tzinfo: datetime.tzinfo or None + + :raises ValueError: if one of the parameters is out of range. + """ + + # CONSTRUCTOR # + + 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, nanosecond, tzinfo): + instance = object.__new__(cls) + instance.__ticks = int(ticks) + instance.__hour = int(hour) + instance.__minute = int(minute) + instance.__second = int(second) + instance.__nanosecond = int(nanosecond) + instance.__tzinfo = tzinfo + return instance + + def __getattr__(self, name): + """Map standard library attribute names to local attribute names, + for compatibility. + """ + try: + return { + "isoformat": self.iso_format, + "utcoffset": self.utc_offset, + }[name] + except KeyError: + raise AttributeError("Date has no attribute %r" % name) + + # CLASS METHODS # + + @classmethod + def now(cls, tz=None): + """Get the current time. + + :param tz: optional timezone + :type tz: datetime.tzinfo + :rtype: Time + + :raises OverflowError: if the timestamp is out of the range of values + supported by the platform C localtime() function. It’s common for + this to be restricted to years from 1970 through 2038. + """ + if tz is None: + return cls.from_clock_time(Clock().local_time(), UnixEpoch) + else: + return ( + DateTime.utc_now() + .replace(tzinfo=timezone.utc).astimezone(tz) + .timetz() + ) + + @classmethod + def utc_now(cls): + """Get the current time as UTC local time. + + :rtype: Time + """ + return cls.from_clock_time(Clock().utc_time(), UnixEpoch) + + @classmethod + def from_iso_format(cls, s): + """Parse a ISO formatted time string. + + Accepted formats: + Local times: + 'hh' + 'hh:mm' + 'hh:mm:ss' + 'hh:mm:ss.ssss...' + Times with timezones (UTC offset): + '+hh:mm' + '+hh:mm:ss' + '+hh:mm:ss.ssss....' + '-hh:mm' + '-hh:mm:ss' + '-hh:mm:ss.ssss....' + + Where the UTC offset will only respect hours and minutes. + Seconds and sub-seconds are ignored. + + :param s: String to parse + :type s: str + + :rtype: Time + + :raises ValueError: if the string does not match the required format. + """ + from pytz import FixedOffset + m = TIME_ISO_PATTERN.match(s) + if m: + hour = int(m.group(1)) + minute = int(m.group(3) or 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, nanosecond) + else: + offset_multiplier = 1 if m.group(9) == "+" else -1 + offset_hour = int(m.group(10)) + offset_minute = int(m.group(11)) + # pytz only supports offsets of minute resolution + # 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, nanosecond, + tzinfo=FixedOffset(offset_multiplier * offset)) + raise ValueError("Time string is not in ISO format") + + @classmethod + def from_ticks(cls, ticks, tz=None): + """Create a time from ticks (nanoseconds since midnight). + + :param ticks: nanoseconds since midnight + :type ticks: int + :param tz: optional timezone + :type tz: datetime.tzinfo + + :rtype: Time + + :raises ValueError: if ticks is out of bounds + (0 <= ticks < 86400000000000) + """ + 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 :class:`datetime.time` value. + + :param t: time to convert from + :type t: datetime.time + + :rtype: Time + """ + 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 :class:`.ClockTime` relative to a given epoch. + + This method, in contrast to most others of this package, assumes days of + exactly 24 hours. + + :param clock_time: the clock time as :class:`.ClockTime` or as tuple of + (seconds, nanoseconds) + :type clock_time: ClockTime or (float, int) + :param epoch: the epoch to which `clock_time` is relative + :type epoch: DateTime + + :rtype: Time + """ + clock_time = ClockTime(*clock_time) + ts = clock_time.seconds % 86400 + nanoseconds = int(NANO_SECONDS * ts + clock_time.nanoseconds) + ticks = (epoch.time().ticks + nanoseconds) % (86400 * NANO_SECONDS) + return Time.from_ticks(ticks) + + @classmethod + def __normalize_hour(cls, hour): + hour = int(hour) + if 0 <= hour < 24: + 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, 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, second + raise ValueError("Second out of range (0..59)") + + @classmethod + def __normalize_nanosecond(cls, hour, minute, second, nanosecond): + hour, minute, second = cls.__normalize_second(hour, minute, second) + if 0 <= nanosecond < NANO_SECONDS: + return hour, minute, second, nanosecond + raise ValueError("Nanosecond out of range (0..%s)" % (NANO_SECONDS - 1)) + + # CLASS ATTRIBUTES # + + min = None + """The earliest time value possible.""" + + max = None + """The latest time value possible.""" + + resolution = None + """The minimum resolution supported.""" + + # INSTANCE ATTRIBUTES # + + __ticks = 0 + + __hour = 0 + + __minute = 0 + + __second = 0 + + __nanosecond = 0 + + __tzinfo = None + + @property + def ticks(self): + """The total number of nanoseconds since midnight. + + :type: int + """ + return self.__ticks + + @property + def hour(self): + """The hours of the time. + + :type: int + """ + return self.__hour + + @property + def minute(self): + """The minutes of the time. + + :type: int + """ + return self.__minute + + @property + def second(self): + """The seconds of the time. + + :type: int + """ + return self.__second + + @property + def nanosecond(self): + """The nanoseconds of the time. + + :type: int + """ + return self.__nanosecond + + @property + def hour_minute_second_nanosecond(self): + """The time as a tuple of (hour, minute, second, nanosecond). + + :type: (int, int, int, int)""" + return self.__hour, self.__minute, self.__second, self.__nanosecond + + @property + def tzinfo(self): + """The timezone of this time. + + :type: datetime.tzinfo or None""" + return self.__tzinfo + + # OPERATIONS # + + def _get_both_normalized_ticks(self, other, strict=True): + if (isinstance(other, (time, Time)) + and ((self.utc_offset() is None) + ^ (other.utcoffset() is None))): + if strict: + raise TypeError("can't compare offset-naive and offset-aware " + "times") + else: + return None, None + if isinstance(other, Time): + other_ticks = other.__ticks + elif isinstance(other, time): + other_ticks = int(3600000000000 * other.hour + + 60000000000 * other.minute + + NANO_SECONDS * other.second + + 1000 * other.microsecond) + else: + return None, None + utc_offset = other.utcoffset() + if utc_offset is not None: + other_ticks -= utc_offset.total_seconds() * NANO_SECONDS + self_ticks = self.__ticks + utc_offset = self.utc_offset() + if utc_offset is not None: + self_ticks -= utc_offset.total_seconds() * NANO_SECONDS + return self_ticks, other_ticks + + def __hash__(self): + """""" + if self.__nanosecond % 1000 == 0: + return hash(self.to_native()) + self_ticks = self.__ticks + if self.utc_offset() is not None: + self_ticks -= self.utc_offset().total_seconds() * NANO_SECONDS + return hash(self_ticks) + + def __eq__(self, other): + """`==` comparison with :class:`.Time` or :class:`datetime.time`.""" + self_ticks, other_ticks = self._get_both_normalized_ticks(other, + strict=False) + if self_ticks is None: + return False + return self_ticks == other_ticks + + def __ne__(self, other): + """`!=` comparison with :class:`.Time` or :class:`datetime.time`.""" + return not self.__eq__(other) + + def __lt__(self, other): + """`<` comparison with :class:`.Time` or :class:`datetime.time`.""" + self_ticks, other_ticks = self._get_both_normalized_ticks(other) + if self_ticks is None: + return NotImplemented + return self_ticks < other_ticks + + def __le__(self, other): + """`<=` comparison with :class:`.Time` or :class:`datetime.time`.""" + self_ticks, other_ticks = self._get_both_normalized_ticks(other) + if self_ticks is None: + return NotImplemented + return self_ticks <= other_ticks + + def __ge__(self, other): + """`>=` comparison with :class:`.Time` or :class:`datetime.time`.""" + self_ticks, other_ticks = self._get_both_normalized_ticks(other) + if self_ticks is None: + return NotImplemented + return self_ticks >= other_ticks + + def __gt__(self, other): + """`>` comparison with :class:`.Time` or :class:`datetime.time`.""" + self_ticks, other_ticks = self._get_both_normalized_ticks(other) + if self_ticks is None: + return NotImplemented + return self_ticks > other_ticks + + def __copy__(self): + return self.__new(self.__ticks, self.__hour, self.__minute, + self.__second, self.__nanosecond, self.__tzinfo) + + def __deepcopy__(self, *args, **kwargs): + return self.__copy__() + + # INSTANCE METHODS # + + def replace(self, **kwargs): + """Return a :class:`.Time` with one or more components replaced. + + :Keyword Arguments: + * **hour** (`int`): overwrite the hour - + default: `self.hour` + * **minute** (`int`): overwrite the minute - + default: `self.minute` + * **second** (`int`): overwrite the second - + default: `int(self.second)` + * **nanosecond** (`int`): overwrite the nanosecond - + default: `self.nanosecond` + * **tzinfo** (`datetime.tzinfo` or `None`): overwrite the timezone - + default: `self.tzinfo` + + :rtype: Time + """ + 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, dt=None): + if self.tzinfo is None: + return None + try: + value = self.tzinfo.utcoffset(dt) + except TypeError: + # For timezone implementations not compatible with the custom + # datetime implementations, we can't do better than this. + value = self.tzinfo.utcoffset(dt.to_native()) + if value is None: + return None + if isinstance(value, timedelta): + s = value.total_seconds() + if not (-86400 < s < 86400): + raise ValueError("utcoffset must be less than a day") + if s % 60 != 0 or value.microseconds != 0: + raise ValueError("utcoffset must be a whole number of minutes") + return value + raise TypeError("utcoffset must be a timedelta") + + def utc_offset(self): + """Return the UTC offset of this time. + + :return: None if this is a local time (:attr:`.tzinfo` is None), else + returns `self.tzinfo.utcoffset(self)`. + :rtype: datetime.timedelta + + :raises ValueError: if `self.tzinfo.utcoffset(self)` is not None and a + :class:`timedelta` with a magnitude greater equal 1 day or that is + not a whole number of minutes. + :raises TypeError: if `self.tzinfo.utcoffset(self)` does return anything but + None or a :class:`datetime.timedelta`. + """ + return self._utc_offset() + + def dst(self): + """Get the daylight saving time adjustment (DST). + + :return: None if this is a local time (:attr:`.tzinfo` is None), else + returns `self.tzinfo.dst(self)`. + :rtype: datetime.timedelta + + :raises ValueError: if `self.tzinfo.dst(self)` is not None and a + :class:`timedelta` with a magnitude greater equal 1 day or that is + not a whole number of minutes. + :raises TypeError: if `self.tzinfo.dst(self)` does return anything but + None or a :class:`datetime.timedelta`. + """ + if self.tzinfo is None: + return None + try: + value = self.tzinfo.dst(self) + except TypeError: + # For timezone implementations not compatible with the custom + # datetime implementations, we can't do better than this. + value = self.tzinfo.dst(self.to_native()) + if value is None: + return None + if isinstance(value, timedelta): + if value.days != 0: + raise ValueError("dst must be less than a day") + if value.seconds % 60 != 0 or value.microseconds != 0: + raise ValueError("dst must be a whole number of minutes") + return value + raise TypeError("dst must be a timedelta") + + def tzname(self): + """Get the name of the :class:`.Time`'s timezone. + + :returns: None if the time is local (i.e., has no timezone), else return + `self.tzinfo.tzname(self)` + + :rtype: str or None + """ + if self.tzinfo is None: + return None + try: + return self.tzinfo.tzname(self) + except TypeError: + # For timezone implementations not compatible with the custom + # datetime implementations, we can't do better than this. + return self.tzinfo.tzname(self.to_native()) + + def to_clock_time(self): + """Convert to :class:`.ClockTime`. + + :rtype: ClockTime + """ + seconds, nanoseconds = divmod(self.ticks, NANO_SECONDS) + return ClockTime(seconds, nanoseconds) + + def to_native(self): + """Convert to a native Python `datetime.time` value. + + This conversion is lossy as the native time implementation only supports + a resolution of microseconds instead of nanoseconds. + + :rtype: datetime.time + """ + h, m, s, ns = self.hour_minute_second_nanosecond + µs = round_half_to_even(ns / 1000) + tz = self.tzinfo + return time(h, m, s, µs, tz) + + def iso_format(self): + """Return the :class:`.Time` as ISO formatted string. + + :rtype: str + """ + s = "%02d:%02d:%02d.%09d" % self.hour_minute_second_nanosecond + offset = self.utc_offset() + if offset is not None: + s += "%+03d:%02d" % divmod(offset.total_seconds() // 60, 60) + return s + + def __repr__(self): + """""" + if self.tzinfo is None: + return "neo4j.time.Time(%r, %r, %r, %r)" % \ + self.hour_minute_second_nanosecond + else: + return "neo4j.time.Time(%r, %r, %r, %r, tzinfo=%r)" % \ + (self.hour_minute_second_nanosecond + (self.tzinfo,)) + + def __str__(self): + """""" + return self.iso_format() + + def __format__(self, format_spec): + """""" + raise NotImplementedError() + + +Time.min = Time(hour=0, minute=0, second=0, nanosecond=0) +Time.max = Time(hour=23, minute=59, second=59, nanosecond=999999999) +Time.resolution = Duration(nanoseconds=1) + +#: A :class:`.Time` instance set to `00:00:00`. +#: This has a :attr:`.ticks` value of `0`. +Midnight = Time.min + +#: A :class:`.Time` instance set to `12:00:00`. +#: This has a :attr:`.ticks` value of `43200000000000`. +Midday = Time(hour=12) + + +@total_ordering +class DateTime(metaclass=DateTimeType): + """A point in time represented as a date and a time. + + The :class:`.DateTime` class is a nanosecond-precision drop-in replacement + for the standard library :class:`datetime.datetime` class. + + As such, it contains both :class:`.Date` and :class:`.Time` information and + draws functionality from those individual classes. + + A :class:`.DateTime` object is fully compatible with the Python time zone + library `pytz `_. Functions such as + `normalize` and `localize` can be used in the same way as they are with the + standard library classes. + + Regular construction of a :class:`.DateTime` object requires at + least the `year`, `month` and `day` arguments to be supplied. The + optional `hour`, `minute` and `second` arguments default to zero and + `tzinfo` defaults to :const:`None`. + + `year`, `month`, and `day` are passed to the constructor of :class:`.Date`. + `hour`, `minute`, `second`, `nanosecond`, and `tzinfo` are passed to the + constructor of :class:`.Time`. See their documentation for more details. + + >>> dt = DateTime(2018, 4, 30, 12, 34, 56, 789123456); dt + neo4j.time.DateTime(2018, 4, 30, 12, 34, 56, 789123456) + >>> dt.second + 56.789123456 + """ + + # CONSTRUCTOR # + + 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, + for compatibility. + """ + try: + return { + "astimezone": self.as_timezone, + "isocalendar": self.iso_calendar, + "isoformat": self.iso_format, + "isoweekday": self.iso_weekday, + "strftime": self.__format__, + "toordinal": self.to_ordinal, + "timetuple": self.time_tuple, + "utcoffset": self.utc_offset, + "utctimetuple": self.utc_time_tuple, + }[name] + except KeyError: + raise AttributeError("DateTime has no attribute %r" % name) + + # CLASS METHODS # + + @classmethod + def now(cls, tz=None): + """Get the current date and time. + + :param tz: timezone. Set to None to create a local :class:`.DateTime`. + :type tz: datetime.tzinfo` or None + + :rtype: DateTime + + :raises OverflowError: if the timestamp is out of the range of values + supported by the platform C localtime() function. It’s common for + this to be restricted to years from 1970 through 2038. + """ + if tz is None: + return cls.from_clock_time(Clock().local_time(), UnixEpoch) + else: + try: + return tz.fromutc(cls.from_clock_time( + Clock().utc_time(), UnixEpoch + ).replace(tzinfo=tz)) + except TypeError: + # For timezone implementations not compatible with the custom + # datetime implementations, we can't do better than this. + utc_now = cls.from_clock_time( + Clock().utc_time(), UnixEpoch + ) + utc_now_native = utc_now.to_native() + now_native = tz.fromutc(utc_now_native) + now = cls.from_native(now_native) + return now.replace( + nanosecond=(now.nanosecond + + utc_now.nanosecond + - utc_now_native.microsecond * 1000) + ) + + @classmethod + def utc_now(cls): + """Get the current date and time in UTC + + :rtype: DateTime + """ + return cls.from_clock_time(Clock().utc_time(), UnixEpoch) + + @classmethod + def from_iso_format(cls, s): + """Parse a ISO formatted date with time string. + + :param s: String to parse + :type s: str + + :rtype: Time + + :raises ValueError: if the string does not match the ISO format. + """ + try: + return cls.combine(Date.from_iso_format(s[0:10]), + Time.from_iso_format(s[11:])) + except ValueError: + raise ValueError("DateTime string is not in ISO format") + + @classmethod + def from_timestamp(cls, timestamp, tz=None): + """:class:`.DateTime` from a time stamp (seconds since unix epoch). + + :param timestamp: the unix timestamp (seconds since unix epoch). + :type timestamp: float + :param tz: timezone. Set to None to create a local :class:`.DateTime`. + :type tz: datetime.tzinfo or None + + :rtype: DateTime + + :raises OverflowError: if the timestamp is out of the range of values + supported by the platform C localtime() function. It’s common for + this to be restricted to years from 1970 through 2038. + """ + if tz is None: + return cls.from_clock_time( + ClockTime(timestamp) + Clock().local_offset(), UnixEpoch + ) + else: + return ( + cls.utc_from_timestamp(timestamp) + .replace(tzinfo=timezone.utc).astimezone(tz) + ) + + @classmethod + def utc_from_timestamp(cls, timestamp): + """:class:`.DateTime` from a time stamp (seconds since unix epoch). + + Returns the `DateTime` as local date `DateTime` in UTC. + + :rtype: DateTime + """ + return cls.from_clock_time((timestamp, 0), UnixEpoch) + + @classmethod + def from_ordinal(cls, ordinal): + """:class:`.DateTime` from an ordinal. + + For more info about ordinals see :meth:`.Date.from_ordinal`. + + :rtype: DateTime + """ + return cls.combine(Date.from_ordinal(ordinal), Midnight) + + @classmethod + def combine(cls, date, time): + """Combine a :class:`.Date` and a :class:`.Time` to a :class:`DateTime`. + + :param date: the date + :type date: Date + :param time: the time + :type time: Time + + :rtype: DateTime + + :raises AssertionError: if the parameter types don't match. + """ + assert isinstance(date, Date) + assert isinstance(time, Time) + instance = object.__new__(cls) + instance.__date = date + instance.__time = time + return instance + + @classmethod + def parse(cls, date_string, format): + raise NotImplementedError() + + @classmethod + def from_native(cls, dt): + """Convert from a native Python :class:`datetime.datetime` value. + + :param dt: the datetime to convert + :type dt: datetime.datetime + + :rtype: DateTime + """ + return cls.combine(Date.from_native(dt.date()), Time.from_native(dt.timetz())) + + @classmethod + def from_clock_time(cls, clock_time, epoch): + """Convert from a :class:`ClockTime` relative to a given epoch. + + :param clock_time: the clock time as :class:`.ClockTime` or as tuple of + (seconds, nanoseconds) + :type clock_time: ClockTime or (float, int) + :param epoch: the epoch to which `clock_time` is relative + :type epoch: DateTime + + :rtype: DateTime + + :raises ValueError: if `clock_time` is invalid. + """ + try: + seconds, nanoseconds = ClockTime(*clock_time) + except (TypeError, ValueError): + raise ValueError("Clock time must be a 2-tuple of (s, ns)") + else: + ordinal, seconds = divmod(seconds, 86400) + ticks = epoch.time().ticks + seconds * NANO_SECONDS + nanoseconds + days, ticks = divmod(ticks, 86400 * NANO_SECONDS) + ordinal += days + date_ = Date.from_ordinal(ordinal + epoch.date().to_ordinal()) + time_ = Time.from_ticks(ticks) + return cls.combine(date_, time_) + + # CLASS ATTRIBUTES # + + min = None + """The earliest date time value possible.""" + + max = None + """The latest date time value possible.""" + + resolution = None + """The minimum resolution supported.""" + + # INSTANCE ATTRIBUTES # + + @property + def year(self): + """The year of the :class:`.DateTime`. + + See :attr:`.Date.year`. + """ + return self.__date.year + + @property + def month(self): + """The year of the :class:`.DateTime`. + + See :attr:`.Date.year`.""" + return self.__date.month + + @property + def day(self): + """The day of the :class:`.DateTime`'s date. + + See :attr:`.Date.day`.""" + return self.__date.day + + @property + def year_month_day(self): + """The year_month_day of the :class:`.DateTime`'s date. + + See :attr:`.Date.year_month_day`.""" + return self.__date.year_month_day + + @property + def year_week_day(self): + """The year_week_day of the :class:`.DateTime`'s date. + + See :attr:`.Date.year_week_day`.""" + return self.__date.year_week_day + + @property + def year_day(self): + """The year_day of the :class:`.DateTime`'s date. + + See :attr:`.Date.year_day`.""" + return self.__date.year_day + + @property + def hour(self): + """The hour of the :class:`.DateTime`'s time. + + See :attr:`.Time.hour`.""" + return self.__time.hour + + @property + def minute(self): + """The minute of the :class:`.DateTime`'s time. + + See :attr:`.Time.minute`.""" + return self.__time.minute + + @property + def second(self): + """The second of the :class:`.DateTime`'s time. + + See :attr:`.Time.second`.""" + return self.__time.second + + @property + def nanosecond(self): + """The nanosecond of the :class:`.DateTime`'s time. + + See :attr:`.Time.nanosecond`.""" + return self.__time.nanosecond + + @property + def tzinfo(self): + """The tzinfo of the :class:`.DateTime`'s time. + + See :attr:`.Time.tzinfo`.""" + return self.__time.tzinfo + + @property + def hour_minute_second_nanosecond(self): + """The hour_minute_second_nanosecond of the :class:`.DateTime`'s time. + + See :attr:`.Time.hour_minute_second_nanosecond`.""" + return self.__time.hour_minute_second_nanosecond + + # OPERATIONS # + + def _get_both_normalized(self, other, strict=True): + if (isinstance(other, (datetime, DateTime)) + and ((self.utc_offset() is None) + ^ (other.utcoffset() is None))): + if strict: + raise TypeError("can't compare offset-naive and offset-aware " + "datetimes") + else: + return None, None + self_norm = self + utc_offset = self.utc_offset() + if utc_offset is not None: + self_norm -= utc_offset + self_norm = self_norm.replace(tzinfo=None) + other_norm = other + if isinstance(other, (datetime, DateTime)): + utc_offset = other.utcoffset() + if utc_offset is not None: + other_norm -= utc_offset + other_norm = other_norm.replace(tzinfo=None) + else: + return None, None + return self_norm, other_norm + + def __hash__(self): + """""" + if self.nanosecond % 1000 == 0: + return hash(self.to_native()) + self_norm = self + utc_offset = self.utc_offset() + if utc_offset is not None: + self_norm -= utc_offset + return hash(self_norm.date()) ^ hash(self_norm.time()) + + def __eq__(self, other): + """ + `==` comparison with :class:`.DateTime` or :class:`datetime.datetime`. + """ + if not isinstance(other, (datetime, DateTime)): + return NotImplemented + if self.utc_offset() == other.utcoffset(): + return self.date() == other.date() and self.time() == other.time() + self_norm, other_norm = self._get_both_normalized(other, strict=False) + if self_norm is None: + return False + return self_norm == other_norm + + def __ne__(self, other): + """ + `!=` comparison with :class:`.DateTime` or :class:`datetime.datetime`. + """ + return not self.__eq__(other) + + def __lt__(self, other): + """ + `<` comparison with :class:`.DateTime` or :class:`datetime.datetime`. + """ + if not isinstance(other, (datetime, DateTime)): + return NotImplemented + if self.utc_offset() == other.utcoffset(): + if self.date() == other.date(): + return self.time() < other.time() + return self.date() < other.date() + self_norm, other_norm = self._get_both_normalized(other) + return (self_norm.date() < other_norm.date() + or self_norm.time() < other_norm.time()) + + def __le__(self, other): + """ + `<=` comparison with :class:`.DateTime` or :class:`datetime.datetime`. + """ + if not isinstance(other, (datetime, DateTime)): + return NotImplemented + if self.utc_offset() == other.utcoffset(): + if self.date() == other.date(): + return self.time() <= other.time() + return self.date() <= other.date() + self_norm, other_norm = self._get_both_normalized(other) + return self_norm <= other_norm + + def __ge__(self, other): + """ + `>=` comparison with :class:`.DateTime` or :class:`datetime.datetime`. + """ + if not isinstance(other, (datetime, DateTime)): + return NotImplemented + if self.utc_offset() == other.utcoffset(): + if self.date() == other.date(): + return self.time() >= other.time() + return self.date() >= other.date() + self_norm, other_norm = self._get_both_normalized(other) + return self_norm >= other_norm + + def __gt__(self, other): + """ + `>` comparison with :class:`.DateTime` or :class:`datetime.datetime`. + """ + if not isinstance(other, (datetime, DateTime)): + return NotImplemented + if self.utc_offset() == other.utcoffset(): + if self.date() == other.date(): + return self.time() > other.time() + return self.date() > other.date() + self_norm, other_norm = self._get_both_normalized(other) + return (self_norm.date() > other_norm.date() + or self_norm.time() > other_norm.time()) + + def __add__(self, other): + """Add a :class:`datetime.timedelta`. + + :rtype: DateTime + """ + if isinstance(other, timedelta): + 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(round_half_to_even( + seconds * NANO_SECONDS + t.nanoseconds + )) + return self.combine(date_, time_).replace(tzinfo=self.tzinfo) + if isinstance(other, Duration): + t = (self.to_clock_time() + + ClockTime(other.seconds, other.nanoseconds)) + days, seconds = symmetric_divmod(t.seconds, 86400) + date_ = self.date() + Duration(months=other.months, + days=days + other.days) + time_ = Time.from_ticks(seconds * NANO_SECONDS + t.nanoseconds) + return self.combine(date_, time_).replace(tzinfo=self.tzinfo) + return NotImplemented + + def __sub__(self, other): + """Subtract a datetime or a timedelta. + + Supported :class:`.DateTime` (returns :class:`.Duration`), + :class:`datetime.datetime` (returns :class:`datetime.timedelta`), and + :class:`datetime.timedelta` (returns :class:`.DateTime`). + + :rtype: Duration or datetime.timedelta or DateTime + """ + if isinstance(other, DateTime): + self_month_ordinal = 12 * (self.year - 1) + self.month + other_month_ordinal = 12 * (other.year - 1) + other.month + months = self_month_ordinal - other_month_ordinal + days = self.day - other.day + t = self.time().to_clock_time() - other.time().to_clock_time() + return Duration(months=months, days=days, seconds=t.seconds, + nanoseconds=t.nanoseconds) + if isinstance(other, datetime): + days = self.to_ordinal() - other.toordinal() + t = (self.time().to_clock_time() + - ClockTime( + 3600 * other.hour + 60 * other.minute + other.second, + other.microsecond * 1000 + )) + return timedelta(days=days, seconds=t.seconds, + microseconds=(t.nanoseconds // 1000)) + if isinstance(other, Duration): + return self.__add__(-other) + if isinstance(other, timedelta): + return self.__add__(-other) + return NotImplemented + + def __copy__(self): + return self.combine(self.__date, self.__time) + + def __deepcopy__(self, *args, **kwargs): + return self.__copy__() + + # INSTANCE METHODS # + + def date(self): + """The date + + :rtype: Date + """ + return self.__date + + def time(self): + """The time without timezone info + + :rtype: Time + """ + return self.__time.replace(tzinfo=None) + + def timetz(self): + """The time with timezone info + + :rtype: Time + """ + return self.__time + + def replace(self, **kwargs): + """Return a :class:`.DateTime` with one or more components replaced. + + See :meth:`.Date.replace` and :meth:`.Time.replace` for available + arguments. + + :rtype: DateTime + """ + date_ = self.__date.replace(**kwargs) + time_ = self.__time.replace(**kwargs) + return self.combine(date_, time_) + + def as_timezone(self, tz): + """Convert this :class:`.DateTime` to another timezone. + + :param tz: the new timezone + :type tz: datetime.tzinfo or None + + :return: the same object if `tz` is None. Else, a new :class:`.DateTime` + that's the same point in time but in a different timezone. + :rtype: DateTime + """ + if self.tzinfo is None: + return self + utc = (self - self.utc_offset()).replace(tzinfo=tz) + try: + return tz.fromutc(utc) + except TypeError: + # For timezone implementations not compatible with the custom + # datetime implementations, we can't do better than this. + native_utc = utc.to_native() + native_res = tz.fromutc(native_utc) + res = self.from_native(native_res) + return res.replace( + nanosecond=(native_res.microsecond * 1000 + + self.nanosecond % 1000) + ) + + def utc_offset(self): + """Get the date times utc offset. + + See :meth:`.Time.utc_offset`. + """ + + return self.__time._utc_offset(self) + + def dst(self): + """Get the daylight saving time adjustment (DST). + + See :meth:`.Time.dst`. + """ + return self.__time.dst() + + def tzname(self): + """Get the timezone name. + + See :meth:`.Time.tzname`. + """ + return self.__time.tzname() + + def time_tuple(self): + raise NotImplementedError() + + def utc_time_tuple(self): + raise NotImplementedError() + + def to_ordinal(self): + """Get the ordinal of the :class:`.DateTime`'s date. + + See :meth:`.Date.to_ordinal` + """ + return self.__date.to_ordinal() + + def to_clock_time(self): + """Convert to :class:`.ClockTime`. + + :rtype: ClockTime + """ + total_seconds = 0 + for year in range(1, self.year): + total_seconds += 86400 * DAYS_IN_YEAR[year] + for month in range(1, self.month): + total_seconds += 86400 * Date.days_in_month(self.year, month) + total_seconds += 86400 * (self.day - 1) + seconds, nanoseconds = divmod(self.__time.ticks, NANO_SECONDS) + return ClockTime(total_seconds + seconds, nanoseconds) + + def to_native(self): + """Convert to a native Python :class:`datetime.datetime` value. + + This conversion is lossy as the native time implementation only supports + a resolution of microseconds instead of nanoseconds. + + :rtype: datetime.datetime + """ + y, mo, d = self.year_month_day + h, m, s, ns = self.hour_minute_second_nanosecond + ms = int(ns / 1000) + tz = self.tzinfo + return datetime(y, mo, d, h, m, s, ms, tz) + + def weekday(self): + """Get the weekday. + + See :meth:`.Date.weekday` + """ + return self.__date.weekday() + + def iso_weekday(self): + """Get the ISO weekday. + + See :meth:`.Date.iso_weekday` + """ + return self.__date.iso_weekday() + + def iso_calendar(self): + """Get date as ISO tuple. + + See :meth:`.Date.iso_calendar` + """ + return self.__date.iso_calendar() + + def iso_format(self, sep="T"): + """Return the :class:`.DateTime` as ISO formatted string. + + This method joins `self.date().iso_format()` (see + :meth:`.Date.iso_format`) and `self.timetz().iso_format()` (see + :meth:`.Time.iso_format`) with `sep` in between. + + :param sep: the separator between the formatted date and time. + :type sep: str + + :rtype: str + """ + s = "%s%s%s" % (self.date().iso_format(), sep, + self.timetz().iso_format()) + time_tz = self.timetz() + offset = time_tz.utc_offset() + if offset is not None: + # the time component will have taken care of formatting the offset + return s + offset = self.utc_offset() + if offset is not None: + s += "%+03d:%02d" % divmod(offset.total_seconds() // 60, 60) + return s + + def __repr__(self): + """""" + if self.tzinfo is None: + fields = (*self.year_month_day, + *self.hour_minute_second_nanosecond) + return "neo4j.time.DateTime(%r, %r, %r, %r, %r, %r, %r)" % fields + else: + fields = (*self.year_month_day, + *self.hour_minute_second_nanosecond, self.tzinfo) + return ("neo4j.time.DateTime(%r, %r, %r, %r, %r, %r, %r, tzinfo=%r)" + % fields) + + def __str__(self): + """""" + return self.iso_format() + + def __format__(self, format_spec): + """""" + raise NotImplementedError() + + +DateTime.min = DateTime.combine(Date.min, Time.min) +DateTime.max = DateTime.combine(Date.max, Time.max) +DateTime.resolution = Time.resolution + +#: A :class:`.DateTime` instance set to `0000-00-00T00:00:00`. +#: This has a :class:`.Date` component equal to :attr:`ZeroDate` and a +Never = DateTime.combine(ZeroDate, Midnight) + +#: A :class:`.DateTime` instance set to `1970-01-01T00:00:00`. +UnixEpoch = DateTime(1970, 1, 1, 0, 0, 0) diff --git a/test_cartesian_point.py b/test_cartesian_point.py new file mode 100644 index 000000000..8e22f1a9e --- /dev/null +++ b/test_cartesian_point.py @@ -0,0 +1,44 @@ +# Copyright (c) "Neo4j" +# Neo4j Sweden AB [https://neo4j.com] +# +# This file is part of Neo4j. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import pytest + +from neo4j.spatial import CartesianPoint + + +class CartesianPointTestCase: + + def test_alias_3d(self): + x, y, z = 3.2, 4.0, -1.2 + p = CartesianPoint((x, y, z)) + assert hasattr(p, "x") + assert p.x == x + assert hasattr(p, "y") + assert p.y == y + assert hasattr(p, "z") + assert p.z == z + + def test_alias_2d(self): + x, y = 3.2, 4.0 + p = CartesianPoint((x, y)) + assert hasattr(p, "x") + assert p.x == x + assert hasattr(p, "y") + assert p.y == y + with pytest.raises(AttributeError): + p.z diff --git a/test_clock.py b/test_clock.py new file mode 100644 index 000000000..43b957548 --- /dev/null +++ b/test_clock.py @@ -0,0 +1,72 @@ +# Copyright (c) "Neo4j" +# Neo4j Sweden AB [https://neo4j.com] +# +# This file is part of Neo4j. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +import pytest + +from neo4j.time._clock_implementations import ( + Clock, + ClockTime, +) + + +class TestClock: + + def test_no_clock_implementations(self): + try: + Clock._Clock__implementations = [] + with pytest.raises(RuntimeError): + _ = Clock() + finally: + Clock._Clock__implementations = None + + def test_base_clock_precision(self): + clock = object.__new__(Clock) + with pytest.raises(NotImplementedError): + _ = clock.precision() + + def test_base_clock_available(self): + clock = object.__new__(Clock) + with pytest.raises(NotImplementedError): + _ = clock.available() + + def test_base_clock_utc_time(self): + clock = object.__new__(Clock) + with pytest.raises(NotImplementedError): + _ = clock.utc_time() + + def test_local_offset(self): + clock = object.__new__(Clock) + offset = clock.local_offset() + assert isinstance(offset, ClockTime) + + def test_local_time(self): + _ = Clock() + for impl in Clock._Clock__implementations: + assert issubclass(impl, Clock) + clock = object.__new__(impl) + time = clock.local_time() + assert isinstance(time, ClockTime) + + def test_utc_time(self): + _ = Clock() + for impl in Clock._Clock__implementations: + assert issubclass(impl, Clock) + clock = object.__new__(impl) + time = clock.utc_time() + assert isinstance(time, ClockTime) diff --git a/test_clocktime.py b/test_clocktime.py new file mode 100644 index 000000000..0d3d8077f --- /dev/null +++ b/test_clocktime.py @@ -0,0 +1,103 @@ +# Copyright (c) "Neo4j" +# Neo4j Sweden AB [https://neo4j.com] +# +# This file is part of Neo4j. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +import pytest + +from neo4j.time import ( + ClockTime, + Duration, +) + + +class TestClockTime: + + def test_zero_(self): + ct = ClockTime() + assert ct.seconds == 0 + assert ct.nanoseconds == 0 + + def test_only_seconds(self): + ct = ClockTime(123456) + assert ct.seconds == 123456 + assert ct.nanoseconds == 0 + + def test_float(self): + ct = ClockTime(123456.789) + assert ct.seconds == 123456 + assert ct.nanoseconds == 789000000 + + def test_only_nanoseconds(self): + ct = ClockTime(0, 123456789) + assert ct.seconds == 0 + assert ct.nanoseconds == 123456789 + + def test_nanoseconds_overflow(self): + ct = ClockTime(0, 2123456789) + assert ct.seconds == 2 + assert ct.nanoseconds == 123456789 + + def test_positive_nanoseconds(self): + ct = ClockTime(1, 1) + assert ct.seconds == 1 + assert ct.nanoseconds == 1 + + def test_negative_nanoseconds(self): + ct = ClockTime(1, -1) + assert ct.seconds == 0 + assert ct.nanoseconds == 999999999 + + def test_add_float(self): + ct = ClockTime(123456.789) + 0.1 + assert ct.seconds == 123456 + assert ct.nanoseconds == 889000000 + + def test_add_duration(self): + ct = ClockTime(123456.789) + Duration(seconds=1) + assert ct.seconds == 123457 + assert ct.nanoseconds == 789000000 + + def test_add_duration_with_months(self): + with pytest.raises(ValueError): + _ = ClockTime(123456.789) + Duration(months=1) + + def test_add_object(self): + with pytest.raises(TypeError): + _ = ClockTime(123456.789) + object() + + def test_sub_float(self): + ct = ClockTime(123456.789) - 0.1 + assert ct.seconds == 123456 + assert ct.nanoseconds == 689000000 + + def test_sub_duration(self): + ct = ClockTime(123456.789) - Duration(seconds=1) + assert ct.seconds == 123455 + assert ct.nanoseconds == 789000000 + + def test_sub_duration_with_months(self): + with pytest.raises(ValueError): + _ = ClockTime(123456.789) - Duration(months=1) + + def test_sub_object(self): + with pytest.raises(TypeError): + _ = ClockTime(123456.789) - object() + + def test_repr(self): + ct = ClockTime(123456.789) + assert repr(ct).startswith("ClockTime") diff --git a/test_date.py b/test_date.py new file mode 100644 index 000000000..5cb496767 --- /dev/null +++ b/test_date.py @@ -0,0 +1,562 @@ +# Copyright (c) "Neo4j" +# Neo4j Sweden AB [https://neo4j.com] +# +# This file is part of Neo4j. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import copy +import datetime +from datetime import date +from time import struct_time + +import pytest +import pytz + +from neo4j.time import ( + Date, + Duration, + UnixEpoch, + ZeroDate, +) + + +timezone_eastern = pytz.timezone("US/Eastern") +timezone_utc = pytz.utc + + +class TestDate: + + def test_bad_attribute(self): + d = Date(2000, 1, 1) + with pytest.raises(AttributeError): + _ = d.x + + def test_zero_date(self): + d = Date(0, 0, 0) + assert d.year_month_day == (0, 0, 0) + assert d.year == 0 + assert d.month == 0 + assert d.day == 0 + assert d == ZeroDate + + def test_zero_ordinal(self): + d = Date.from_ordinal(0) + assert d.year_month_day == (0, 0, 0) + assert d.year == 0 + assert d.month == 0 + assert d.day == 0 + assert d == ZeroDate + + def test_ordinal_at_start_of_1970(self): + d = Date.from_ordinal(719163) + assert d.year_month_day == (1970, 1, 1) + assert d.year == 1970 + assert d.month == 1 + assert d.day == 1 + + def test_ordinal_at_end_of_1969(self): + d = Date.from_ordinal(719162) + assert d.year_month_day == (1969, 12, 31) + assert d.year == 1969 + assert d.month == 12 + assert d.day == 31 + + def test_ordinal_at_start_of_2018(self): + d = Date.from_ordinal(736695) + assert d.year_month_day == (2018, 1, 1) + assert d.year == 2018 + assert d.month == 1 + assert d.day == 1 + + def test_ordinal_at_end_of_2017(self): + d = Date.from_ordinal(736694) + assert d.year_month_day == (2017, 12, 31) + assert d.year == 2017 + assert d.month == 12 + assert d.day == 31 + + def test_all_positive_days_of_month_for_31_day_month(self): + for day in range(1, 32): + t = Date(1976, 1, day) + assert t.year_month_day == (1976, 1, day) + assert t.year == 1976 + assert t.month == 1 + assert t.day == day + with pytest.raises(ValueError): + _ = Date(1976, 1, 32) + + def test_all_positive_days_of_month_for_30_day_month(self): + for day in range(1, 31): + t = Date(1976, 6, day) + assert t.year_month_day == (1976, 6, day) + assert t.year == 1976 + assert t.month == 6 + assert t.day == day + with pytest.raises(ValueError): + _ = Date(1976, 6, 31) + + def test_all_positive_days_of_month_for_29_day_month(self): + for day in range(1, 30): + t = Date(1976, 2, day) + assert t.year_month_day == (1976, 2, day) + assert t.year == 1976 + assert t.month == 2 + assert t.day == day + with pytest.raises(ValueError): + _ = Date(1976, 2, 30) + + def test_all_positive_days_of_month_for_28_day_month(self): + for day in range(1, 29): + t = Date(1977, 2, day) + assert t.year_month_day == (1977, 2, day) + assert t.year == 1977 + assert t.month == 2 + assert t.day == day + with pytest.raises(ValueError): + _ = Date(1977, 2, 29) + + def test_last_but_2_day_for_31_day_month(self): + t = Date(1976, 1, -3) + assert t.year_month_day == (1976, 1, 29) + assert t.year == 1976 + assert t.month == 1 + assert t.day == 29 + + def test_last_but_1_day_for_31_day_month(self): + t = Date(1976, 1, -2) + assert t.year_month_day == (1976, 1, 30) + assert t.year == 1976 + assert t.month == 1 + assert t.day == 30 + + def test_last_day_for_31_day_month(self): + t = Date(1976, 1, -1) + assert t.year_month_day == (1976, 1, 31) + assert t.year == 1976 + assert t.month == 1 + assert t.day == 31 + + def test_last_but_1_day_for_30_day_month(self): + t = Date(1976, 6, -2) + assert t.year_month_day == (1976, 6, 29) + assert t.year == 1976 + assert t.month == 6 + assert t.day == 29 + + def test_last_day_for_30_day_month(self): + t = Date(1976, 6, -1) + assert t.year_month_day == (1976, 6, 30) + assert t.year == 1976 + assert t.month == 6 + assert t.day == 30 + + def test_day_28_for_29_day_month(self): + t = Date(1976, 2, 28) + assert t.year_month_day == (1976, 2, 28) + assert t.year == 1976 + assert t.month == 2 + assert t.day == 28 + + def test_last_day_for_29_day_month(self): + t = Date(1976, 2, -1) + assert t.year_month_day == (1976, 2, 29) + assert t.year == 1976 + assert t.month == 2 + assert t.day == 29 + + def test_last_day_for_28_day_month(self): + t = Date(1977, 2, -1) + assert t.year_month_day == (1977, 2, 28) + assert t.year == 1977 + assert t.month == 2 + assert t.day == 28 + + def test_cannot_use_year_lower_than_one(self): + with pytest.raises(ValueError): + _ = Date(0, 2, 1) + + def test_cannot_use_year_higher_than_9999(self): + with pytest.raises(ValueError): + _ = Date(10000, 2, 1) + + def test_from_timestamp_without_tz(self): + d = Date.from_timestamp(0) + assert d == Date(1970, 1, 1) + + def test_from_timestamp_with_tz(self): + d = Date.from_timestamp(0, tz=timezone_eastern) + assert d == Date(1969, 12, 31) + + def test_utc_from_timestamp(self): + d = Date.utc_from_timestamp(0) + assert d == Date(1970, 1, 1) + + def test_from_ordinal(self): + d = Date.from_ordinal(1) + assert d == Date(1, 1, 1) + + def test_parse(self): + d = Date.parse("2018-04-30") + assert d == Date(2018, 4, 30) + + def test_bad_parse_1(self): + with pytest.raises(ValueError): + _ = Date.parse("30 April 2018") + + def test_bad_parse_2(self): + with pytest.raises(ValueError): + _ = Date.parse("2018-04") + + def test_bad_parse_3(self): + with pytest.raises(ValueError): + _ = Date.parse(object()) + + def test_replace(self): + d1 = Date(2018, 4, 30) + d2 = d1.replace(year=2017) + assert d2 == Date(2017, 4, 30) + + def test_from_clock_time(self): + d = Date.from_clock_time((0, 0), epoch=UnixEpoch) + assert d == Date(1970, 1, 1) + + def test_bad_from_clock_time(self): + with pytest.raises(ValueError): + _ = Date.from_clock_time(object(), None) + + def test_is_leap_year(self): + assert (Date.is_leap_year(2000)) + assert (Date.is_leap_year(2001)) + + def test_days_in_year(self): + assert Date.days_in_year(2000) == 366 + assert Date.days_in_year(2001) == 365 + + def test_days_in_month(self): + assert Date.days_in_month(2000, 1) == 31 + assert Date.days_in_month(2000, 2) == 29 + assert Date.days_in_month(2001, 2) == 28 + + def test_instance_attributes(self): + d = Date(2018, 4, 30) + assert d.year == 2018 + assert d.month == 4 + assert d.day == 30 + assert d.year_month_day == (2018, 4, 30) + assert d.year_week_day == (2018, 18, 1) + assert d.year_day == (2018, 120) + + def test_can_add_years(self): + d1 = Date(1976, 6, 13) + d2 = d1 + Duration(years=2) + assert d2 == Date(1978, 6, 13) + + def test_can_add_negative_years(self): + d1 = Date(1976, 6, 13) + d2 = d1 + Duration(years=-2) + assert d2 == Date(1974, 6, 13) + + def test_can_add_years_and_months(self): + d1 = Date(1976, 6, 13) + d2 = d1 + Duration(years=2, months=3) + assert d2 == Date(1978, 9, 13) + + def test_can_add_negative_years_and_months(self): + d1 = Date(1976, 6, 13) + d2 = d1 + Duration(years=-2, months=-3) + assert d2 == Date(1974, 3, 13) + + def test_can_retain_offset_from_end_of_month(self): + d = Date(1976, 1, -1) + assert d == Date(1976, 1, 31) + d += Duration(months=1) + assert d == Date(1976, 2, 29) + d += Duration(months=1) + assert d == Date(1976, 3, 31) + d += Duration(months=1) + assert d == Date(1976, 4, 30) + d += Duration(months=1) + assert d == Date(1976, 5, 31) + d += Duration(months=1) + assert d == Date(1976, 6, 30) + + def test_can_roll_over_end_of_year(self): + d = Date(1976, 12, 1) + assert d == Date(1976, 12, 1) + d += Duration(months=1) + assert d == Date(1977, 1, 1) + + def test_can_add_months_and_days(self): + d1 = Date(1976, 6, 13) + d2 = d1 + Duration(months=1, days=1) + assert d2 == Date(1976, 7, 14) + + def test_can_add_months_then_days(self): + d1 = Date(1976, 6, 13) + d2 = d1 + Duration(months=1) + Duration(days=1) + assert d2 == Date(1976, 7, 14) + + def test_cannot_add_seconds(self): + d1 = Date(1976, 6, 13) + with pytest.raises(ValueError): + _ = d1 + Duration(seconds=1) + + def test_adding_empty_duration_returns_self(self): + d1 = Date(1976, 6, 13) + d2 = d1 + Duration() + assert d1 is d2 + + def test_adding_object(self): + d1 = Date(1976, 6, 13) + with pytest.raises(TypeError): + _ = d1 + object() + + def test_can_add_days_then_months(self): + d1 = Date(1976, 6, 13) + d2 = d1 + Duration(days=1) + Duration(months=1) + assert d2 == Date(1976, 7, 14) + + def test_can_add_months_and_days_for_last_day_of_short_month(self): + d1 = Date(1976, 6, 30) + d2 = d1 + Duration(months=1, days=1) + assert d2 == Date(1976, 8, 1) + + def test_can_add_months_then_days_for_last_day_of_short_month(self): + d1 = Date(1976, 6, 30) + d2 = d1 + Duration(months=1) + Duration(days=1) + assert d2 == Date(1976, 8, 1) + + def test_can_add_days_then_months_for_last_day_of_short_month(self): + d1 = Date(1976, 6, 30) + d2 = d1 + Duration(days=1) + Duration(months=1) + assert d2 == Date(1976, 8, 1) + + def test_can_add_months_and_days_for_last_day_of_long_month(self): + d1 = Date(1976, 1, 31) + d2 = d1 + Duration(months=1, days=1) + assert d2 == Date(1976, 3, 1) + + def test_can_add_months_then_days_for_last_day_of_long_month(self): + d1 = Date(1976, 1, 31) + d2 = d1 + Duration(months=1) + Duration(days=1) + assert d2 == Date(1976, 3, 1) + + def test_can_add_days_then_months_for_last_day_of_long_month(self): + d1 = Date(1976, 1, 31) + d2 = d1 + Duration(days=1) + Duration(months=1) + assert d2 == Date(1976, 3, 1) + + def test_can_add_negative_months_and_days(self): + d1 = Date(1976, 6, 13) + d2 = d1 + Duration(months=-1, days=-1) + assert d2 == Date(1976, 5, 12) + + def test_can_add_negative_months_then_days(self): + d1 = Date(1976, 6, 13) + d2 = d1 + Duration(months=-1) + Duration(days=-1) + assert d2 == Date(1976, 5, 12) + + def test_can_add_negative_days_then_months(self): + d1 = Date(1976, 6, 13) + d2 = d1 + Duration(days=-1) + Duration(months=-1) + assert d2 == Date(1976, 5, 12) + + def test_can_add_negative_months_and_days_for_first_day_of_month(self): + d1 = Date(1976, 6, 1) + d2 = d1 + Duration(months=-1, days=-1) + assert d2 == Date(1976, 4, 30) + + def test_can_add_negative_months_then_days_for_first_day_of_month(self): + d1 = Date(1976, 6, 1) + d2 = d1 + Duration(months=-1) + Duration(days=-1) + assert d2 == Date(1976, 4, 30) + + def test_can_add_negative_days_then_months_for_last_day_of_month(self): + d1 = Date(1976, 6, 1) + d2 = d1 + Duration(days=-1) + Duration(months=-1) + assert d2 == Date(1976, 4, 30) + + def test_can_add_negative_month_for_last_day_of_long_month(self): + d1 = Date(1976, 5, 31) + d2 = d1 + Duration(months=-1) + assert d2 == Date(1976, 4, 30) + + def test_can_add_negative_month_for_january(self): + d1 = Date(1976, 1, 31) + d2 = d1 + Duration(months=-1) + assert d2 == Date(1975, 12, 31) + + def test_subtract_date(self): + new_year = Date(2000, 1, 1) + christmas = Date(1999, 12, 25) + assert new_year - christmas == Duration(days=7) + + def test_subtract_duration(self): + new_year = Date(2000, 1, 1) + christmas = Date(1999, 12, 25) + assert new_year - Duration(days=7) == christmas + + def test_subtract_object(self): + new_year = Date(2000, 1, 1) + with pytest.raises(TypeError): + _ = new_year - object() + + def test_date_less_than(self): + new_year = Date(2000, 1, 1) + christmas = Date(1999, 12, 25) + assert christmas < new_year + + def test_date_less_than_object(self): + d = Date(2000, 1, 1) + with pytest.raises(TypeError): + _ = d < object() + + def test_date_less_than_or_equal_to(self): + new_year = Date(2000, 1, 1) + christmas = Date(1999, 12, 25) + assert christmas <= new_year + + def test_date_less_than_or_equal_to_object(self): + d = Date(2000, 1, 1) + with pytest.raises(TypeError): + _ = d <= object() + + def test_date_greater_than_or_equal_to(self): + new_year = Date(2000, 1, 1) + christmas = Date(1999, 12, 25) + assert new_year >= christmas + + def test_date_greater_than_or_equal_to_object(self): + d = Date(2000, 1, 1) + with pytest.raises(TypeError): + _ = d >= object() + + def test_date_greater_than(self): + new_year = Date(2000, 1, 1) + christmas = Date(1999, 12, 25) + assert new_year > christmas + + def test_date_greater_than_object(self): + d = Date(2000, 1, 1) + with pytest.raises(TypeError): + _ = d > object() + + def test_date_equal(self): + d1 = Date(2000, 1, 1) + d2 = Date(2000, 1, 1) + assert d1 == d2 + + def test_date_not_equal(self): + d1 = Date(2000, 1, 1) + d2 = Date(2000, 1, 2) + assert d1 != d2 + + def test_date_not_equal_to_object(self): + d1 = Date(2000, 1, 1) + assert d1 != object() + + def test_year_week_day(self): + for ordinal in range(Date(2001, 1, 1).to_ordinal(), + Date(2008, 1, 1).to_ordinal()): + assert Date.from_ordinal(ordinal).iso_calendar() \ + == date.fromordinal(ordinal).isocalendar() + + def test_time_tuple(self): + d = Date(2018, 4, 30) + assert d.time_tuple() == struct_time( + (2018, 4, 30, 0, 0, 0, 0, 120, -1)) + + def test_to_clock_time(self): + d = Date(2018, 4, 30) + assert d.to_clock_time(UnixEpoch) == (1525046400, 0) + assert d.to_clock_time(d) == (0, 0) + with pytest.raises(TypeError): + _ = d.to_clock_time(object()) + + def test_weekday(self): + d = Date(2018, 4, 30) + assert d.weekday() == 0 + + def test_iso_weekday(self): + d = Date(2018, 4, 30) + assert d.iso_weekday() == 1 + + def test_str(self): + assert str(Date(2018, 4, 30)) == "2018-04-30" + assert str(Date(0, 0, 0)) == "0000-00-00" + + def test_repr(self): + assert repr(Date(2018, 4, 30)) == "neo4j.time.Date(2018, 4, 30)" + assert repr(Date(0, 0, 0)) == "neo4j.time.ZeroDate" + + def test_format(self): + d = Date(2018, 4, 30) + with pytest.raises(NotImplementedError): + _ = d.__format__("") + + def test_from_native(self): + native = date(2018, 10, 1) + d = Date.from_native(native) + assert d.year == native.year + assert d.month == native.month + assert d.day == native.day + + def test_to_native(self): + d = Date(2018, 10, 1) + native = d.to_native() + assert d.year == native.year + assert d.month == native.month + assert d.day == native.day + + def test_iso_format(self): + d = Date(2018, 10, 1) + assert "2018-10-01" == d.iso_format() + + def test_from_iso_format(self): + expected = Date(2018, 10, 1) + actual = Date.from_iso_format("2018-10-01") + assert expected == actual + + def test_date_copy(self): + d = Date(2010, 10, 1) + d2 = copy.copy(d) + assert d != d2 + assert d == d2 + + def test_date_deep_copy(self): + d = Date(2010, 10, 1) + d2 = copy.deepcopy(d) + assert d != d2 + assert d == d2 + + +@pytest.mark.parametrize(("tz", "expected"), ( + (None, (1970, 1, 1)), + (timezone_eastern, (1970, 1, 1)), + (timezone_utc, (1970, 1, 1)), + (pytz.FixedOffset(-12 * 60), (1970, 1, 1)), + (datetime.timezone(datetime.timedelta(hours=-12)), (1970, 1, 1)), + (pytz.FixedOffset(-13 * 60), (1969, 12, 31)), + (datetime.timezone(datetime.timedelta(hours=-13)), (1969, 12, 31)), + (pytz.FixedOffset(11 * 60), (1970, 1, 1)), + (datetime.timezone(datetime.timedelta(hours=11)), (1970, 1, 1)), + (pytz.FixedOffset(12 * 60), (1970, 1, 2)), + (datetime.timezone(datetime.timedelta(hours=12)), (1970, 1, 2)), + +)) +def test_today(tz, expected): + d = Date.today(tz=tz) + assert isinstance(d, Date) + assert d.year_month_day == expected diff --git a/test_point.py b/test_point.py new file mode 100644 index 000000000..0b46d25dc --- /dev/null +++ b/test_point.py @@ -0,0 +1,52 @@ +# Copyright (c) "Neo4j" +# Neo4j Sweden AB [https://neo4j.com] +# +# This file is part of Neo4j. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import pytest + +from neo4j._spatial import ( + Point, + point_type, +) + + +class PointTestCase: + + def test_wrong_type_arguments(self): + for argument in (("a", "b"), ({"x": 1.0, "y": 2.0})): + with pytest.subtests(): + with pytest.raises(ValueError): + Point(argument) + + def test_number_arguments(self): + for argument in ((1, 2), (1.2, 2.1)): + with pytest.subtests(): + p = Point(argument) + assert tuple(p) == argument + + def test_immutable_coordinates(self): + MyPoint = point_type("MyPoint", ["x", "y"], {2: 1234}) + coordinates = (.1, 0) + p = MyPoint(coordinates) + with pytest.raises(AttributeError): + p.x = 2.0 + with pytest.raises(AttributeError): + p.y = 2.0 + with pytest.raises(TypeError): + p[0] = 2.0 + with pytest.raises(TypeError): + p[1] = 2.0 diff --git a/test_wgs84_point.py b/test_wgs84_point.py new file mode 100644 index 000000000..aad72ab6c --- /dev/null +++ b/test_wgs84_point.py @@ -0,0 +1,62 @@ +# Copyright (c) "Neo4j" +# Neo4j Sweden AB [https://neo4j.com] +# +# This file is part of Neo4j. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import pytest + +from neo4j.spatial import WGS84Point + + +class WGS84PointTestCase: + + def test_alias_3d(self): + x, y, z = 3.2, 4.0, -1.2 + p = WGS84Point((x, y, z)) + + assert hasattr(p, "longitude") + assert p.longitude == x + assert hasattr(p, "x") + assert p.x == x + + assert hasattr(p, "latitude") + assert p.latitude == y + assert hasattr(p, "y") + assert p.y == y + + assert hasattr(p, "height") + assert p.height == z + assert hasattr(p, "z") + assert p.z == z + + def test_alias_2d(self): + x, y = 3.2, 4.0 + p = WGS84Point((x, y)) + + assert hasattr(p, "longitude") + assert p.longitude == x + assert hasattr(p, "x") + assert p.x == x + + assert hasattr(p, "latitude") + assert p.latitude == y + assert hasattr(p, "y") + assert p.y == y + + with pytest.raises(AttributeError): + p.height + with pytest.raises(AttributeError): + p.z From 5ff05dd1c007d7ddef8182af79c4922cce8eaddb Mon Sep 17 00:00:00 2001 From: Prychantovskyi <75383953+Prychantovskyi@users.noreply.github.com> Date: Fri, 22 Jul 2022 09:51:13 +0100 Subject: [PATCH 02/19] Delete __init__.py --- __init__.py | 2624 --------------------------------------------------- 1 file changed, 2624 deletions(-) delete mode 100644 __init__.py diff --git a/__init__.py b/__init__.py deleted file mode 100644 index 5d0edf244..000000000 --- a/__init__.py +++ /dev/null @@ -1,2624 +0,0 @@ -# Copyright (c) "Neo4j" -# Neo4j Sweden AB [https://neo4j.com] -# -# This file is part of Neo4j. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -""" -This module contains the fundamental types used for temporal accounting as well -as a number of utility functions. -""" - - -from datetime import ( - date, - datetime, - time, - timedelta, - timezone, -) -from functools import total_ordering -from re import compile as re_compile -from time import ( - gmtime, - mktime, - struct_time, -) - -from ._arithmetic import ( - nano_add, - nano_div, - round_half_to_even, - symmetric_divmod, -) -from ._metaclasses import ( - DateTimeType, - DateType, - TimeType, -) - - -__all__ = [ - "MIN_INT64", - "MAX_INT64", - "MIN_YEAR", - "MAX_YEAR", - "Duration", - "Date", - "ZeroDate", - "Time", - "Midnight", - "Midday", - "DateTime", - "Never", - "UnixEpoch", -] - - -MIN_INT64 = -(2 ** 63) -MAX_INT64 = (2 ** 63) - 1 - -#: The smallest year number allowed in a :class:`.Date` or :class:`.DateTime` -#: object to be compatible with :class:`datetime.date` and -#: :class:`datetime.datetime`. -MIN_YEAR = 1 - -#: The largest year number allowed in a :class:`.Date` or :class:`.DateTime` -#: object to be compatible with :class:`datetime.date` and -#: :class:`datetime.datetime`. -MAX_YEAR = 9999 - -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)?)?$" -) - -NANO_SECONDS = 1000000000 -AVERAGE_SECONDS_IN_MONTH = 2629746 -AVERAGE_SECONDS_IN_DAY = 86400 - - -def _is_leap_year(year): - if year % 4 != 0: - return False - if year % 100 != 0: - return True - return year % 400 == 0 - - -IS_LEAP_YEAR = {year: _is_leap_year(year) for year in range(MIN_YEAR, MAX_YEAR + 1)} - - -def _days_in_year(year): - return 366 if IS_LEAP_YEAR[year] else 365 - - -DAYS_IN_YEAR = {year: _days_in_year(year) for year in range(MIN_YEAR, MAX_YEAR + 1)} - - -def _days_in_month(year, month): - if month in (9, 4, 6, 11): - return 30 - elif month != 2: - return 31 - else: - return 29 if IS_LEAP_YEAR[year] else 28 - - -DAYS_IN_MONTH = {(year, month): _days_in_month(year, month) - for year in range(MIN_YEAR, MAX_YEAR + 1) for month in range(1, 13)} - - -def _normalize_day(year, month, day): - """ Coerce the day of the month to an internal value that may or - may not match the "public" value. - - With the exception of the last three days of every month, all - days are stored as-is. The last three days are instead stored - as -1 (the last), -2 (the second to last) and -3 (the third to - last). - - Therefore, for a 28-day month, the last week is as follows: - - Day | 22 23 24 25 26 27 28 - Value | 22 23 24 25 -3 -2 -1 - - For a 29-day month, the last week is as follows: - - Day | 23 24 25 26 27 28 29 - Value | 23 24 25 26 -3 -2 -1 - - For a 30-day month, the last week is as follows: - - Day | 24 25 26 27 28 29 30 - Value | 24 25 26 27 -3 -2 -1 - - For a 31-day month, the last week is as follows: - - Day | 25 26 27 28 29 30 31 - Value | 25 26 27 28 -3 -2 -1 - - This slightly unintuitive system makes some temporal arithmetic - produce a more desirable outcome. - - :param year: - :param month: - :param day: - :return: - """ - if year < MIN_YEAR or year > MAX_YEAR: - raise ValueError("Year out of range (%d..%d)" % (MIN_YEAR, MAX_YEAR)) - if month < 1 or month > 12: - raise ValueError("Month out of range (1..12)") - days_in_month = DAYS_IN_MONTH[(year, month)] - if day in (days_in_month, -1): - return year, month, -1 - if day in (days_in_month - 1, -2): - return year, month, -2 - if day in (days_in_month - 2, -3): - return year, month, -3 - if 1 <= day <= days_in_month - 3: - return year, month, int(day) - # TODO improve this error message - raise ValueError("Day %d out of range (1..%d, -1, -2 ,-3)" % (day, days_in_month)) - - -class ClockTime(tuple): - """ A count of `seconds` and `nanoseconds`. This class can be used to - mark a particular point in time, relative to an externally-specified - epoch. - - The `seconds` and `nanoseconds` values provided to the constructor can - can have any sign but will be normalized internally into a positive or - negative `seconds` value along with a positive `nanoseconds` value - between `0` and `999,999,999`. Therefore ``ClockTime(-1, -1)`` is - normalized to ``ClockTime(-2, 999999999)``. - - Note that the structure of a :class:`.ClockTime` object is similar to - the ``timespec`` struct in C. - """ - - def __new__(cls, seconds=0, nanoseconds=0): - seconds, nanoseconds = divmod( - int(NANO_SECONDS * seconds) + int(nanoseconds), NANO_SECONDS - ) - return tuple.__new__(cls, (seconds, nanoseconds)) - - def __add__(self, other): - if isinstance(other, (int, float)): - other = ClockTime(other) - if isinstance(other, ClockTime): - return ClockTime(self.seconds + other.seconds, self.nanoseconds + other.nanoseconds) - if isinstance(other, Duration): - 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.nanoseconds)) - return NotImplemented - - def __sub__(self, other): - if isinstance(other, (int, float)): - other = ClockTime(other) - if isinstance(other, ClockTime): - return ClockTime(self.seconds - other.seconds, self.nanoseconds - other.nanoseconds) - 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.nanoseconds)) - return NotImplemented - - def __repr__(self): - return "ClockTime(seconds=%r, nanoseconds=%r)" % self - - @property - def seconds(self): - return self[0] - - @property - def nanoseconds(self): - return self[1] - - -class Clock: - """ Accessor for time values. This class is fulfilled by implementations - that subclass :class:`.Clock`. These implementations are contained within - the ``neo4j.time.clock_implementations`` module, and are not intended to be - accessed directly. - - Creating a new :class:`.Clock` instance will produce the highest - precision clock implementation available. - - >>> clock = Clock() - >>> type(clock) # doctest: +SKIP - neo4j.time.clock_implementations.LibCClock - >>> clock.local_time() # doctest: +SKIP - ClockTime(seconds=1525265942, nanoseconds=506844026) - - """ - - _Clock__implementations = None - __implementations = None - - def __new__(cls): - if cls.__implementations is None: - # Find an available clock with the best precision - import neo4j.time._clock_implementations - cls.__implementations = sorted((clock for clock in Clock.__subclasses__() if clock.available()), - key=lambda clock: clock.precision(), reverse=True) - if not cls.__implementations: - raise RuntimeError("No clock implementations available") - instance = object.__new__(cls.__implementations[0]) - return instance - - @classmethod - def precision(cls): - """ The precision of this clock implementation, represented as a - number of decimal places. Therefore, for a nanosecond precision - clock, this function returns `9`. - """ - raise NotImplementedError("No clock implementation selected") - - @classmethod - def available(cls): - """ A boolean flag to indicate whether or not this clock - implementation is available on this platform. - """ - raise NotImplementedError("No clock implementation selected") - - @classmethod - def local_offset(cls): - """The offset from UTC for local time read from this clock. - This may raise OverflowError if not supported, because of platform depending C libraries. - - :returns: - :rtype: - - :raises OverflowError: - """ - # 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. - This may raise OverflowError if not supported, because of platform depending C libraries. - - :returns: - :rtype: - - :raises OverflowError: - """ - return self.utc_time() + self.local_offset() - - def utc_time(self): - """ Read and return the current UTC time from this clock, measured - relative to the Unix Epoch. - """ - raise NotImplementedError("No clock implementation selected") - - -class Duration(tuple): - """A difference between two points in time. - - A :class:`.Duration` represents the difference between two points in time. - Duration objects store a composite value of `months`, `days`, `seconds`, - and `nanoseconds`. Unlike :class:`datetime.timedelta` however, days, and - seconds/nanoseconds are never interchanged. All values except seconds and - nanoseconds are applied separately in calculations (element-wise). - - A :class:`.Duration` stores four primary instance attributes internally: - `months`, `days`, `seconds` and `nanoseconds`. These are maintained as - individual values and are immutable. Each of these four attributes can carry - its own sign, with the exception of `nanoseconds`, which always has the same - sign as `seconds`. The constructor will establish this state, should the - duration be initialized with conflicting `seconds` and `nanoseconds` signs. - This structure allows the modelling of durations such as - `3 months minus 2 days`. - - To determine if a :class:`Duration` `d` is overflowing the accepted values - of the database, first, all `nanoseconds` outside the range -999_999_999 and - 999_999_999 are transferred into the seconds field. Then, `months`, `days`, - and `seconds` are summed up like so: - `months * 2629746 + days * 86400 + d.seconds + d.nanoseconds // 1000000000`. - (Like the integer division in Python, this one is to be understood as - rounding down rather than towards 0.) - This value must be between -(2\\ :sup:`63`) and (2\\ :sup:`63` - 1) - inclusive. - - :param years: will be added times 12 to `months` - :type years: float - :param months: will be truncated to :class:`int` (`int(months)`) - :type months: float - :param weeks: will be added times 7 to `days` - :type weeks: float - :param days: will be truncated to :class:`int` (`int(days)`) - :type days: float - :param hours: will be added times 3,600,000,000,000 to `nanoseconds` - :type hours: float - :param minutes: will be added times 60,000,000,000 to `nanoseconds` - :type minutes: float - :param seconds: will be added times 1,000,000,000 to `nanoseconds`` - :type seconds: float - :param milliseconds: will be added times 1,000,000 to `nanoseconds` - :type microseconds: float - :param microseconds: will be added times 1,000 to `nanoseconds` - :type milliseconds: float - :param nanoseconds: will be truncated to :class:`int` (`int(nanoseconds)`) - :type nanoseconds: float - - :raises ValueError: the components exceed the limits as described above. - """ - - # i64: i64:i64: i32 - - min = None - """The lowest duration value possible.""" - - max = None - """The highest duration value possible.""" - - def __new__(cls, years=0, months=0, weeks=0, days=0, hours=0, minutes=0, - seconds=0, milliseconds=0, microseconds=0, nanoseconds=0): - mo = int(12 * years + months) - if mo < MIN_INT64 or mo > MAX_INT64: - raise ValueError("Months value out of range") - 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) - avg_total_seconds = (mo * AVERAGE_SECONDS_IN_MONTH - + d * AVERAGE_SECONDS_IN_DAY - + s - - (1 if ns < 0 else 0)) - if avg_total_seconds < MIN_INT64 or avg_total_seconds > MAX_INT64: - raise ValueError("Duration value out of range: %r", - cls.__repr__((mo, d, s, ns))) - return tuple.__new__(cls, (mo, d, s, ns)) - - def __bool__(self): - """Falsy if all primary instance attributes are.""" - return any(map(bool, self)) - - __nonzero__ = __bool__ - - def __add__(self, other): - """Add a :class:`.Duration` or :class:`datetime.timedelta`. - - :rtype: Duration - """ - if isinstance(other, Duration): - 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] + other.days, - seconds=self[2] + other.seconds, - nanoseconds=self[3] + other.microseconds * 1000 - ) - return NotImplemented - - def __sub__(self, other): - """Subtract a :class:`.Duration` or :class:`datetime.timedelta`. - - :rtype: Duration - """ - if isinstance(other, Duration): - 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] - other.days, - seconds=self[2] - other.seconds, - nanoseconds=self[3] - other.microseconds * 1000 - ) - return NotImplemented - - def __mul__(self, other): - """Multiply by an :class:`int` or :class:`float`. - - The operation is performed element-wise on - ``(months, days, nanaoseconds)`` where - - * years go into months, - * weeks go into days, - * seconds and all sub-second units go into nanoseconds. - - Each element will be rounded to the nearest integer (.5 towards even). - - :rtype: Duration - """ - if isinstance(other, (int, float)): - 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 - - def __floordiv__(self, other): - """Integer division by an :class:`int`. - - The operation is performed element-wise on - ``(months, days, nanaoseconds)`` where - - * years go into months, - * weeks go into days, - * seconds and all sub-second units go into nanoseconds. - - Each element will be rounded towards -inf. - - :rtype: Duration - """ - if isinstance(other, int): - return Duration( - months=self[0] // other, days=self[1] // other, - nanoseconds=(self[2] * NANO_SECONDS + self[3]) // other - ) - return NotImplemented - - def __mod__(self, other): - """Modulo operation by an :class:`int`. - - The operation is performed element-wise on - ``(months, days, nanaoseconds)`` where - - * years go into months, - * weeks go into days, - * seconds and all sub-second units go into nanoseconds. - - :rtype: Duration - """ - if isinstance(other, int): - return Duration( - months=self[0] % other, days=self[1] % other, - nanoseconds=(self[2] * NANO_SECONDS + self[3]) % other - ) - return NotImplemented - - def __divmod__(self, other): - """Division and modulo operation by an :class:`int`. - - See :meth:`__floordiv__` and :meth:`__mod__`. - - :rtype: (Duration, Duration) - """ - if isinstance(other, int): - return self.__floordiv__(other), self.__mod__(other) - return NotImplemented - - def __truediv__(self, other): - """Division by an :class:`int` or :class:`float`. - - The operation is performed element-wise on - ``(months, days, nanaoseconds)`` where - - * years go into months, - * weeks go into days, - * seconds and all sub-second units go into nanoseconds. - - Each element will be rounded to the nearest integer (.5 towards even). - - :rtype: Duration - """ - if isinstance(other, (int, float)): - return Duration( - months=round_half_to_even(self[0] / other), - days=round_half_to_even(self[1] / other), - nanoseconds=round_half_to_even( - self[2] * NANO_SECONDS / other - + self[3] / other - ) - ) - return NotImplemented - - def __pos__(self): - """""" - return self - - def __neg__(self): - """""" - 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]), nanoseconds=abs(self[3])) - - def __repr__(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.__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): - """Parse a ISO formatted duration string. - - Accepted formats (all lowercase letters are placeholders): - 'P', a zero length duration - 'PyY', y being a number of years - 'PmM', m being a number of months - 'PdD', d being a number of days - - Any combination of the above, e.g., 'P25Y1D' for 25 years and 1 day. - - 'PThH', h being a number of hours - 'PTmM', h being a number of minutes - 'PTsS', h being a number of seconds - 'PTs.sss...S', h being a fractional number of seconds - - Any combination of the above, e.g. 'PT5H1.2S' for 5 hours and 1.2 - seconds. - Any combination of all options, e.g. 'P13MT100M' for 13 months and - 100 minutes. - - :param s: String to parse - :type s: str - - :rtype: Duration - - :raises ValueError: if the string does not match the required format. - """ - 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(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") - - fromisoformat = from_iso_format - - def iso_format(self, sep="T"): - """Return the :class:`Duration` as ISO formatted string. - - :param sep: the separator before the time components. - :type sep: str - - :rtype: str - """ - parts = [] - hours, minutes, seconds, nanoseconds = \ - self.hours_minutes_seconds_nanoseconds - if hours: - parts.append("%dH" % hours) - if minutes: - parts.append("%dM" % minutes) - 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: - 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 - if days: - parts.insert(0, "%dD" % days) - if months: - parts.insert(0, "%dM" % months) - if years: - parts.insert(0, "%dY" % years) - if parts: - parts.insert(0, "P") - return "".join(parts) - else: - return "PT0S" - - @property - def months(self): - """The months of the :class:`Duration`. - - :type: int - """ - return self[0] - - @property - def days(self): - """The days of the :class:`Duration`. - - :type: int - """ - return self[1] - - @property - def seconds(self): - """The seconds of the :class:`Duration`. - - :type: int - """ - return self[2] - - @property - def nanoseconds(self): - """The nanoseconds of the :class:`Duration`. - - :type: int - """ - return self[3] - - @property - def years_months_days(self): - """ - - :return: - """ - years, months = symmetric_divmod(self[0], 12) - return years, months, self[1] - - @property - def hours_minutes_seconds_nanoseconds(self): - """ A 4-tuple of (hours, minutes, seconds, nanoseconds). - - :type: (int, int, int, int) - """ - minutes, seconds = symmetric_divmod(self[2], 60) - hours, minutes = symmetric_divmod(minutes, 60) - return hours, minutes, seconds, self[3] - - -Duration.min = Duration(seconds=MIN_INT64, nanoseconds=0) -Duration.max = Duration(seconds=MAX_INT64, nanoseconds=999999999) - - -class Date(metaclass=DateType): - """Idealized date representation. - - A :class:`.Date` object represents a date (year, month, and day) in the - `proleptic Gregorian Calendar - `_. - - Years between `0001` and `9999` are supported, with additional support for - the "zero date" used in some contexts. - - Each date is based on a proleptic Gregorian ordinal, which models - 1 Jan 0001 as `day 1` and counts each subsequent day up to, and including, - 31 Dec 9999. The standard `year`, `month` and `day` value of each date is - also available. - - Internally, the day of the month is always stored as-is, with the exception - of the last three days of that month. These are always stored as - -1, -2 and -3 (counting from the last day). This system allows some temporal - arithmetic (particularly adding or subtracting months) to produce a more - desirable outcome than would otherwise be produced. Externally, the day - number is always the same as would be written on a calendar. - - :param year: the year. Minimum :attr:`.MIN_YEAR` (0001), maximum - :attr:`.MAX_YEAR` (9999). - :type year: int - :param month: the month. Minimum 1, maximum 12. - :type month: int - :param day: the day. Minimum 1, maximum - :attr:`Date.days_in_month(year, month) `. - :type day: int - - A zero date can also be acquired by passing all zeroes to the - :class:`neo4j.time.Date` constructor or by using the :attr:`ZeroDate` - constant. - """ - - # CONSTRUCTOR # - - def __new__(cls, year, month, day): - if year == month == day == 0: - return ZeroDate - year, month, day = _normalize_day(year, month, day) - ordinal = cls.__calc_ordinal(year, month, day) - return cls.__new(ordinal, year, month, day) - - @classmethod - def __new(cls, ordinal, year, month, day): - instance = object.__new__(cls) - instance.__ordinal = int(ordinal) - instance.__year = int(year) - instance.__month = int(month) - instance.__day = int(day) - return instance - - def __getattr__(self, name): - """ Map standard library attribute names to local attribute names, - for compatibility. - """ - try: - return { - "isocalendar": self.iso_calendar, - "isoformat": self.iso_format, - "isoweekday": self.iso_weekday, - "strftime": self.__format__, - "toordinal": self.to_ordinal, - "timetuple": self.time_tuple, - }[name] - except KeyError: - raise AttributeError("Date has no attribute %r" % name) - - # CLASS METHODS # - - @classmethod - def today(cls, tz=None): - """Get the current date. - - :param tz: timezone or None to get the local :class:`.Date`. - :type tz: datetime.tzinfo or None - - :rtype: Date - - :raises OverflowError: if the timestamp is out of the range of values - supported by the platform C localtime() function. It’s common for - this to be restricted to years from 1970 through 2038. - """ - if tz is None: - return cls.from_clock_time(Clock().local_time(), UnixEpoch) - else: - return ( - DateTime.utc_now() - .replace(tzinfo=timezone.utc).astimezone(tz) - .date() - ) - - @classmethod - def utc_today(cls): - """Get the current date as UTC local date. - - :rtype: Date - """ - return cls.from_clock_time(Clock().utc_time(), UnixEpoch) - - @classmethod - def from_timestamp(cls, timestamp, tz=None): - """:class:`.Date` from a time stamp (seconds since unix epoch). - - :param timestamp: the unix timestamp (seconds since unix epoch). - :type timestamp: float - :param tz: timezone. Set to None to create a local :class:`.Date`. - :type tz: datetime.tzinfo or None - - :rtype: Date - - :raises OverflowError: if the timestamp is out of the range of values - supported by the platform C localtime() function. It’s common for - this to be restricted to years from 1970 through 2038. - """ - return cls.from_native(datetime.fromtimestamp(timestamp, tz)) - - @classmethod - def utc_from_timestamp(cls, timestamp): - """:class:`.Date` from a time stamp (seconds since unix epoch). - - Returns the `Date` as local date `Date` in UTC. - - :rtype: Date - """ - return cls.from_clock_time((timestamp, 0), UnixEpoch) - - @classmethod - def from_ordinal(cls, ordinal): - """ - The :class:`.Date` that corresponds to the proleptic Gregorian ordinal. - - `0001-01-01` has ordinal 1 and `9999-12-31` has ordinal 3,652,059. - Values outside of this range trigger a :exc:`ValueError`. - The corresponding instance method for the reverse date-to-ordinal - transformation is :meth:`.to_ordinal`. - The ordinal 0 has a special semantic and will return :attr:`ZeroDate`. - - :rtype: Date - - :raises ValueError: if the ordinal is outside the range [0, 3652059] - (both values included). - """ - if ordinal == 0: - return ZeroDate - if ordinal >= 736695: - year = 2018 # Project release year - month = 1 - day = int(ordinal - 736694) - elif ordinal >= 719163: - year = 1970 # Unix epoch - month = 1 - day = int(ordinal - 719162) - else: - year = 1 - month = 1 - day = int(ordinal) - if day < 1 or day > 3652059: - # Note: this requires a maximum of 22 bits for storage - # Could be transferred in 3 bytes. - raise ValueError("Ordinal out of range (1..3652059)") - if year < MIN_YEAR or year > MAX_YEAR: - raise ValueError("Year out of range (%d..%d)" % (MIN_YEAR, MAX_YEAR)) - days_in_year = DAYS_IN_YEAR[year] - while day > days_in_year: - day -= days_in_year - year += 1 - days_in_year = DAYS_IN_YEAR[year] - days_in_month = DAYS_IN_MONTH[(year, month)] - while day > days_in_month: - day -= days_in_month - month += 1 - days_in_month = DAYS_IN_MONTH[(year, month)] - year, month, day = _normalize_day(year, month, day) - return cls.__new(ordinal, year, month, day) - - @classmethod - def parse(cls, s): - """Parse a string to produce a :class:`.Date`. - - Accepted formats: - 'Y-M-D' - - :param s: the string to be parsed. - :type s: str - - :rtype: Date - - :raises ValueError: if the string could not be parsed. - """ - try: - numbers = map(int, s.split("-")) - except (ValueError, AttributeError): - raise ValueError("Date string must be in format YYYY-MM-DD") - else: - numbers = list(numbers) - if len(numbers) == 3: - return cls(*numbers) - raise ValueError("Date string must be in format YYYY-MM-DD") - - @classmethod - def from_iso_format(cls, s): - """Parse a ISO formatted Date string. - - Accepted formats: - 'YYYY-MM-DD' - - :param s: the string to be parsed. - :type s: str - - :rtype: Date - - :raises ValueError: if the string could not be parsed. - """ - m = DATE_ISO_PATTERN.match(s) - if m: - year = int(m.group(1)) - month = int(m.group(2)) - day = int(m.group(3)) - return cls(year, month, day) - raise ValueError("Date string must be in format YYYY-MM-DD") - - @classmethod - def from_native(cls, d): - """Convert from a native Python `datetime.date` value. - - :param d: the date to convert. - :type d: datetime.date - - :rtype: Date - """ - return Date.from_ordinal(d.toordinal()) - - @classmethod - def from_clock_time(cls, clock_time, epoch): - """Convert from a ClockTime relative to a given epoch. - - :param clock_time: the clock time as :class:`.ClockTime` or as tuple of - (seconds, nanoseconds) - :type clock_time: ClockTime or (float, int) - :param epoch: the epoch to which `clock_time` is relative - :type epoch: DateTime - - :rtype: Date - """ - try: - clock_time = ClockTime(*clock_time) - except (TypeError, ValueError): - raise ValueError("Clock time must be a 2-tuple of (s, ns)") - else: - ordinal = clock_time.seconds // 86400 - return Date.from_ordinal(ordinal + epoch.date().to_ordinal()) - - @classmethod - def is_leap_year(cls, year): - """Indicates whether or not `year` is a leap year. - - :param year: the year to look up - :type year: int - - :rtype: bool - - :raises ValueError: if `year` is out of range: - :attr:`MIN_YEAR` <= year <= :attr:`MAX_YEAR` - """ - if year < MIN_YEAR or year > MAX_YEAR: - raise ValueError("Year out of range (%d..%d)" % (MIN_YEAR, MAX_YEAR)) - return IS_LEAP_YEAR[year] - - @classmethod - def days_in_year(cls, year): - """Return the number of days in `year`. - - :param year: the year to look up - :type year: int - - :rtype: int - - :raises ValueError: if `year` is out of range: - :attr:`MIN_YEAR` <= year <= :attr:`MAX_YEAR` - """ - if year < MIN_YEAR or year > MAX_YEAR: - raise ValueError("Year out of range (%d..%d)" % (MIN_YEAR, MAX_YEAR)) - return DAYS_IN_YEAR[year] - - @classmethod - def days_in_month(cls, year, month): - """Return the number of days in `month` of `year`. - - :param year: the year to look up - :type year: int - :param year: the month to look up - :type year: int - - :rtype: int - - :raises ValueError: if `year` or `month` is out of range: - :attr:`MIN_YEAR` <= year <= :attr:`MAX_YEAR`; - 1 <= year <= 12 - """ - if year < MIN_YEAR or year > MAX_YEAR: - raise ValueError("Year out of range (%d..%d)" % (MIN_YEAR, MAX_YEAR)) - if month < 1 or month > 12: - raise ValueError("Month out of range (1..12)") - return DAYS_IN_MONTH[(year, month)] - - @classmethod - def __calc_ordinal(cls, year, month, day): - if day < 0: - day = cls.days_in_month(year, month) + int(day) + 1 - # The built-in date class does this faster than a - # long-hand pure Python algorithm could - return date(year, month, day).toordinal() - - # CLASS ATTRIBUTES # - - min = None - """The earliest date value possible.""" - - max = None - """The latest date value possible.""" - - resolution = None - """The minimum resolution supported.""" - - # INSTANCE ATTRIBUTES # - - __ordinal = 0 - - __year = 0 - - __month = 0 - - __day = 0 - - @property - def year(self): - """The year of the date. - - :type: int - """ - return self.__year - - @property - def month(self): - """The month of the date. - - :type: int - """ - return self.__month - - @property - def day(self): - """The day of the date. - - :type: int - """ - if self.__day == 0: - return 0 - if self.__day >= 1: - return self.__day - return self.days_in_month(self.__year, self.__month) + self.__day + 1 - - @property - def year_month_day(self): - """3-tuple of (year, month, day) describing the date. - - :rtype: (int, int, int) - """ - return self.year, self.month, self.day - - @property - def year_week_day(self): - """3-tuple of (year, week_of_year, day_of_week) describing the date. - - `day_of_week` will be 1 for Monday and 7 for Sunday. - - :rtype: (int, int, int) - """ - ordinal = self.__ordinal - year = self.__year - - def day_of_week(o): - return ((o - 1) % 7) + 1 - - def iso_week_1(y): - j4 = Date(y, 1, 4) - return j4 + Duration(days=(1 - day_of_week(j4.to_ordinal()))) - - if ordinal >= Date(year, 12, 29).to_ordinal(): - week1 = iso_week_1(year + 1) - if ordinal < week1.to_ordinal(): - week1 = iso_week_1(year) - else: - year += 1 - else: - week1 = iso_week_1(year) - if ordinal < week1.to_ordinal(): - year -= 1 - week1 = iso_week_1(year) - return (year, int((ordinal - week1.to_ordinal()) / 7 + 1), - day_of_week(ordinal)) - - @property - def year_day(self): - """2-tuple of (year, day_of_the_year) describing the date. - - This is the number of the day relative to the start of the year, - with `1 Jan` corresponding to `1`. - - :rtype: (int, int) - """ - return (self.__year, - self.toordinal() - Date(self.__year, 1, 1).toordinal() + 1) - - # OPERATIONS # - - def __hash__(self): - """""" - return hash(self.toordinal()) - - def __eq__(self, other): - """`==` comparison with :class:`.Date` or :class:`datetime.date`.""" - if isinstance(other, (Date, date)): - return self.toordinal() == other.toordinal() - return False - - def __ne__(self, other): - """`!=` comparison with :class:`.Date` or :class:`datetime.date`.""" - return not self.__eq__(other) - - def __lt__(self, other): - """`<` comparison with :class:`.Date` or :class:`datetime.date`.""" - if isinstance(other, (Date, date)): - return self.toordinal() < other.toordinal() - raise TypeError("'<' not supported between instances of 'Date' and %r" % type(other).__name__) - - def __le__(self, other): - """`<=` comparison with :class:`.Date` or :class:`datetime.date`.""" - if isinstance(other, (Date, date)): - return self.toordinal() <= other.toordinal() - raise TypeError("'<=' not supported between instances of 'Date' and %r" % type(other).__name__) - - def __ge__(self, other): - """`>=` comparison with :class:`.Date` or :class:`datetime.date`.""" - if isinstance(other, (Date, date)): - return self.toordinal() >= other.toordinal() - raise TypeError("'>=' not supported between instances of 'Date' and %r" % type(other).__name__) - - def __gt__(self, other): - """`>` comparison with :class:`.Date` or :class:`datetime.date`.""" - if isinstance(other, (Date, date)): - return self.toordinal() > other.toordinal() - raise TypeError("'>' not supported between instances of 'Date' and %r" % type(other).__name__) - - def __add__(self, other): - """Add a :class:`.Duration`. - - :rtype: Date - - :raises ValueError: if the added duration has a time component. - """ - def add_months(d, months): - years, months = symmetric_divmod(months, 12) - year = d.__year + years - month = d.__month + months - while month > 12: - year += 1 - month -= 12 - while month < 1: - year -= 1 - month += 12 - d.__year = year - d.__month = month - - def add_days(d, days): - assert 1 <= d.__day <= 28 or -28 <= d.__day <= -1 - if d.__day >= 1: - new_days = d.__day + days - if 1 <= new_days <= 27: - d.__day = new_days - return - d0 = Date.from_ordinal(d.__ordinal + days) - d.__year, d.__month, d.__day = d0.__year, d0.__month, d0.__day - - if isinstance(other, Duration): - 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() - # Add days before months as the former sometimes - # requires the current ordinal to be correct. - if other.days: - add_days(new_date, other.days) - if other.months: - add_months(new_date, other.months) - new_date.__ordinal = self.__calc_ordinal(new_date.year, new_date.month, new_date.day) - return new_date - return NotImplemented - - def __sub__(self, other): - """Subtract a :class:`.Date` or :class:`.Duration`. - - :returns: If a :class:`.Date` is subtracted, the time between the two - dates is returned as :class:`.Duration`. If a :class:`.Duration` is - subtracted, a new :class:`.Date` is returned. - :rtype: Date or Duration - - :raises ValueError: if the added duration has a time component. - """ - if isinstance(other, (Date, date)): - return Duration(days=(self.toordinal() - other.toordinal())) - try: - return self.__add__(-other) - except TypeError: - return NotImplemented - - def __copy__(self): - return self.__new(self.__ordinal, self.__year, self.__month, self.__day) - - def __deepcopy__(self, *args, **kwargs): - return self.__copy__() - - # INSTANCE METHODS # - - def replace(self, **kwargs): - """Return a :class:`.Date` with one or more components replaced. - - :Keyword Arguments: - * **year** (`int`): overwrite the year - - default: `self.year` - * **month** (`int`): overwrite the month - - default: `self.month` - * **day** (`int`): overwrite the day - - default: `self.day` - """ - return Date(kwargs.get("year", self.__year), - kwargs.get("month", self.__month), - kwargs.get("day", self.__day)) - - def time_tuple(self): - """Convert the date to :class:`time.struct_time`. - - :rtype: time.struct_time - """ - _, _, day_of_week = self.year_week_day - _, day_of_year = self.year_day - return struct_time((self.year, self.month, self.day, 0, 0, 0, day_of_week - 1, day_of_year, -1)) - - def to_ordinal(self): - """The date's proleptic Gregorian ordinal. - - The corresponding class method for the reverse ordinal-to-date - transformation is :meth:`.Date.from_ordinal`. - - :rtype: int - """ - return self.__ordinal - - def to_clock_time(self, epoch): - """Convert the date to :class:`ClockTime` relative to `epoch`. - - :param epoch: the epoch to which the date is relative - :type epoch: Date - - :rtype: ClockTime - """ - try: - return ClockTime(86400 * (self.to_ordinal() - epoch.to_ordinal())) - except AttributeError: - raise TypeError("Epoch has no ordinal value") - - def to_native(self): - """Convert to a native Python :class:`datetime.date` value. - - :rtype: datetime.date - """ - return date.fromordinal(self.to_ordinal()) - - def weekday(self): - """The day of the week where Monday is 0 and Sunday is 6. - - :rtype: int - """ - return self.year_week_day[2] - 1 - - def iso_weekday(self): - """The day of the week where Monday is 1 and Sunday is 7. - - :rtype: int - """ - return self.year_week_day[2] - - def iso_calendar(self): - """Alias for :attr:`.year_week_day`""" - return self.year_week_day - - def iso_format(self): - """Return the :class:`.Date` as ISO formatted string. - - :rtype: str - """ - if self.__ordinal == 0: - return "0000-00-00" - return "%04d-%02d-%02d" % self.year_month_day - - def __repr__(self): - """""" - if self.__ordinal == 0: - return "neo4j.time.ZeroDate" - return "neo4j.time.Date(%r, %r, %r)" % self.year_month_day - - def __str__(self): - """""" - return self.iso_format() - - def __format__(self, format_spec): - """""" - raise NotImplementedError() - - -Date.min = Date.from_ordinal(1) -Date.max = Date.from_ordinal(3652059) -Date.resolution = Duration(days=1) - -#: A :class:`neo4j.time.Date` instance set to `0000-00-00`. -#: This has an ordinal value of `0`. -ZeroDate = object.__new__(Date) - - -class Time(metaclass=TimeType): - """Time of day. - - The :class:`.Time` class is a nanosecond-precision drop-in replacement for - the standard library :class:`datetime.time` class. - - A high degree of API compatibility with the standard library classes is - provided. - - :class:`neo4j.time.Time` objects introduce the concept of ``ticks``. - This is simply a count of the number of nanoseconds since midnight, - in many ways analogous to the :class:`neo4j.time.Date` ordinal. - `ticks` values are integers, with a minimum value of `0` and a maximum - of `86_399_999_999_999`. - - Local times are represented by :class:`.Time` with no ``tzinfo``. - - :param hour: the hour of the time. Must be in range 0 <= hour < 24. - :type hour: int - :param minute: the minute of the time. Must be in range 0 <= minute < 60. - :type minute: int - :param second: the second of the time. Must be in range 0 <= second < 60. - :type second: int - :param nanosecond: the nanosecond of the time. - Must be in range 0 <= nanosecond < 999999999. - :type nanosecond: int - :param tzinfo: timezone or None to get a local :class:`.Time`. - :type tzinfo: datetime.tzinfo or None - - :raises ValueError: if one of the parameters is out of range. - """ - - # CONSTRUCTOR # - - 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, nanosecond, tzinfo): - instance = object.__new__(cls) - instance.__ticks = int(ticks) - instance.__hour = int(hour) - instance.__minute = int(minute) - instance.__second = int(second) - instance.__nanosecond = int(nanosecond) - instance.__tzinfo = tzinfo - return instance - - def __getattr__(self, name): - """Map standard library attribute names to local attribute names, - for compatibility. - """ - try: - return { - "isoformat": self.iso_format, - "utcoffset": self.utc_offset, - }[name] - except KeyError: - raise AttributeError("Date has no attribute %r" % name) - - # CLASS METHODS # - - @classmethod - def now(cls, tz=None): - """Get the current time. - - :param tz: optional timezone - :type tz: datetime.tzinfo - :rtype: Time - - :raises OverflowError: if the timestamp is out of the range of values - supported by the platform C localtime() function. It’s common for - this to be restricted to years from 1970 through 2038. - """ - if tz is None: - return cls.from_clock_time(Clock().local_time(), UnixEpoch) - else: - return ( - DateTime.utc_now() - .replace(tzinfo=timezone.utc).astimezone(tz) - .timetz() - ) - - @classmethod - def utc_now(cls): - """Get the current time as UTC local time. - - :rtype: Time - """ - return cls.from_clock_time(Clock().utc_time(), UnixEpoch) - - @classmethod - def from_iso_format(cls, s): - """Parse a ISO formatted time string. - - Accepted formats: - Local times: - 'hh' - 'hh:mm' - 'hh:mm:ss' - 'hh:mm:ss.ssss...' - Times with timezones (UTC offset): - '+hh:mm' - '+hh:mm:ss' - '+hh:mm:ss.ssss....' - '-hh:mm' - '-hh:mm:ss' - '-hh:mm:ss.ssss....' - - Where the UTC offset will only respect hours and minutes. - Seconds and sub-seconds are ignored. - - :param s: String to parse - :type s: str - - :rtype: Time - - :raises ValueError: if the string does not match the required format. - """ - from pytz import FixedOffset - m = TIME_ISO_PATTERN.match(s) - if m: - hour = int(m.group(1)) - minute = int(m.group(3) or 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, nanosecond) - else: - offset_multiplier = 1 if m.group(9) == "+" else -1 - offset_hour = int(m.group(10)) - offset_minute = int(m.group(11)) - # pytz only supports offsets of minute resolution - # 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, nanosecond, - tzinfo=FixedOffset(offset_multiplier * offset)) - raise ValueError("Time string is not in ISO format") - - @classmethod - def from_ticks(cls, ticks, tz=None): - """Create a time from ticks (nanoseconds since midnight). - - :param ticks: nanoseconds since midnight - :type ticks: int - :param tz: optional timezone - :type tz: datetime.tzinfo - - :rtype: Time - - :raises ValueError: if ticks is out of bounds - (0 <= ticks < 86400000000000) - """ - 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 :class:`datetime.time` value. - - :param t: time to convert from - :type t: datetime.time - - :rtype: Time - """ - 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 :class:`.ClockTime` relative to a given epoch. - - This method, in contrast to most others of this package, assumes days of - exactly 24 hours. - - :param clock_time: the clock time as :class:`.ClockTime` or as tuple of - (seconds, nanoseconds) - :type clock_time: ClockTime or (float, int) - :param epoch: the epoch to which `clock_time` is relative - :type epoch: DateTime - - :rtype: Time - """ - clock_time = ClockTime(*clock_time) - ts = clock_time.seconds % 86400 - nanoseconds = int(NANO_SECONDS * ts + clock_time.nanoseconds) - ticks = (epoch.time().ticks + nanoseconds) % (86400 * NANO_SECONDS) - return Time.from_ticks(ticks) - - @classmethod - def __normalize_hour(cls, hour): - hour = int(hour) - if 0 <= hour < 24: - 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, 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, second - raise ValueError("Second out of range (0..59)") - - @classmethod - def __normalize_nanosecond(cls, hour, minute, second, nanosecond): - hour, minute, second = cls.__normalize_second(hour, minute, second) - if 0 <= nanosecond < NANO_SECONDS: - return hour, minute, second, nanosecond - raise ValueError("Nanosecond out of range (0..%s)" % (NANO_SECONDS - 1)) - - # CLASS ATTRIBUTES # - - min = None - """The earliest time value possible.""" - - max = None - """The latest time value possible.""" - - resolution = None - """The minimum resolution supported.""" - - # INSTANCE ATTRIBUTES # - - __ticks = 0 - - __hour = 0 - - __minute = 0 - - __second = 0 - - __nanosecond = 0 - - __tzinfo = None - - @property - def ticks(self): - """The total number of nanoseconds since midnight. - - :type: int - """ - return self.__ticks - - @property - def hour(self): - """The hours of the time. - - :type: int - """ - return self.__hour - - @property - def minute(self): - """The minutes of the time. - - :type: int - """ - return self.__minute - - @property - def second(self): - """The seconds of the time. - - :type: int - """ - return self.__second - - @property - def nanosecond(self): - """The nanoseconds of the time. - - :type: int - """ - return self.__nanosecond - - @property - def hour_minute_second_nanosecond(self): - """The time as a tuple of (hour, minute, second, nanosecond). - - :type: (int, int, int, int)""" - return self.__hour, self.__minute, self.__second, self.__nanosecond - - @property - def tzinfo(self): - """The timezone of this time. - - :type: datetime.tzinfo or None""" - return self.__tzinfo - - # OPERATIONS # - - def _get_both_normalized_ticks(self, other, strict=True): - if (isinstance(other, (time, Time)) - and ((self.utc_offset() is None) - ^ (other.utcoffset() is None))): - if strict: - raise TypeError("can't compare offset-naive and offset-aware " - "times") - else: - return None, None - if isinstance(other, Time): - other_ticks = other.__ticks - elif isinstance(other, time): - other_ticks = int(3600000000000 * other.hour - + 60000000000 * other.minute - + NANO_SECONDS * other.second - + 1000 * other.microsecond) - else: - return None, None - utc_offset = other.utcoffset() - if utc_offset is not None: - other_ticks -= utc_offset.total_seconds() * NANO_SECONDS - self_ticks = self.__ticks - utc_offset = self.utc_offset() - if utc_offset is not None: - self_ticks -= utc_offset.total_seconds() * NANO_SECONDS - return self_ticks, other_ticks - - def __hash__(self): - """""" - if self.__nanosecond % 1000 == 0: - return hash(self.to_native()) - self_ticks = self.__ticks - if self.utc_offset() is not None: - self_ticks -= self.utc_offset().total_seconds() * NANO_SECONDS - return hash(self_ticks) - - def __eq__(self, other): - """`==` comparison with :class:`.Time` or :class:`datetime.time`.""" - self_ticks, other_ticks = self._get_both_normalized_ticks(other, - strict=False) - if self_ticks is None: - return False - return self_ticks == other_ticks - - def __ne__(self, other): - """`!=` comparison with :class:`.Time` or :class:`datetime.time`.""" - return not self.__eq__(other) - - def __lt__(self, other): - """`<` comparison with :class:`.Time` or :class:`datetime.time`.""" - self_ticks, other_ticks = self._get_both_normalized_ticks(other) - if self_ticks is None: - return NotImplemented - return self_ticks < other_ticks - - def __le__(self, other): - """`<=` comparison with :class:`.Time` or :class:`datetime.time`.""" - self_ticks, other_ticks = self._get_both_normalized_ticks(other) - if self_ticks is None: - return NotImplemented - return self_ticks <= other_ticks - - def __ge__(self, other): - """`>=` comparison with :class:`.Time` or :class:`datetime.time`.""" - self_ticks, other_ticks = self._get_both_normalized_ticks(other) - if self_ticks is None: - return NotImplemented - return self_ticks >= other_ticks - - def __gt__(self, other): - """`>` comparison with :class:`.Time` or :class:`datetime.time`.""" - self_ticks, other_ticks = self._get_both_normalized_ticks(other) - if self_ticks is None: - return NotImplemented - return self_ticks > other_ticks - - def __copy__(self): - return self.__new(self.__ticks, self.__hour, self.__minute, - self.__second, self.__nanosecond, self.__tzinfo) - - def __deepcopy__(self, *args, **kwargs): - return self.__copy__() - - # INSTANCE METHODS # - - def replace(self, **kwargs): - """Return a :class:`.Time` with one or more components replaced. - - :Keyword Arguments: - * **hour** (`int`): overwrite the hour - - default: `self.hour` - * **minute** (`int`): overwrite the minute - - default: `self.minute` - * **second** (`int`): overwrite the second - - default: `int(self.second)` - * **nanosecond** (`int`): overwrite the nanosecond - - default: `self.nanosecond` - * **tzinfo** (`datetime.tzinfo` or `None`): overwrite the timezone - - default: `self.tzinfo` - - :rtype: Time - """ - 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, dt=None): - if self.tzinfo is None: - return None - try: - value = self.tzinfo.utcoffset(dt) - except TypeError: - # For timezone implementations not compatible with the custom - # datetime implementations, we can't do better than this. - value = self.tzinfo.utcoffset(dt.to_native()) - if value is None: - return None - if isinstance(value, timedelta): - s = value.total_seconds() - if not (-86400 < s < 86400): - raise ValueError("utcoffset must be less than a day") - if s % 60 != 0 or value.microseconds != 0: - raise ValueError("utcoffset must be a whole number of minutes") - return value - raise TypeError("utcoffset must be a timedelta") - - def utc_offset(self): - """Return the UTC offset of this time. - - :return: None if this is a local time (:attr:`.tzinfo` is None), else - returns `self.tzinfo.utcoffset(self)`. - :rtype: datetime.timedelta - - :raises ValueError: if `self.tzinfo.utcoffset(self)` is not None and a - :class:`timedelta` with a magnitude greater equal 1 day or that is - not a whole number of minutes. - :raises TypeError: if `self.tzinfo.utcoffset(self)` does return anything but - None or a :class:`datetime.timedelta`. - """ - return self._utc_offset() - - def dst(self): - """Get the daylight saving time adjustment (DST). - - :return: None if this is a local time (:attr:`.tzinfo` is None), else - returns `self.tzinfo.dst(self)`. - :rtype: datetime.timedelta - - :raises ValueError: if `self.tzinfo.dst(self)` is not None and a - :class:`timedelta` with a magnitude greater equal 1 day or that is - not a whole number of minutes. - :raises TypeError: if `self.tzinfo.dst(self)` does return anything but - None or a :class:`datetime.timedelta`. - """ - if self.tzinfo is None: - return None - try: - value = self.tzinfo.dst(self) - except TypeError: - # For timezone implementations not compatible with the custom - # datetime implementations, we can't do better than this. - value = self.tzinfo.dst(self.to_native()) - if value is None: - return None - if isinstance(value, timedelta): - if value.days != 0: - raise ValueError("dst must be less than a day") - if value.seconds % 60 != 0 or value.microseconds != 0: - raise ValueError("dst must be a whole number of minutes") - return value - raise TypeError("dst must be a timedelta") - - def tzname(self): - """Get the name of the :class:`.Time`'s timezone. - - :returns: None if the time is local (i.e., has no timezone), else return - `self.tzinfo.tzname(self)` - - :rtype: str or None - """ - if self.tzinfo is None: - return None - try: - return self.tzinfo.tzname(self) - except TypeError: - # For timezone implementations not compatible with the custom - # datetime implementations, we can't do better than this. - return self.tzinfo.tzname(self.to_native()) - - def to_clock_time(self): - """Convert to :class:`.ClockTime`. - - :rtype: ClockTime - """ - seconds, nanoseconds = divmod(self.ticks, NANO_SECONDS) - return ClockTime(seconds, nanoseconds) - - def to_native(self): - """Convert to a native Python `datetime.time` value. - - This conversion is lossy as the native time implementation only supports - a resolution of microseconds instead of nanoseconds. - - :rtype: datetime.time - """ - h, m, s, ns = self.hour_minute_second_nanosecond - µs = round_half_to_even(ns / 1000) - tz = self.tzinfo - return time(h, m, s, µs, tz) - - def iso_format(self): - """Return the :class:`.Time` as ISO formatted string. - - :rtype: str - """ - s = "%02d:%02d:%02d.%09d" % self.hour_minute_second_nanosecond - offset = self.utc_offset() - if offset is not None: - s += "%+03d:%02d" % divmod(offset.total_seconds() // 60, 60) - return s - - def __repr__(self): - """""" - if self.tzinfo is None: - return "neo4j.time.Time(%r, %r, %r, %r)" % \ - self.hour_minute_second_nanosecond - else: - return "neo4j.time.Time(%r, %r, %r, %r, tzinfo=%r)" % \ - (self.hour_minute_second_nanosecond + (self.tzinfo,)) - - def __str__(self): - """""" - return self.iso_format() - - def __format__(self, format_spec): - """""" - raise NotImplementedError() - - -Time.min = Time(hour=0, minute=0, second=0, nanosecond=0) -Time.max = Time(hour=23, minute=59, second=59, nanosecond=999999999) -Time.resolution = Duration(nanoseconds=1) - -#: A :class:`.Time` instance set to `00:00:00`. -#: This has a :attr:`.ticks` value of `0`. -Midnight = Time.min - -#: A :class:`.Time` instance set to `12:00:00`. -#: This has a :attr:`.ticks` value of `43200000000000`. -Midday = Time(hour=12) - - -@total_ordering -class DateTime(metaclass=DateTimeType): - """A point in time represented as a date and a time. - - The :class:`.DateTime` class is a nanosecond-precision drop-in replacement - for the standard library :class:`datetime.datetime` class. - - As such, it contains both :class:`.Date` and :class:`.Time` information and - draws functionality from those individual classes. - - A :class:`.DateTime` object is fully compatible with the Python time zone - library `pytz `_. Functions such as - `normalize` and `localize` can be used in the same way as they are with the - standard library classes. - - Regular construction of a :class:`.DateTime` object requires at - least the `year`, `month` and `day` arguments to be supplied. The - optional `hour`, `minute` and `second` arguments default to zero and - `tzinfo` defaults to :const:`None`. - - `year`, `month`, and `day` are passed to the constructor of :class:`.Date`. - `hour`, `minute`, `second`, `nanosecond`, and `tzinfo` are passed to the - constructor of :class:`.Time`. See their documentation for more details. - - >>> dt = DateTime(2018, 4, 30, 12, 34, 56, 789123456); dt - neo4j.time.DateTime(2018, 4, 30, 12, 34, 56, 789123456) - >>> dt.second - 56.789123456 - """ - - # CONSTRUCTOR # - - 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, - for compatibility. - """ - try: - return { - "astimezone": self.as_timezone, - "isocalendar": self.iso_calendar, - "isoformat": self.iso_format, - "isoweekday": self.iso_weekday, - "strftime": self.__format__, - "toordinal": self.to_ordinal, - "timetuple": self.time_tuple, - "utcoffset": self.utc_offset, - "utctimetuple": self.utc_time_tuple, - }[name] - except KeyError: - raise AttributeError("DateTime has no attribute %r" % name) - - # CLASS METHODS # - - @classmethod - def now(cls, tz=None): - """Get the current date and time. - - :param tz: timezone. Set to None to create a local :class:`.DateTime`. - :type tz: datetime.tzinfo` or None - - :rtype: DateTime - - :raises OverflowError: if the timestamp is out of the range of values - supported by the platform C localtime() function. It’s common for - this to be restricted to years from 1970 through 2038. - """ - if tz is None: - return cls.from_clock_time(Clock().local_time(), UnixEpoch) - else: - try: - return tz.fromutc(cls.from_clock_time( - Clock().utc_time(), UnixEpoch - ).replace(tzinfo=tz)) - except TypeError: - # For timezone implementations not compatible with the custom - # datetime implementations, we can't do better than this. - utc_now = cls.from_clock_time( - Clock().utc_time(), UnixEpoch - ) - utc_now_native = utc_now.to_native() - now_native = tz.fromutc(utc_now_native) - now = cls.from_native(now_native) - return now.replace( - nanosecond=(now.nanosecond - + utc_now.nanosecond - - utc_now_native.microsecond * 1000) - ) - - @classmethod - def utc_now(cls): - """Get the current date and time in UTC - - :rtype: DateTime - """ - return cls.from_clock_time(Clock().utc_time(), UnixEpoch) - - @classmethod - def from_iso_format(cls, s): - """Parse a ISO formatted date with time string. - - :param s: String to parse - :type s: str - - :rtype: Time - - :raises ValueError: if the string does not match the ISO format. - """ - try: - return cls.combine(Date.from_iso_format(s[0:10]), - Time.from_iso_format(s[11:])) - except ValueError: - raise ValueError("DateTime string is not in ISO format") - - @classmethod - def from_timestamp(cls, timestamp, tz=None): - """:class:`.DateTime` from a time stamp (seconds since unix epoch). - - :param timestamp: the unix timestamp (seconds since unix epoch). - :type timestamp: float - :param tz: timezone. Set to None to create a local :class:`.DateTime`. - :type tz: datetime.tzinfo or None - - :rtype: DateTime - - :raises OverflowError: if the timestamp is out of the range of values - supported by the platform C localtime() function. It’s common for - this to be restricted to years from 1970 through 2038. - """ - if tz is None: - return cls.from_clock_time( - ClockTime(timestamp) + Clock().local_offset(), UnixEpoch - ) - else: - return ( - cls.utc_from_timestamp(timestamp) - .replace(tzinfo=timezone.utc).astimezone(tz) - ) - - @classmethod - def utc_from_timestamp(cls, timestamp): - """:class:`.DateTime` from a time stamp (seconds since unix epoch). - - Returns the `DateTime` as local date `DateTime` in UTC. - - :rtype: DateTime - """ - return cls.from_clock_time((timestamp, 0), UnixEpoch) - - @classmethod - def from_ordinal(cls, ordinal): - """:class:`.DateTime` from an ordinal. - - For more info about ordinals see :meth:`.Date.from_ordinal`. - - :rtype: DateTime - """ - return cls.combine(Date.from_ordinal(ordinal), Midnight) - - @classmethod - def combine(cls, date, time): - """Combine a :class:`.Date` and a :class:`.Time` to a :class:`DateTime`. - - :param date: the date - :type date: Date - :param time: the time - :type time: Time - - :rtype: DateTime - - :raises AssertionError: if the parameter types don't match. - """ - assert isinstance(date, Date) - assert isinstance(time, Time) - instance = object.__new__(cls) - instance.__date = date - instance.__time = time - return instance - - @classmethod - def parse(cls, date_string, format): - raise NotImplementedError() - - @classmethod - def from_native(cls, dt): - """Convert from a native Python :class:`datetime.datetime` value. - - :param dt: the datetime to convert - :type dt: datetime.datetime - - :rtype: DateTime - """ - return cls.combine(Date.from_native(dt.date()), Time.from_native(dt.timetz())) - - @classmethod - def from_clock_time(cls, clock_time, epoch): - """Convert from a :class:`ClockTime` relative to a given epoch. - - :param clock_time: the clock time as :class:`.ClockTime` or as tuple of - (seconds, nanoseconds) - :type clock_time: ClockTime or (float, int) - :param epoch: the epoch to which `clock_time` is relative - :type epoch: DateTime - - :rtype: DateTime - - :raises ValueError: if `clock_time` is invalid. - """ - try: - seconds, nanoseconds = ClockTime(*clock_time) - except (TypeError, ValueError): - raise ValueError("Clock time must be a 2-tuple of (s, ns)") - else: - ordinal, seconds = divmod(seconds, 86400) - ticks = epoch.time().ticks + seconds * NANO_SECONDS + nanoseconds - days, ticks = divmod(ticks, 86400 * NANO_SECONDS) - ordinal += days - date_ = Date.from_ordinal(ordinal + epoch.date().to_ordinal()) - time_ = Time.from_ticks(ticks) - return cls.combine(date_, time_) - - # CLASS ATTRIBUTES # - - min = None - """The earliest date time value possible.""" - - max = None - """The latest date time value possible.""" - - resolution = None - """The minimum resolution supported.""" - - # INSTANCE ATTRIBUTES # - - @property - def year(self): - """The year of the :class:`.DateTime`. - - See :attr:`.Date.year`. - """ - return self.__date.year - - @property - def month(self): - """The year of the :class:`.DateTime`. - - See :attr:`.Date.year`.""" - return self.__date.month - - @property - def day(self): - """The day of the :class:`.DateTime`'s date. - - See :attr:`.Date.day`.""" - return self.__date.day - - @property - def year_month_day(self): - """The year_month_day of the :class:`.DateTime`'s date. - - See :attr:`.Date.year_month_day`.""" - return self.__date.year_month_day - - @property - def year_week_day(self): - """The year_week_day of the :class:`.DateTime`'s date. - - See :attr:`.Date.year_week_day`.""" - return self.__date.year_week_day - - @property - def year_day(self): - """The year_day of the :class:`.DateTime`'s date. - - See :attr:`.Date.year_day`.""" - return self.__date.year_day - - @property - def hour(self): - """The hour of the :class:`.DateTime`'s time. - - See :attr:`.Time.hour`.""" - return self.__time.hour - - @property - def minute(self): - """The minute of the :class:`.DateTime`'s time. - - See :attr:`.Time.minute`.""" - return self.__time.minute - - @property - def second(self): - """The second of the :class:`.DateTime`'s time. - - See :attr:`.Time.second`.""" - return self.__time.second - - @property - def nanosecond(self): - """The nanosecond of the :class:`.DateTime`'s time. - - See :attr:`.Time.nanosecond`.""" - return self.__time.nanosecond - - @property - def tzinfo(self): - """The tzinfo of the :class:`.DateTime`'s time. - - See :attr:`.Time.tzinfo`.""" - return self.__time.tzinfo - - @property - def hour_minute_second_nanosecond(self): - """The hour_minute_second_nanosecond of the :class:`.DateTime`'s time. - - See :attr:`.Time.hour_minute_second_nanosecond`.""" - return self.__time.hour_minute_second_nanosecond - - # OPERATIONS # - - def _get_both_normalized(self, other, strict=True): - if (isinstance(other, (datetime, DateTime)) - and ((self.utc_offset() is None) - ^ (other.utcoffset() is None))): - if strict: - raise TypeError("can't compare offset-naive and offset-aware " - "datetimes") - else: - return None, None - self_norm = self - utc_offset = self.utc_offset() - if utc_offset is not None: - self_norm -= utc_offset - self_norm = self_norm.replace(tzinfo=None) - other_norm = other - if isinstance(other, (datetime, DateTime)): - utc_offset = other.utcoffset() - if utc_offset is not None: - other_norm -= utc_offset - other_norm = other_norm.replace(tzinfo=None) - else: - return None, None - return self_norm, other_norm - - def __hash__(self): - """""" - if self.nanosecond % 1000 == 0: - return hash(self.to_native()) - self_norm = self - utc_offset = self.utc_offset() - if utc_offset is not None: - self_norm -= utc_offset - return hash(self_norm.date()) ^ hash(self_norm.time()) - - def __eq__(self, other): - """ - `==` comparison with :class:`.DateTime` or :class:`datetime.datetime`. - """ - if not isinstance(other, (datetime, DateTime)): - return NotImplemented - if self.utc_offset() == other.utcoffset(): - return self.date() == other.date() and self.time() == other.time() - self_norm, other_norm = self._get_both_normalized(other, strict=False) - if self_norm is None: - return False - return self_norm == other_norm - - def __ne__(self, other): - """ - `!=` comparison with :class:`.DateTime` or :class:`datetime.datetime`. - """ - return not self.__eq__(other) - - def __lt__(self, other): - """ - `<` comparison with :class:`.DateTime` or :class:`datetime.datetime`. - """ - if not isinstance(other, (datetime, DateTime)): - return NotImplemented - if self.utc_offset() == other.utcoffset(): - if self.date() == other.date(): - return self.time() < other.time() - return self.date() < other.date() - self_norm, other_norm = self._get_both_normalized(other) - return (self_norm.date() < other_norm.date() - or self_norm.time() < other_norm.time()) - - def __le__(self, other): - """ - `<=` comparison with :class:`.DateTime` or :class:`datetime.datetime`. - """ - if not isinstance(other, (datetime, DateTime)): - return NotImplemented - if self.utc_offset() == other.utcoffset(): - if self.date() == other.date(): - return self.time() <= other.time() - return self.date() <= other.date() - self_norm, other_norm = self._get_both_normalized(other) - return self_norm <= other_norm - - def __ge__(self, other): - """ - `>=` comparison with :class:`.DateTime` or :class:`datetime.datetime`. - """ - if not isinstance(other, (datetime, DateTime)): - return NotImplemented - if self.utc_offset() == other.utcoffset(): - if self.date() == other.date(): - return self.time() >= other.time() - return self.date() >= other.date() - self_norm, other_norm = self._get_both_normalized(other) - return self_norm >= other_norm - - def __gt__(self, other): - """ - `>` comparison with :class:`.DateTime` or :class:`datetime.datetime`. - """ - if not isinstance(other, (datetime, DateTime)): - return NotImplemented - if self.utc_offset() == other.utcoffset(): - if self.date() == other.date(): - return self.time() > other.time() - return self.date() > other.date() - self_norm, other_norm = self._get_both_normalized(other) - return (self_norm.date() > other_norm.date() - or self_norm.time() > other_norm.time()) - - def __add__(self, other): - """Add a :class:`datetime.timedelta`. - - :rtype: DateTime - """ - if isinstance(other, timedelta): - 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(round_half_to_even( - seconds * NANO_SECONDS + t.nanoseconds - )) - return self.combine(date_, time_).replace(tzinfo=self.tzinfo) - if isinstance(other, Duration): - t = (self.to_clock_time() - + ClockTime(other.seconds, other.nanoseconds)) - days, seconds = symmetric_divmod(t.seconds, 86400) - date_ = self.date() + Duration(months=other.months, - days=days + other.days) - time_ = Time.from_ticks(seconds * NANO_SECONDS + t.nanoseconds) - return self.combine(date_, time_).replace(tzinfo=self.tzinfo) - return NotImplemented - - def __sub__(self, other): - """Subtract a datetime or a timedelta. - - Supported :class:`.DateTime` (returns :class:`.Duration`), - :class:`datetime.datetime` (returns :class:`datetime.timedelta`), and - :class:`datetime.timedelta` (returns :class:`.DateTime`). - - :rtype: Duration or datetime.timedelta or DateTime - """ - if isinstance(other, DateTime): - self_month_ordinal = 12 * (self.year - 1) + self.month - other_month_ordinal = 12 * (other.year - 1) + other.month - months = self_month_ordinal - other_month_ordinal - days = self.day - other.day - t = self.time().to_clock_time() - other.time().to_clock_time() - return Duration(months=months, days=days, seconds=t.seconds, - nanoseconds=t.nanoseconds) - if isinstance(other, datetime): - days = self.to_ordinal() - other.toordinal() - t = (self.time().to_clock_time() - - ClockTime( - 3600 * other.hour + 60 * other.minute + other.second, - other.microsecond * 1000 - )) - return timedelta(days=days, seconds=t.seconds, - microseconds=(t.nanoseconds // 1000)) - if isinstance(other, Duration): - return self.__add__(-other) - if isinstance(other, timedelta): - return self.__add__(-other) - return NotImplemented - - def __copy__(self): - return self.combine(self.__date, self.__time) - - def __deepcopy__(self, *args, **kwargs): - return self.__copy__() - - # INSTANCE METHODS # - - def date(self): - """The date - - :rtype: Date - """ - return self.__date - - def time(self): - """The time without timezone info - - :rtype: Time - """ - return self.__time.replace(tzinfo=None) - - def timetz(self): - """The time with timezone info - - :rtype: Time - """ - return self.__time - - def replace(self, **kwargs): - """Return a :class:`.DateTime` with one or more components replaced. - - See :meth:`.Date.replace` and :meth:`.Time.replace` for available - arguments. - - :rtype: DateTime - """ - date_ = self.__date.replace(**kwargs) - time_ = self.__time.replace(**kwargs) - return self.combine(date_, time_) - - def as_timezone(self, tz): - """Convert this :class:`.DateTime` to another timezone. - - :param tz: the new timezone - :type tz: datetime.tzinfo or None - - :return: the same object if `tz` is None. Else, a new :class:`.DateTime` - that's the same point in time but in a different timezone. - :rtype: DateTime - """ - if self.tzinfo is None: - return self - utc = (self - self.utc_offset()).replace(tzinfo=tz) - try: - return tz.fromutc(utc) - except TypeError: - # For timezone implementations not compatible with the custom - # datetime implementations, we can't do better than this. - native_utc = utc.to_native() - native_res = tz.fromutc(native_utc) - res = self.from_native(native_res) - return res.replace( - nanosecond=(native_res.microsecond * 1000 - + self.nanosecond % 1000) - ) - - def utc_offset(self): - """Get the date times utc offset. - - See :meth:`.Time.utc_offset`. - """ - - return self.__time._utc_offset(self) - - def dst(self): - """Get the daylight saving time adjustment (DST). - - See :meth:`.Time.dst`. - """ - return self.__time.dst() - - def tzname(self): - """Get the timezone name. - - See :meth:`.Time.tzname`. - """ - return self.__time.tzname() - - def time_tuple(self): - raise NotImplementedError() - - def utc_time_tuple(self): - raise NotImplementedError() - - def to_ordinal(self): - """Get the ordinal of the :class:`.DateTime`'s date. - - See :meth:`.Date.to_ordinal` - """ - return self.__date.to_ordinal() - - def to_clock_time(self): - """Convert to :class:`.ClockTime`. - - :rtype: ClockTime - """ - total_seconds = 0 - for year in range(1, self.year): - total_seconds += 86400 * DAYS_IN_YEAR[year] - for month in range(1, self.month): - total_seconds += 86400 * Date.days_in_month(self.year, month) - total_seconds += 86400 * (self.day - 1) - seconds, nanoseconds = divmod(self.__time.ticks, NANO_SECONDS) - return ClockTime(total_seconds + seconds, nanoseconds) - - def to_native(self): - """Convert to a native Python :class:`datetime.datetime` value. - - This conversion is lossy as the native time implementation only supports - a resolution of microseconds instead of nanoseconds. - - :rtype: datetime.datetime - """ - y, mo, d = self.year_month_day - h, m, s, ns = self.hour_minute_second_nanosecond - ms = int(ns / 1000) - tz = self.tzinfo - return datetime(y, mo, d, h, m, s, ms, tz) - - def weekday(self): - """Get the weekday. - - See :meth:`.Date.weekday` - """ - return self.__date.weekday() - - def iso_weekday(self): - """Get the ISO weekday. - - See :meth:`.Date.iso_weekday` - """ - return self.__date.iso_weekday() - - def iso_calendar(self): - """Get date as ISO tuple. - - See :meth:`.Date.iso_calendar` - """ - return self.__date.iso_calendar() - - def iso_format(self, sep="T"): - """Return the :class:`.DateTime` as ISO formatted string. - - This method joins `self.date().iso_format()` (see - :meth:`.Date.iso_format`) and `self.timetz().iso_format()` (see - :meth:`.Time.iso_format`) with `sep` in between. - - :param sep: the separator between the formatted date and time. - :type sep: str - - :rtype: str - """ - s = "%s%s%s" % (self.date().iso_format(), sep, - self.timetz().iso_format()) - time_tz = self.timetz() - offset = time_tz.utc_offset() - if offset is not None: - # the time component will have taken care of formatting the offset - return s - offset = self.utc_offset() - if offset is not None: - s += "%+03d:%02d" % divmod(offset.total_seconds() // 60, 60) - return s - - def __repr__(self): - """""" - if self.tzinfo is None: - fields = (*self.year_month_day, - *self.hour_minute_second_nanosecond) - return "neo4j.time.DateTime(%r, %r, %r, %r, %r, %r, %r)" % fields - else: - fields = (*self.year_month_day, - *self.hour_minute_second_nanosecond, self.tzinfo) - return ("neo4j.time.DateTime(%r, %r, %r, %r, %r, %r, %r, tzinfo=%r)" - % fields) - - def __str__(self): - """""" - return self.iso_format() - - def __format__(self, format_spec): - """""" - raise NotImplementedError() - - -DateTime.min = DateTime.combine(Date.min, Time.min) -DateTime.max = DateTime.combine(Date.max, Time.max) -DateTime.resolution = Time.resolution - -#: A :class:`.DateTime` instance set to `0000-00-00T00:00:00`. -#: This has a :class:`.Date` component equal to :attr:`ZeroDate` and a -Never = DateTime.combine(ZeroDate, Midnight) - -#: A :class:`.DateTime` instance set to `1970-01-01T00:00:00`. -UnixEpoch = DateTime(1970, 1, 1, 0, 0, 0) From 1de91252a79e7fd003d2c2b79a43aa3ab522e056 Mon Sep 17 00:00:00 2001 From: Prychantovskyi <75383953+Prychantovskyi@users.noreply.github.com> Date: Fri, 22 Jul 2022 09:51:22 +0100 Subject: [PATCH 03/19] Delete test_clock.py --- test_clock.py | 72 --------------------------------------------------- 1 file changed, 72 deletions(-) delete mode 100644 test_clock.py diff --git a/test_clock.py b/test_clock.py deleted file mode 100644 index 43b957548..000000000 --- a/test_clock.py +++ /dev/null @@ -1,72 +0,0 @@ -# Copyright (c) "Neo4j" -# Neo4j Sweden AB [https://neo4j.com] -# -# This file is part of Neo4j. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - - -import pytest - -from neo4j.time._clock_implementations import ( - Clock, - ClockTime, -) - - -class TestClock: - - def test_no_clock_implementations(self): - try: - Clock._Clock__implementations = [] - with pytest.raises(RuntimeError): - _ = Clock() - finally: - Clock._Clock__implementations = None - - def test_base_clock_precision(self): - clock = object.__new__(Clock) - with pytest.raises(NotImplementedError): - _ = clock.precision() - - def test_base_clock_available(self): - clock = object.__new__(Clock) - with pytest.raises(NotImplementedError): - _ = clock.available() - - def test_base_clock_utc_time(self): - clock = object.__new__(Clock) - with pytest.raises(NotImplementedError): - _ = clock.utc_time() - - def test_local_offset(self): - clock = object.__new__(Clock) - offset = clock.local_offset() - assert isinstance(offset, ClockTime) - - def test_local_time(self): - _ = Clock() - for impl in Clock._Clock__implementations: - assert issubclass(impl, Clock) - clock = object.__new__(impl) - time = clock.local_time() - assert isinstance(time, ClockTime) - - def test_utc_time(self): - _ = Clock() - for impl in Clock._Clock__implementations: - assert issubclass(impl, Clock) - clock = object.__new__(impl) - time = clock.utc_time() - assert isinstance(time, ClockTime) From fcb4c32f0a5df5aab2fac992cb65983f984d653d Mon Sep 17 00:00:00 2001 From: Prychantovskyi <75383953+Prychantovskyi@users.noreply.github.com> Date: Fri, 22 Jul 2022 09:51:31 +0100 Subject: [PATCH 04/19] Delete test_wgs84_point.py --- test_wgs84_point.py | 62 --------------------------------------------- 1 file changed, 62 deletions(-) delete mode 100644 test_wgs84_point.py diff --git a/test_wgs84_point.py b/test_wgs84_point.py deleted file mode 100644 index aad72ab6c..000000000 --- a/test_wgs84_point.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright (c) "Neo4j" -# Neo4j Sweden AB [https://neo4j.com] -# -# This file is part of Neo4j. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import pytest - -from neo4j.spatial import WGS84Point - - -class WGS84PointTestCase: - - def test_alias_3d(self): - x, y, z = 3.2, 4.0, -1.2 - p = WGS84Point((x, y, z)) - - assert hasattr(p, "longitude") - assert p.longitude == x - assert hasattr(p, "x") - assert p.x == x - - assert hasattr(p, "latitude") - assert p.latitude == y - assert hasattr(p, "y") - assert p.y == y - - assert hasattr(p, "height") - assert p.height == z - assert hasattr(p, "z") - assert p.z == z - - def test_alias_2d(self): - x, y = 3.2, 4.0 - p = WGS84Point((x, y)) - - assert hasattr(p, "longitude") - assert p.longitude == x - assert hasattr(p, "x") - assert p.x == x - - assert hasattr(p, "latitude") - assert p.latitude == y - assert hasattr(p, "y") - assert p.y == y - - with pytest.raises(AttributeError): - p.height - with pytest.raises(AttributeError): - p.z From faa5821caebbdb8f459a7792e3ea8427071510e6 Mon Sep 17 00:00:00 2001 From: Prychantovskyi <75383953+Prychantovskyi@users.noreply.github.com> Date: Fri, 22 Jul 2022 09:51:40 +0100 Subject: [PATCH 05/19] Delete test_point.py --- test_point.py | 52 --------------------------------------------------- 1 file changed, 52 deletions(-) delete mode 100644 test_point.py diff --git a/test_point.py b/test_point.py deleted file mode 100644 index 0b46d25dc..000000000 --- a/test_point.py +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright (c) "Neo4j" -# Neo4j Sweden AB [https://neo4j.com] -# -# This file is part of Neo4j. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import pytest - -from neo4j._spatial import ( - Point, - point_type, -) - - -class PointTestCase: - - def test_wrong_type_arguments(self): - for argument in (("a", "b"), ({"x": 1.0, "y": 2.0})): - with pytest.subtests(): - with pytest.raises(ValueError): - Point(argument) - - def test_number_arguments(self): - for argument in ((1, 2), (1.2, 2.1)): - with pytest.subtests(): - p = Point(argument) - assert tuple(p) == argument - - def test_immutable_coordinates(self): - MyPoint = point_type("MyPoint", ["x", "y"], {2: 1234}) - coordinates = (.1, 0) - p = MyPoint(coordinates) - with pytest.raises(AttributeError): - p.x = 2.0 - with pytest.raises(AttributeError): - p.y = 2.0 - with pytest.raises(TypeError): - p[0] = 2.0 - with pytest.raises(TypeError): - p[1] = 2.0 From 2b5dd18698e3001481ac83c72d0217d1ce26159a Mon Sep 17 00:00:00 2001 From: Prychantovskyi <75383953+Prychantovskyi@users.noreply.github.com> Date: Fri, 22 Jul 2022 09:51:48 +0100 Subject: [PATCH 06/19] Delete test_date.py --- test_date.py | 562 --------------------------------------------------- 1 file changed, 562 deletions(-) delete mode 100644 test_date.py diff --git a/test_date.py b/test_date.py deleted file mode 100644 index 5cb496767..000000000 --- a/test_date.py +++ /dev/null @@ -1,562 +0,0 @@ -# Copyright (c) "Neo4j" -# Neo4j Sweden AB [https://neo4j.com] -# -# This file is part of Neo4j. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import copy -import datetime -from datetime import date -from time import struct_time - -import pytest -import pytz - -from neo4j.time import ( - Date, - Duration, - UnixEpoch, - ZeroDate, -) - - -timezone_eastern = pytz.timezone("US/Eastern") -timezone_utc = pytz.utc - - -class TestDate: - - def test_bad_attribute(self): - d = Date(2000, 1, 1) - with pytest.raises(AttributeError): - _ = d.x - - def test_zero_date(self): - d = Date(0, 0, 0) - assert d.year_month_day == (0, 0, 0) - assert d.year == 0 - assert d.month == 0 - assert d.day == 0 - assert d == ZeroDate - - def test_zero_ordinal(self): - d = Date.from_ordinal(0) - assert d.year_month_day == (0, 0, 0) - assert d.year == 0 - assert d.month == 0 - assert d.day == 0 - assert d == ZeroDate - - def test_ordinal_at_start_of_1970(self): - d = Date.from_ordinal(719163) - assert d.year_month_day == (1970, 1, 1) - assert d.year == 1970 - assert d.month == 1 - assert d.day == 1 - - def test_ordinal_at_end_of_1969(self): - d = Date.from_ordinal(719162) - assert d.year_month_day == (1969, 12, 31) - assert d.year == 1969 - assert d.month == 12 - assert d.day == 31 - - def test_ordinal_at_start_of_2018(self): - d = Date.from_ordinal(736695) - assert d.year_month_day == (2018, 1, 1) - assert d.year == 2018 - assert d.month == 1 - assert d.day == 1 - - def test_ordinal_at_end_of_2017(self): - d = Date.from_ordinal(736694) - assert d.year_month_day == (2017, 12, 31) - assert d.year == 2017 - assert d.month == 12 - assert d.day == 31 - - def test_all_positive_days_of_month_for_31_day_month(self): - for day in range(1, 32): - t = Date(1976, 1, day) - assert t.year_month_day == (1976, 1, day) - assert t.year == 1976 - assert t.month == 1 - assert t.day == day - with pytest.raises(ValueError): - _ = Date(1976, 1, 32) - - def test_all_positive_days_of_month_for_30_day_month(self): - for day in range(1, 31): - t = Date(1976, 6, day) - assert t.year_month_day == (1976, 6, day) - assert t.year == 1976 - assert t.month == 6 - assert t.day == day - with pytest.raises(ValueError): - _ = Date(1976, 6, 31) - - def test_all_positive_days_of_month_for_29_day_month(self): - for day in range(1, 30): - t = Date(1976, 2, day) - assert t.year_month_day == (1976, 2, day) - assert t.year == 1976 - assert t.month == 2 - assert t.day == day - with pytest.raises(ValueError): - _ = Date(1976, 2, 30) - - def test_all_positive_days_of_month_for_28_day_month(self): - for day in range(1, 29): - t = Date(1977, 2, day) - assert t.year_month_day == (1977, 2, day) - assert t.year == 1977 - assert t.month == 2 - assert t.day == day - with pytest.raises(ValueError): - _ = Date(1977, 2, 29) - - def test_last_but_2_day_for_31_day_month(self): - t = Date(1976, 1, -3) - assert t.year_month_day == (1976, 1, 29) - assert t.year == 1976 - assert t.month == 1 - assert t.day == 29 - - def test_last_but_1_day_for_31_day_month(self): - t = Date(1976, 1, -2) - assert t.year_month_day == (1976, 1, 30) - assert t.year == 1976 - assert t.month == 1 - assert t.day == 30 - - def test_last_day_for_31_day_month(self): - t = Date(1976, 1, -1) - assert t.year_month_day == (1976, 1, 31) - assert t.year == 1976 - assert t.month == 1 - assert t.day == 31 - - def test_last_but_1_day_for_30_day_month(self): - t = Date(1976, 6, -2) - assert t.year_month_day == (1976, 6, 29) - assert t.year == 1976 - assert t.month == 6 - assert t.day == 29 - - def test_last_day_for_30_day_month(self): - t = Date(1976, 6, -1) - assert t.year_month_day == (1976, 6, 30) - assert t.year == 1976 - assert t.month == 6 - assert t.day == 30 - - def test_day_28_for_29_day_month(self): - t = Date(1976, 2, 28) - assert t.year_month_day == (1976, 2, 28) - assert t.year == 1976 - assert t.month == 2 - assert t.day == 28 - - def test_last_day_for_29_day_month(self): - t = Date(1976, 2, -1) - assert t.year_month_day == (1976, 2, 29) - assert t.year == 1976 - assert t.month == 2 - assert t.day == 29 - - def test_last_day_for_28_day_month(self): - t = Date(1977, 2, -1) - assert t.year_month_day == (1977, 2, 28) - assert t.year == 1977 - assert t.month == 2 - assert t.day == 28 - - def test_cannot_use_year_lower_than_one(self): - with pytest.raises(ValueError): - _ = Date(0, 2, 1) - - def test_cannot_use_year_higher_than_9999(self): - with pytest.raises(ValueError): - _ = Date(10000, 2, 1) - - def test_from_timestamp_without_tz(self): - d = Date.from_timestamp(0) - assert d == Date(1970, 1, 1) - - def test_from_timestamp_with_tz(self): - d = Date.from_timestamp(0, tz=timezone_eastern) - assert d == Date(1969, 12, 31) - - def test_utc_from_timestamp(self): - d = Date.utc_from_timestamp(0) - assert d == Date(1970, 1, 1) - - def test_from_ordinal(self): - d = Date.from_ordinal(1) - assert d == Date(1, 1, 1) - - def test_parse(self): - d = Date.parse("2018-04-30") - assert d == Date(2018, 4, 30) - - def test_bad_parse_1(self): - with pytest.raises(ValueError): - _ = Date.parse("30 April 2018") - - def test_bad_parse_2(self): - with pytest.raises(ValueError): - _ = Date.parse("2018-04") - - def test_bad_parse_3(self): - with pytest.raises(ValueError): - _ = Date.parse(object()) - - def test_replace(self): - d1 = Date(2018, 4, 30) - d2 = d1.replace(year=2017) - assert d2 == Date(2017, 4, 30) - - def test_from_clock_time(self): - d = Date.from_clock_time((0, 0), epoch=UnixEpoch) - assert d == Date(1970, 1, 1) - - def test_bad_from_clock_time(self): - with pytest.raises(ValueError): - _ = Date.from_clock_time(object(), None) - - def test_is_leap_year(self): - assert (Date.is_leap_year(2000)) - assert (Date.is_leap_year(2001)) - - def test_days_in_year(self): - assert Date.days_in_year(2000) == 366 - assert Date.days_in_year(2001) == 365 - - def test_days_in_month(self): - assert Date.days_in_month(2000, 1) == 31 - assert Date.days_in_month(2000, 2) == 29 - assert Date.days_in_month(2001, 2) == 28 - - def test_instance_attributes(self): - d = Date(2018, 4, 30) - assert d.year == 2018 - assert d.month == 4 - assert d.day == 30 - assert d.year_month_day == (2018, 4, 30) - assert d.year_week_day == (2018, 18, 1) - assert d.year_day == (2018, 120) - - def test_can_add_years(self): - d1 = Date(1976, 6, 13) - d2 = d1 + Duration(years=2) - assert d2 == Date(1978, 6, 13) - - def test_can_add_negative_years(self): - d1 = Date(1976, 6, 13) - d2 = d1 + Duration(years=-2) - assert d2 == Date(1974, 6, 13) - - def test_can_add_years_and_months(self): - d1 = Date(1976, 6, 13) - d2 = d1 + Duration(years=2, months=3) - assert d2 == Date(1978, 9, 13) - - def test_can_add_negative_years_and_months(self): - d1 = Date(1976, 6, 13) - d2 = d1 + Duration(years=-2, months=-3) - assert d2 == Date(1974, 3, 13) - - def test_can_retain_offset_from_end_of_month(self): - d = Date(1976, 1, -1) - assert d == Date(1976, 1, 31) - d += Duration(months=1) - assert d == Date(1976, 2, 29) - d += Duration(months=1) - assert d == Date(1976, 3, 31) - d += Duration(months=1) - assert d == Date(1976, 4, 30) - d += Duration(months=1) - assert d == Date(1976, 5, 31) - d += Duration(months=1) - assert d == Date(1976, 6, 30) - - def test_can_roll_over_end_of_year(self): - d = Date(1976, 12, 1) - assert d == Date(1976, 12, 1) - d += Duration(months=1) - assert d == Date(1977, 1, 1) - - def test_can_add_months_and_days(self): - d1 = Date(1976, 6, 13) - d2 = d1 + Duration(months=1, days=1) - assert d2 == Date(1976, 7, 14) - - def test_can_add_months_then_days(self): - d1 = Date(1976, 6, 13) - d2 = d1 + Duration(months=1) + Duration(days=1) - assert d2 == Date(1976, 7, 14) - - def test_cannot_add_seconds(self): - d1 = Date(1976, 6, 13) - with pytest.raises(ValueError): - _ = d1 + Duration(seconds=1) - - def test_adding_empty_duration_returns_self(self): - d1 = Date(1976, 6, 13) - d2 = d1 + Duration() - assert d1 is d2 - - def test_adding_object(self): - d1 = Date(1976, 6, 13) - with pytest.raises(TypeError): - _ = d1 + object() - - def test_can_add_days_then_months(self): - d1 = Date(1976, 6, 13) - d2 = d1 + Duration(days=1) + Duration(months=1) - assert d2 == Date(1976, 7, 14) - - def test_can_add_months_and_days_for_last_day_of_short_month(self): - d1 = Date(1976, 6, 30) - d2 = d1 + Duration(months=1, days=1) - assert d2 == Date(1976, 8, 1) - - def test_can_add_months_then_days_for_last_day_of_short_month(self): - d1 = Date(1976, 6, 30) - d2 = d1 + Duration(months=1) + Duration(days=1) - assert d2 == Date(1976, 8, 1) - - def test_can_add_days_then_months_for_last_day_of_short_month(self): - d1 = Date(1976, 6, 30) - d2 = d1 + Duration(days=1) + Duration(months=1) - assert d2 == Date(1976, 8, 1) - - def test_can_add_months_and_days_for_last_day_of_long_month(self): - d1 = Date(1976, 1, 31) - d2 = d1 + Duration(months=1, days=1) - assert d2 == Date(1976, 3, 1) - - def test_can_add_months_then_days_for_last_day_of_long_month(self): - d1 = Date(1976, 1, 31) - d2 = d1 + Duration(months=1) + Duration(days=1) - assert d2 == Date(1976, 3, 1) - - def test_can_add_days_then_months_for_last_day_of_long_month(self): - d1 = Date(1976, 1, 31) - d2 = d1 + Duration(days=1) + Duration(months=1) - assert d2 == Date(1976, 3, 1) - - def test_can_add_negative_months_and_days(self): - d1 = Date(1976, 6, 13) - d2 = d1 + Duration(months=-1, days=-1) - assert d2 == Date(1976, 5, 12) - - def test_can_add_negative_months_then_days(self): - d1 = Date(1976, 6, 13) - d2 = d1 + Duration(months=-1) + Duration(days=-1) - assert d2 == Date(1976, 5, 12) - - def test_can_add_negative_days_then_months(self): - d1 = Date(1976, 6, 13) - d2 = d1 + Duration(days=-1) + Duration(months=-1) - assert d2 == Date(1976, 5, 12) - - def test_can_add_negative_months_and_days_for_first_day_of_month(self): - d1 = Date(1976, 6, 1) - d2 = d1 + Duration(months=-1, days=-1) - assert d2 == Date(1976, 4, 30) - - def test_can_add_negative_months_then_days_for_first_day_of_month(self): - d1 = Date(1976, 6, 1) - d2 = d1 + Duration(months=-1) + Duration(days=-1) - assert d2 == Date(1976, 4, 30) - - def test_can_add_negative_days_then_months_for_last_day_of_month(self): - d1 = Date(1976, 6, 1) - d2 = d1 + Duration(days=-1) + Duration(months=-1) - assert d2 == Date(1976, 4, 30) - - def test_can_add_negative_month_for_last_day_of_long_month(self): - d1 = Date(1976, 5, 31) - d2 = d1 + Duration(months=-1) - assert d2 == Date(1976, 4, 30) - - def test_can_add_negative_month_for_january(self): - d1 = Date(1976, 1, 31) - d2 = d1 + Duration(months=-1) - assert d2 == Date(1975, 12, 31) - - def test_subtract_date(self): - new_year = Date(2000, 1, 1) - christmas = Date(1999, 12, 25) - assert new_year - christmas == Duration(days=7) - - def test_subtract_duration(self): - new_year = Date(2000, 1, 1) - christmas = Date(1999, 12, 25) - assert new_year - Duration(days=7) == christmas - - def test_subtract_object(self): - new_year = Date(2000, 1, 1) - with pytest.raises(TypeError): - _ = new_year - object() - - def test_date_less_than(self): - new_year = Date(2000, 1, 1) - christmas = Date(1999, 12, 25) - assert christmas < new_year - - def test_date_less_than_object(self): - d = Date(2000, 1, 1) - with pytest.raises(TypeError): - _ = d < object() - - def test_date_less_than_or_equal_to(self): - new_year = Date(2000, 1, 1) - christmas = Date(1999, 12, 25) - assert christmas <= new_year - - def test_date_less_than_or_equal_to_object(self): - d = Date(2000, 1, 1) - with pytest.raises(TypeError): - _ = d <= object() - - def test_date_greater_than_or_equal_to(self): - new_year = Date(2000, 1, 1) - christmas = Date(1999, 12, 25) - assert new_year >= christmas - - def test_date_greater_than_or_equal_to_object(self): - d = Date(2000, 1, 1) - with pytest.raises(TypeError): - _ = d >= object() - - def test_date_greater_than(self): - new_year = Date(2000, 1, 1) - christmas = Date(1999, 12, 25) - assert new_year > christmas - - def test_date_greater_than_object(self): - d = Date(2000, 1, 1) - with pytest.raises(TypeError): - _ = d > object() - - def test_date_equal(self): - d1 = Date(2000, 1, 1) - d2 = Date(2000, 1, 1) - assert d1 == d2 - - def test_date_not_equal(self): - d1 = Date(2000, 1, 1) - d2 = Date(2000, 1, 2) - assert d1 != d2 - - def test_date_not_equal_to_object(self): - d1 = Date(2000, 1, 1) - assert d1 != object() - - def test_year_week_day(self): - for ordinal in range(Date(2001, 1, 1).to_ordinal(), - Date(2008, 1, 1).to_ordinal()): - assert Date.from_ordinal(ordinal).iso_calendar() \ - == date.fromordinal(ordinal).isocalendar() - - def test_time_tuple(self): - d = Date(2018, 4, 30) - assert d.time_tuple() == struct_time( - (2018, 4, 30, 0, 0, 0, 0, 120, -1)) - - def test_to_clock_time(self): - d = Date(2018, 4, 30) - assert d.to_clock_time(UnixEpoch) == (1525046400, 0) - assert d.to_clock_time(d) == (0, 0) - with pytest.raises(TypeError): - _ = d.to_clock_time(object()) - - def test_weekday(self): - d = Date(2018, 4, 30) - assert d.weekday() == 0 - - def test_iso_weekday(self): - d = Date(2018, 4, 30) - assert d.iso_weekday() == 1 - - def test_str(self): - assert str(Date(2018, 4, 30)) == "2018-04-30" - assert str(Date(0, 0, 0)) == "0000-00-00" - - def test_repr(self): - assert repr(Date(2018, 4, 30)) == "neo4j.time.Date(2018, 4, 30)" - assert repr(Date(0, 0, 0)) == "neo4j.time.ZeroDate" - - def test_format(self): - d = Date(2018, 4, 30) - with pytest.raises(NotImplementedError): - _ = d.__format__("") - - def test_from_native(self): - native = date(2018, 10, 1) - d = Date.from_native(native) - assert d.year == native.year - assert d.month == native.month - assert d.day == native.day - - def test_to_native(self): - d = Date(2018, 10, 1) - native = d.to_native() - assert d.year == native.year - assert d.month == native.month - assert d.day == native.day - - def test_iso_format(self): - d = Date(2018, 10, 1) - assert "2018-10-01" == d.iso_format() - - def test_from_iso_format(self): - expected = Date(2018, 10, 1) - actual = Date.from_iso_format("2018-10-01") - assert expected == actual - - def test_date_copy(self): - d = Date(2010, 10, 1) - d2 = copy.copy(d) - assert d != d2 - assert d == d2 - - def test_date_deep_copy(self): - d = Date(2010, 10, 1) - d2 = copy.deepcopy(d) - assert d != d2 - assert d == d2 - - -@pytest.mark.parametrize(("tz", "expected"), ( - (None, (1970, 1, 1)), - (timezone_eastern, (1970, 1, 1)), - (timezone_utc, (1970, 1, 1)), - (pytz.FixedOffset(-12 * 60), (1970, 1, 1)), - (datetime.timezone(datetime.timedelta(hours=-12)), (1970, 1, 1)), - (pytz.FixedOffset(-13 * 60), (1969, 12, 31)), - (datetime.timezone(datetime.timedelta(hours=-13)), (1969, 12, 31)), - (pytz.FixedOffset(11 * 60), (1970, 1, 1)), - (datetime.timezone(datetime.timedelta(hours=11)), (1970, 1, 1)), - (pytz.FixedOffset(12 * 60), (1970, 1, 2)), - (datetime.timezone(datetime.timedelta(hours=12)), (1970, 1, 2)), - -)) -def test_today(tz, expected): - d = Date.today(tz=tz) - assert isinstance(d, Date) - assert d.year_month_day == expected From 9c782a551332ce0ff81a70aa02ed1a5e50afd3aa Mon Sep 17 00:00:00 2001 From: Prychantovskyi <75383953+Prychantovskyi@users.noreply.github.com> Date: Fri, 22 Jul 2022 09:51:56 +0100 Subject: [PATCH 07/19] Delete test_clocktime.py --- test_clocktime.py | 103 ---------------------------------------------- 1 file changed, 103 deletions(-) delete mode 100644 test_clocktime.py diff --git a/test_clocktime.py b/test_clocktime.py deleted file mode 100644 index 0d3d8077f..000000000 --- a/test_clocktime.py +++ /dev/null @@ -1,103 +0,0 @@ -# Copyright (c) "Neo4j" -# Neo4j Sweden AB [https://neo4j.com] -# -# This file is part of Neo4j. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - - -import pytest - -from neo4j.time import ( - ClockTime, - Duration, -) - - -class TestClockTime: - - def test_zero_(self): - ct = ClockTime() - assert ct.seconds == 0 - assert ct.nanoseconds == 0 - - def test_only_seconds(self): - ct = ClockTime(123456) - assert ct.seconds == 123456 - assert ct.nanoseconds == 0 - - def test_float(self): - ct = ClockTime(123456.789) - assert ct.seconds == 123456 - assert ct.nanoseconds == 789000000 - - def test_only_nanoseconds(self): - ct = ClockTime(0, 123456789) - assert ct.seconds == 0 - assert ct.nanoseconds == 123456789 - - def test_nanoseconds_overflow(self): - ct = ClockTime(0, 2123456789) - assert ct.seconds == 2 - assert ct.nanoseconds == 123456789 - - def test_positive_nanoseconds(self): - ct = ClockTime(1, 1) - assert ct.seconds == 1 - assert ct.nanoseconds == 1 - - def test_negative_nanoseconds(self): - ct = ClockTime(1, -1) - assert ct.seconds == 0 - assert ct.nanoseconds == 999999999 - - def test_add_float(self): - ct = ClockTime(123456.789) + 0.1 - assert ct.seconds == 123456 - assert ct.nanoseconds == 889000000 - - def test_add_duration(self): - ct = ClockTime(123456.789) + Duration(seconds=1) - assert ct.seconds == 123457 - assert ct.nanoseconds == 789000000 - - def test_add_duration_with_months(self): - with pytest.raises(ValueError): - _ = ClockTime(123456.789) + Duration(months=1) - - def test_add_object(self): - with pytest.raises(TypeError): - _ = ClockTime(123456.789) + object() - - def test_sub_float(self): - ct = ClockTime(123456.789) - 0.1 - assert ct.seconds == 123456 - assert ct.nanoseconds == 689000000 - - def test_sub_duration(self): - ct = ClockTime(123456.789) - Duration(seconds=1) - assert ct.seconds == 123455 - assert ct.nanoseconds == 789000000 - - def test_sub_duration_with_months(self): - with pytest.raises(ValueError): - _ = ClockTime(123456.789) - Duration(months=1) - - def test_sub_object(self): - with pytest.raises(TypeError): - _ = ClockTime(123456.789) - object() - - def test_repr(self): - ct = ClockTime(123456.789) - assert repr(ct).startswith("ClockTime") From bb07d94d8d7a9d9f9b0108c0cb279ca2cb6e85e4 Mon Sep 17 00:00:00 2001 From: Prychantovskyi <75383953+Prychantovskyi@users.noreply.github.com> Date: Fri, 22 Jul 2022 09:52:06 +0100 Subject: [PATCH 08/19] Delete test_cartesian_point.py --- test_cartesian_point.py | 44 ----------------------------------------- 1 file changed, 44 deletions(-) delete mode 100644 test_cartesian_point.py diff --git a/test_cartesian_point.py b/test_cartesian_point.py deleted file mode 100644 index 8e22f1a9e..000000000 --- a/test_cartesian_point.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright (c) "Neo4j" -# Neo4j Sweden AB [https://neo4j.com] -# -# This file is part of Neo4j. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import pytest - -from neo4j.spatial import CartesianPoint - - -class CartesianPointTestCase: - - def test_alias_3d(self): - x, y, z = 3.2, 4.0, -1.2 - p = CartesianPoint((x, y, z)) - assert hasattr(p, "x") - assert p.x == x - assert hasattr(p, "y") - assert p.y == y - assert hasattr(p, "z") - assert p.z == z - - def test_alias_2d(self): - x, y = 3.2, 4.0 - p = CartesianPoint((x, y)) - assert hasattr(p, "x") - assert p.x == x - assert hasattr(p, "y") - assert p.y == y - with pytest.raises(AttributeError): - p.z From ac9501bdda0ad9498b7d7677691e09041fe4f336 Mon Sep 17 00:00:00 2001 From: Prychantovskyi <75383953+Prychantovskyi@users.noreply.github.com> Date: Fri, 22 Jul 2022 09:53:10 +0100 Subject: [PATCH 09/19] Add files via upload --- tests/unit/common/time/test_clock.py | 23 +- tests/unit/common/time/test_clocktime.py | 59 ++-- tests/unit/common/time/test_date.py | 358 ++++++++++++----------- 3 files changed, 222 insertions(+), 218 deletions(-) diff --git a/tests/unit/common/time/test_clock.py b/tests/unit/common/time/test_clock.py index f5fc41b8a..43b957548 100644 --- a/tests/unit/common/time/test_clock.py +++ b/tests/unit/common/time/test_clock.py @@ -16,7 +16,8 @@ # limitations under the License. -from unittest import TestCase + +import pytest from neo4j.time._clock_implementations import ( Clock, @@ -24,48 +25,48 @@ ) -class TestClock(TestCase): +class TestClock: def test_no_clock_implementations(self): try: Clock._Clock__implementations = [] - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): _ = Clock() finally: Clock._Clock__implementations = None def test_base_clock_precision(self): clock = object.__new__(Clock) - with self.assertRaises(NotImplementedError): + with pytest.raises(NotImplementedError): _ = clock.precision() def test_base_clock_available(self): clock = object.__new__(Clock) - with self.assertRaises(NotImplementedError): + with pytest.raises(NotImplementedError): _ = clock.available() def test_base_clock_utc_time(self): clock = object.__new__(Clock) - with self.assertRaises(NotImplementedError): + with pytest.raises(NotImplementedError): _ = clock.utc_time() def test_local_offset(self): clock = object.__new__(Clock) offset = clock.local_offset() - self.assertIsInstance(offset, ClockTime) + assert isinstance(offset, ClockTime) def test_local_time(self): _ = Clock() for impl in Clock._Clock__implementations: - self.assertTrue(issubclass(impl, Clock)) + assert issubclass(impl, Clock) clock = object.__new__(impl) time = clock.local_time() - self.assertIsInstance(time, ClockTime) + assert isinstance(time, ClockTime) def test_utc_time(self): _ = Clock() for impl in Clock._Clock__implementations: - self.assertTrue(issubclass(impl, Clock)) + assert issubclass(impl, Clock) clock = object.__new__(impl) time = clock.utc_time() - self.assertIsInstance(time, ClockTime) + assert isinstance(time, ClockTime) diff --git a/tests/unit/common/time/test_clocktime.py b/tests/unit/common/time/test_clocktime.py index 6c1c7842b..0d3d8077f 100644 --- a/tests/unit/common/time/test_clocktime.py +++ b/tests/unit/common/time/test_clocktime.py @@ -16,7 +16,8 @@ # limitations under the License. -from unittest import TestCase + +import pytest from neo4j.time import ( ClockTime, @@ -24,79 +25,79 @@ ) -class TestClockTime(TestCase): +class TestClockTime: def test_zero_(self): ct = ClockTime() - self.assertEqual(ct.seconds, 0) - self.assertEqual(ct.nanoseconds, 0) + assert ct.seconds == 0 + assert ct.nanoseconds == 0 def test_only_seconds(self): ct = ClockTime(123456) - self.assertEqual(ct.seconds, 123456) - self.assertEqual(ct.nanoseconds, 0) + assert ct.seconds == 123456 + assert ct.nanoseconds == 0 def test_float(self): ct = ClockTime(123456.789) - self.assertEqual(ct.seconds, 123456) - self.assertEqual(ct.nanoseconds, 789000000) + assert ct.seconds == 123456 + assert ct.nanoseconds == 789000000 def test_only_nanoseconds(self): ct = ClockTime(0, 123456789) - self.assertEqual(ct.seconds, 0) - self.assertEqual(ct.nanoseconds, 123456789) + assert ct.seconds == 0 + assert ct.nanoseconds == 123456789 def test_nanoseconds_overflow(self): ct = ClockTime(0, 2123456789) - self.assertEqual(ct.seconds, 2) - self.assertEqual(ct.nanoseconds, 123456789) + assert ct.seconds == 2 + assert ct.nanoseconds == 123456789 def test_positive_nanoseconds(self): ct = ClockTime(1, 1) - self.assertEqual(ct.seconds, 1) - self.assertEqual(ct.nanoseconds, 1) + assert ct.seconds == 1 + assert ct.nanoseconds == 1 def test_negative_nanoseconds(self): ct = ClockTime(1, -1) - self.assertEqual(ct.seconds, 0) - self.assertEqual(ct.nanoseconds, 999999999) + assert ct.seconds == 0 + assert ct.nanoseconds == 999999999 def test_add_float(self): ct = ClockTime(123456.789) + 0.1 - self.assertEqual(ct.seconds, 123456) - self.assertEqual(ct.nanoseconds, 889000000) + assert ct.seconds == 123456 + assert ct.nanoseconds == 889000000 def test_add_duration(self): ct = ClockTime(123456.789) + Duration(seconds=1) - self.assertEqual(ct.seconds, 123457) - self.assertEqual(ct.nanoseconds, 789000000) + assert ct.seconds == 123457 + assert ct.nanoseconds == 789000000 def test_add_duration_with_months(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = ClockTime(123456.789) + Duration(months=1) def test_add_object(self): - with self.assertRaises(TypeError): + with pytest.raises(TypeError): _ = ClockTime(123456.789) + object() def test_sub_float(self): ct = ClockTime(123456.789) - 0.1 - self.assertEqual(ct.seconds, 123456) - self.assertEqual(ct.nanoseconds, 689000000) + assert ct.seconds == 123456 + assert ct.nanoseconds == 689000000 def test_sub_duration(self): ct = ClockTime(123456.789) - Duration(seconds=1) - self.assertEqual(ct.seconds, 123455) - self.assertEqual(ct.nanoseconds, 789000000) + assert ct.seconds == 123455 + assert ct.nanoseconds == 789000000 def test_sub_duration_with_months(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = ClockTime(123456.789) - Duration(months=1) def test_sub_object(self): - with self.assertRaises(TypeError): + with pytest.raises(TypeError): _ = ClockTime(123456.789) - object() def test_repr(self): ct = ClockTime(123456.789) - self.assertTrue(repr(ct).startswith("ClockTime")) + assert repr(ct).startswith("ClockTime") diff --git a/tests/unit/common/time/test_date.py b/tests/unit/common/time/test_date.py index 4b8e4e761..5cb496767 100644 --- a/tests/unit/common/time/test_date.py +++ b/tests/unit/common/time/test_date.py @@ -20,7 +20,6 @@ import datetime from datetime import date from time import struct_time -from unittest import TestCase import pytest import pytz @@ -37,507 +36,510 @@ timezone_utc = pytz.utc -class TestDate(TestCase): +class TestDate: def test_bad_attribute(self): d = Date(2000, 1, 1) - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError): _ = d.x def test_zero_date(self): d = Date(0, 0, 0) - self.assertEqual(d.year_month_day, (0, 0, 0)) - self.assertEqual(d.year, 0) - self.assertEqual(d.month, 0) - self.assertEqual(d.day, 0) - self.assertIs(d, ZeroDate) + assert d.year_month_day == (0, 0, 0) + assert d.year == 0 + assert d.month == 0 + assert d.day == 0 + assert d == ZeroDate def test_zero_ordinal(self): d = Date.from_ordinal(0) - self.assertEqual(d.year_month_day, (0, 0, 0)) - self.assertEqual(d.year, 0) - self.assertEqual(d.month, 0) - self.assertEqual(d.day, 0) - self.assertIs(d, ZeroDate) + assert d.year_month_day == (0, 0, 0) + assert d.year == 0 + assert d.month == 0 + assert d.day == 0 + assert d == ZeroDate def test_ordinal_at_start_of_1970(self): d = Date.from_ordinal(719163) - self.assertEqual(d.year_month_day, (1970, 1, 1)) - self.assertEqual(d.year, 1970) - self.assertEqual(d.month, 1) - self.assertEqual(d.day, 1) + assert d.year_month_day == (1970, 1, 1) + assert d.year == 1970 + assert d.month == 1 + assert d.day == 1 def test_ordinal_at_end_of_1969(self): d = Date.from_ordinal(719162) - self.assertEqual(d.year_month_day, (1969, 12, 31)) - self.assertEqual(d.year, 1969) - self.assertEqual(d.month, 12) - self.assertEqual(d.day, 31) + assert d.year_month_day == (1969, 12, 31) + assert d.year == 1969 + assert d.month == 12 + assert d.day == 31 def test_ordinal_at_start_of_2018(self): d = Date.from_ordinal(736695) - self.assertEqual(d.year_month_day, (2018, 1, 1)) - self.assertEqual(d.year, 2018) - self.assertEqual(d.month, 1) - self.assertEqual(d.day, 1) + assert d.year_month_day == (2018, 1, 1) + assert d.year == 2018 + assert d.month == 1 + assert d.day == 1 def test_ordinal_at_end_of_2017(self): d = Date.from_ordinal(736694) - self.assertEqual(d.year_month_day, (2017, 12, 31)) - self.assertEqual(d.year, 2017) - self.assertEqual(d.month, 12) - self.assertEqual(d.day, 31) + assert d.year_month_day == (2017, 12, 31) + assert d.year == 2017 + assert d.month == 12 + assert d.day == 31 def test_all_positive_days_of_month_for_31_day_month(self): for day in range(1, 32): t = Date(1976, 1, day) - self.assertEqual(t.year_month_day, (1976, 1, day)) - self.assertEqual(t.year, 1976) - self.assertEqual(t.month, 1) - self.assertEqual(t.day, day) - with self.assertRaises(ValueError): + assert t.year_month_day == (1976, 1, day) + assert t.year == 1976 + assert t.month == 1 + assert t.day == day + with pytest.raises(ValueError): _ = Date(1976, 1, 32) def test_all_positive_days_of_month_for_30_day_month(self): for day in range(1, 31): t = Date(1976, 6, day) - self.assertEqual(t.year_month_day, (1976, 6, day)) - self.assertEqual(t.year, 1976) - self.assertEqual(t.month, 6) - self.assertEqual(t.day, day) - with self.assertRaises(ValueError): + assert t.year_month_day == (1976, 6, day) + assert t.year == 1976 + assert t.month == 6 + assert t.day == day + with pytest.raises(ValueError): _ = Date(1976, 6, 31) def test_all_positive_days_of_month_for_29_day_month(self): for day in range(1, 30): t = Date(1976, 2, day) - self.assertEqual(t.year_month_day, (1976, 2, day)) - self.assertEqual(t.year, 1976) - self.assertEqual(t.month, 2) - self.assertEqual(t.day, day) - with self.assertRaises(ValueError): + assert t.year_month_day == (1976, 2, day) + assert t.year == 1976 + assert t.month == 2 + assert t.day == day + with pytest.raises(ValueError): _ = Date(1976, 2, 30) def test_all_positive_days_of_month_for_28_day_month(self): for day in range(1, 29): t = Date(1977, 2, day) - self.assertEqual(t.year_month_day, (1977, 2, day)) - self.assertEqual(t.year, 1977) - self.assertEqual(t.month, 2) - self.assertEqual(t.day, day) - with self.assertRaises(ValueError): + assert t.year_month_day == (1977, 2, day) + assert t.year == 1977 + assert t.month == 2 + assert t.day == day + with pytest.raises(ValueError): _ = Date(1977, 2, 29) def test_last_but_2_day_for_31_day_month(self): t = Date(1976, 1, -3) - self.assertEqual(t.year_month_day, (1976, 1, 29)) - self.assertEqual(t.year, 1976) - self.assertEqual(t.month, 1) - self.assertEqual(t.day, 29) + assert t.year_month_day == (1976, 1, 29) + assert t.year == 1976 + assert t.month == 1 + assert t.day == 29 def test_last_but_1_day_for_31_day_month(self): t = Date(1976, 1, -2) - self.assertEqual(t.year_month_day, (1976, 1, 30)) - self.assertEqual(t.year, 1976) - self.assertEqual(t.month, 1) - self.assertEqual(t.day, 30) + assert t.year_month_day == (1976, 1, 30) + assert t.year == 1976 + assert t.month == 1 + assert t.day == 30 def test_last_day_for_31_day_month(self): t = Date(1976, 1, -1) - self.assertEqual(t.year_month_day, (1976, 1, 31)) - self.assertEqual(t.year, 1976) - self.assertEqual(t.month, 1) - self.assertEqual(t.day, 31) + assert t.year_month_day == (1976, 1, 31) + assert t.year == 1976 + assert t.month == 1 + assert t.day == 31 def test_last_but_1_day_for_30_day_month(self): t = Date(1976, 6, -2) - self.assertEqual(t.year_month_day, (1976, 6, 29)) - self.assertEqual(t.year, 1976) - self.assertEqual(t.month, 6) - self.assertEqual(t.day, 29) + assert t.year_month_day == (1976, 6, 29) + assert t.year == 1976 + assert t.month == 6 + assert t.day == 29 def test_last_day_for_30_day_month(self): t = Date(1976, 6, -1) - self.assertEqual(t.year_month_day, (1976, 6, 30)) - self.assertEqual(t.year, 1976) - self.assertEqual(t.month, 6) - self.assertEqual(t.day, 30) + assert t.year_month_day == (1976, 6, 30) + assert t.year == 1976 + assert t.month == 6 + assert t.day == 30 def test_day_28_for_29_day_month(self): t = Date(1976, 2, 28) - self.assertEqual(t.year_month_day, (1976, 2, 28)) - self.assertEqual(t.year, 1976) - self.assertEqual(t.month, 2) - self.assertEqual(t.day, 28) + assert t.year_month_day == (1976, 2, 28) + assert t.year == 1976 + assert t.month == 2 + assert t.day == 28 def test_last_day_for_29_day_month(self): t = Date(1976, 2, -1) - self.assertEqual(t.year_month_day, (1976, 2, 29)) - self.assertEqual(t.year, 1976) - self.assertEqual(t.month, 2) - self.assertEqual(t.day, 29) + assert t.year_month_day == (1976, 2, 29) + assert t.year == 1976 + assert t.month == 2 + assert t.day == 29 def test_last_day_for_28_day_month(self): t = Date(1977, 2, -1) - self.assertEqual(t.year_month_day, (1977, 2, 28)) - self.assertEqual(t.year, 1977) - self.assertEqual(t.month, 2) - self.assertEqual(t.day, 28) + assert t.year_month_day == (1977, 2, 28) + assert t.year == 1977 + assert t.month == 2 + assert t.day == 28 def test_cannot_use_year_lower_than_one(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = Date(0, 2, 1) def test_cannot_use_year_higher_than_9999(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = Date(10000, 2, 1) def test_from_timestamp_without_tz(self): d = Date.from_timestamp(0) - self.assertEqual(d, Date(1970, 1, 1)) + assert d == Date(1970, 1, 1) def test_from_timestamp_with_tz(self): d = Date.from_timestamp(0, tz=timezone_eastern) - self.assertEqual(d, Date(1969, 12, 31)) + assert d == Date(1969, 12, 31) def test_utc_from_timestamp(self): d = Date.utc_from_timestamp(0) - self.assertEqual(d, Date(1970, 1, 1)) + assert d == Date(1970, 1, 1) def test_from_ordinal(self): d = Date.from_ordinal(1) - self.assertEqual(d, Date(1, 1, 1)) + assert d == Date(1, 1, 1) def test_parse(self): d = Date.parse("2018-04-30") - self.assertEqual(d, Date(2018, 4, 30)) + assert d == Date(2018, 4, 30) def test_bad_parse_1(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = Date.parse("30 April 2018") def test_bad_parse_2(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = Date.parse("2018-04") def test_bad_parse_3(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = Date.parse(object()) def test_replace(self): d1 = Date(2018, 4, 30) d2 = d1.replace(year=2017) - self.assertEqual(d2, Date(2017, 4, 30)) + assert d2 == Date(2017, 4, 30) def test_from_clock_time(self): d = Date.from_clock_time((0, 0), epoch=UnixEpoch) - self.assertEqual(d, Date(1970, 1, 1)) + assert d == Date(1970, 1, 1) def test_bad_from_clock_time(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = Date.from_clock_time(object(), None) def test_is_leap_year(self): - self.assertTrue(Date.is_leap_year(2000)) - self.assertFalse(Date.is_leap_year(2001)) + assert (Date.is_leap_year(2000)) + assert (Date.is_leap_year(2001)) def test_days_in_year(self): - self.assertEqual(Date.days_in_year(2000), 366) - self.assertEqual(Date.days_in_year(2001), 365) + assert Date.days_in_year(2000) == 366 + assert Date.days_in_year(2001) == 365 def test_days_in_month(self): - self.assertEqual(Date.days_in_month(2000, 1), 31) - self.assertEqual(Date.days_in_month(2000, 2), 29) - self.assertEqual(Date.days_in_month(2001, 2), 28) + assert Date.days_in_month(2000, 1) == 31 + assert Date.days_in_month(2000, 2) == 29 + assert Date.days_in_month(2001, 2) == 28 def test_instance_attributes(self): d = Date(2018, 4, 30) - self.assertEqual(d.year, 2018) - self.assertEqual(d.month, 4) - self.assertEqual(d.day, 30) - self.assertEqual(d.year_month_day, (2018, 4, 30)) - self.assertEqual(d.year_week_day, (2018, 18, 1)) - self.assertEqual(d.year_day, (2018, 120)) + assert d.year == 2018 + assert d.month == 4 + assert d.day == 30 + assert d.year_month_day == (2018, 4, 30) + assert d.year_week_day == (2018, 18, 1) + assert d.year_day == (2018, 120) def test_can_add_years(self): d1 = Date(1976, 6, 13) d2 = d1 + Duration(years=2) - self.assertEqual(d2, Date(1978, 6, 13)) + assert d2 == Date(1978, 6, 13) def test_can_add_negative_years(self): d1 = Date(1976, 6, 13) d2 = d1 + Duration(years=-2) - self.assertEqual(d2, Date(1974, 6, 13)) + assert d2 == Date(1974, 6, 13) def test_can_add_years_and_months(self): d1 = Date(1976, 6, 13) d2 = d1 + Duration(years=2, months=3) - self.assertEqual(d2, Date(1978, 9, 13)) + assert d2 == Date(1978, 9, 13) def test_can_add_negative_years_and_months(self): d1 = Date(1976, 6, 13) d2 = d1 + Duration(years=-2, months=-3) - self.assertEqual(d2, Date(1974, 3, 13)) + assert d2 == Date(1974, 3, 13) def test_can_retain_offset_from_end_of_month(self): d = Date(1976, 1, -1) - self.assertEqual(d, Date(1976, 1, 31)) + assert d == Date(1976, 1, 31) d += Duration(months=1) - self.assertEqual(d, Date(1976, 2, 29)) + assert d == Date(1976, 2, 29) d += Duration(months=1) - self.assertEqual(d, Date(1976, 3, 31)) + assert d == Date(1976, 3, 31) d += Duration(months=1) - self.assertEqual(d, Date(1976, 4, 30)) + assert d == Date(1976, 4, 30) d += Duration(months=1) - self.assertEqual(d, Date(1976, 5, 31)) + assert d == Date(1976, 5, 31) d += Duration(months=1) - self.assertEqual(d, Date(1976, 6, 30)) + assert d == Date(1976, 6, 30) def test_can_roll_over_end_of_year(self): d = Date(1976, 12, 1) - self.assertEqual(d, Date(1976, 12, 1)) + assert d == Date(1976, 12, 1) d += Duration(months=1) - self.assertEqual(d, Date(1977, 1, 1)) + assert d == Date(1977, 1, 1) def test_can_add_months_and_days(self): d1 = Date(1976, 6, 13) d2 = d1 + Duration(months=1, days=1) - self.assertEqual(d2, Date(1976, 7, 14)) + assert d2 == Date(1976, 7, 14) def test_can_add_months_then_days(self): d1 = Date(1976, 6, 13) d2 = d1 + Duration(months=1) + Duration(days=1) - self.assertEqual(d2, Date(1976, 7, 14)) + assert d2 == Date(1976, 7, 14) def test_cannot_add_seconds(self): d1 = Date(1976, 6, 13) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = d1 + Duration(seconds=1) def test_adding_empty_duration_returns_self(self): d1 = Date(1976, 6, 13) d2 = d1 + Duration() - self.assertIs(d1, d2) + assert d1 is d2 def test_adding_object(self): d1 = Date(1976, 6, 13) - with self.assertRaises(TypeError): + with pytest.raises(TypeError): _ = d1 + object() def test_can_add_days_then_months(self): d1 = Date(1976, 6, 13) d2 = d1 + Duration(days=1) + Duration(months=1) - self.assertEqual(d2, Date(1976, 7, 14)) + assert d2 == Date(1976, 7, 14) def test_can_add_months_and_days_for_last_day_of_short_month(self): d1 = Date(1976, 6, 30) d2 = d1 + Duration(months=1, days=1) - self.assertEqual(d2, Date(1976, 8, 1)) + assert d2 == Date(1976, 8, 1) def test_can_add_months_then_days_for_last_day_of_short_month(self): d1 = Date(1976, 6, 30) d2 = d1 + Duration(months=1) + Duration(days=1) - self.assertEqual(d2, Date(1976, 8, 1)) + assert d2 == Date(1976, 8, 1) def test_can_add_days_then_months_for_last_day_of_short_month(self): d1 = Date(1976, 6, 30) d2 = d1 + Duration(days=1) + Duration(months=1) - self.assertEqual(d2, Date(1976, 8, 1)) + assert d2 == Date(1976, 8, 1) def test_can_add_months_and_days_for_last_day_of_long_month(self): d1 = Date(1976, 1, 31) d2 = d1 + Duration(months=1, days=1) - self.assertEqual(d2, Date(1976, 3, 1)) + assert d2 == Date(1976, 3, 1) def test_can_add_months_then_days_for_last_day_of_long_month(self): d1 = Date(1976, 1, 31) d2 = d1 + Duration(months=1) + Duration(days=1) - self.assertEqual(d2, Date(1976, 3, 1)) + assert d2 == Date(1976, 3, 1) def test_can_add_days_then_months_for_last_day_of_long_month(self): d1 = Date(1976, 1, 31) d2 = d1 + Duration(days=1) + Duration(months=1) - self.assertEqual(d2, Date(1976, 3, 1)) + assert d2 == Date(1976, 3, 1) def test_can_add_negative_months_and_days(self): d1 = Date(1976, 6, 13) d2 = d1 + Duration(months=-1, days=-1) - self.assertEqual(d2, Date(1976, 5, 12)) + assert d2 == Date(1976, 5, 12) def test_can_add_negative_months_then_days(self): d1 = Date(1976, 6, 13) d2 = d1 + Duration(months=-1) + Duration(days=-1) - self.assertEqual(d2, Date(1976, 5, 12)) + assert d2 == Date(1976, 5, 12) def test_can_add_negative_days_then_months(self): d1 = Date(1976, 6, 13) d2 = d1 + Duration(days=-1) + Duration(months=-1) - self.assertEqual(d2, Date(1976, 5, 12)) + assert d2 == Date(1976, 5, 12) def test_can_add_negative_months_and_days_for_first_day_of_month(self): d1 = Date(1976, 6, 1) d2 = d1 + Duration(months=-1, days=-1) - self.assertEqual(d2, Date(1976, 4, 30)) + assert d2 == Date(1976, 4, 30) def test_can_add_negative_months_then_days_for_first_day_of_month(self): d1 = Date(1976, 6, 1) d2 = d1 + Duration(months=-1) + Duration(days=-1) - self.assertEqual(d2, Date(1976, 4, 30)) + assert d2 == Date(1976, 4, 30) def test_can_add_negative_days_then_months_for_last_day_of_month(self): d1 = Date(1976, 6, 1) d2 = d1 + Duration(days=-1) + Duration(months=-1) - self.assertEqual(d2, Date(1976, 4, 30)) + assert d2 == Date(1976, 4, 30) def test_can_add_negative_month_for_last_day_of_long_month(self): d1 = Date(1976, 5, 31) d2 = d1 + Duration(months=-1) - self.assertEqual(d2, Date(1976, 4, 30)) + assert d2 == Date(1976, 4, 30) def test_can_add_negative_month_for_january(self): d1 = Date(1976, 1, 31) d2 = d1 + Duration(months=-1) - self.assertEqual(d2, Date(1975, 12, 31)) + assert d2 == Date(1975, 12, 31) def test_subtract_date(self): new_year = Date(2000, 1, 1) christmas = Date(1999, 12, 25) - self.assertEqual(new_year - christmas, Duration(days=7)) + assert new_year - christmas == Duration(days=7) def test_subtract_duration(self): new_year = Date(2000, 1, 1) christmas = Date(1999, 12, 25) - self.assertEqual(new_year - Duration(days=7), christmas) + assert new_year - Duration(days=7) == christmas def test_subtract_object(self): new_year = Date(2000, 1, 1) - with self.assertRaises(TypeError): + with pytest.raises(TypeError): _ = new_year - object() def test_date_less_than(self): new_year = Date(2000, 1, 1) christmas = Date(1999, 12, 25) - self.assertLess(christmas, new_year) + assert christmas < new_year def test_date_less_than_object(self): d = Date(2000, 1, 1) - with self.assertRaises(TypeError): + with pytest.raises(TypeError): _ = d < object() def test_date_less_than_or_equal_to(self): new_year = Date(2000, 1, 1) christmas = Date(1999, 12, 25) - self.assertLessEqual(christmas, new_year) + assert christmas <= new_year def test_date_less_than_or_equal_to_object(self): d = Date(2000, 1, 1) - with self.assertRaises(TypeError): + with pytest.raises(TypeError): _ = d <= object() def test_date_greater_than_or_equal_to(self): new_year = Date(2000, 1, 1) christmas = Date(1999, 12, 25) - self.assertGreaterEqual(new_year, christmas) + assert new_year >= christmas def test_date_greater_than_or_equal_to_object(self): d = Date(2000, 1, 1) - with self.assertRaises(TypeError): + with pytest.raises(TypeError): _ = d >= object() def test_date_greater_than(self): new_year = Date(2000, 1, 1) christmas = Date(1999, 12, 25) - self.assertGreater(new_year, christmas) + assert new_year > christmas def test_date_greater_than_object(self): d = Date(2000, 1, 1) - with self.assertRaises(TypeError): + with pytest.raises(TypeError): _ = d > object() def test_date_equal(self): d1 = Date(2000, 1, 1) d2 = Date(2000, 1, 1) - self.assertEqual(d1, d2) + assert d1 == d2 def test_date_not_equal(self): d1 = Date(2000, 1, 1) d2 = Date(2000, 1, 2) - self.assertNotEqual(d1, d2) + assert d1 != d2 def test_date_not_equal_to_object(self): d1 = Date(2000, 1, 1) - self.assertNotEqual(d1, object()) + assert d1 != object() def test_year_week_day(self): - for ordinal in range(Date(2001, 1, 1).to_ordinal(), Date(2008, 1, 1).to_ordinal()): - self.assertEqual(Date.from_ordinal(ordinal).iso_calendar(), date.fromordinal(ordinal).isocalendar()) + for ordinal in range(Date(2001, 1, 1).to_ordinal(), + Date(2008, 1, 1).to_ordinal()): + assert Date.from_ordinal(ordinal).iso_calendar() \ + == date.fromordinal(ordinal).isocalendar() def test_time_tuple(self): d = Date(2018, 4, 30) - self.assertEqual(d.time_tuple(), struct_time((2018, 4, 30, 0, 0, 0, 0, 120, -1))) + assert d.time_tuple() == struct_time( + (2018, 4, 30, 0, 0, 0, 0, 120, -1)) def test_to_clock_time(self): d = Date(2018, 4, 30) - self.assertEqual(d.to_clock_time(UnixEpoch), (1525046400, 0)) - self.assertEqual(d.to_clock_time(d), (0, 0)) - with self.assertRaises(TypeError): + assert d.to_clock_time(UnixEpoch) == (1525046400, 0) + assert d.to_clock_time(d) == (0, 0) + with pytest.raises(TypeError): _ = d.to_clock_time(object()) def test_weekday(self): d = Date(2018, 4, 30) - self.assertEqual(d.weekday(), 0) + assert d.weekday() == 0 def test_iso_weekday(self): d = Date(2018, 4, 30) - self.assertEqual(d.iso_weekday(), 1) + assert d.iso_weekday() == 1 def test_str(self): - self.assertEqual(str(Date(2018, 4, 30)), "2018-04-30") - self.assertEqual(str(Date(0, 0, 0)), "0000-00-00") + assert str(Date(2018, 4, 30)) == "2018-04-30" + assert str(Date(0, 0, 0)) == "0000-00-00" def test_repr(self): - self.assertEqual(repr(Date(2018, 4, 30)), "neo4j.time.Date(2018, 4, 30)") - self.assertEqual(repr(Date(0, 0, 0)), "neo4j.time.ZeroDate") + assert repr(Date(2018, 4, 30)) == "neo4j.time.Date(2018, 4, 30)" + assert repr(Date(0, 0, 0)) == "neo4j.time.ZeroDate" def test_format(self): d = Date(2018, 4, 30) - with self.assertRaises(NotImplementedError): + with pytest.raises(NotImplementedError): _ = d.__format__("") def test_from_native(self): native = date(2018, 10, 1) d = Date.from_native(native) - self.assertEqual(d.year, native.year) - self.assertEqual(d.month, native.month) - self.assertEqual(d.day, native.day) + assert d.year == native.year + assert d.month == native.month + assert d.day == native.day def test_to_native(self): d = Date(2018, 10, 1) native = d.to_native() - self.assertEqual(d.year, native.year) - self.assertEqual(d.month, native.month) - self.assertEqual(d.day, native.day) + assert d.year == native.year + assert d.month == native.month + assert d.day == native.day def test_iso_format(self): d = Date(2018, 10, 1) - self.assertEqual("2018-10-01", d.iso_format()) + assert "2018-10-01" == d.iso_format() def test_from_iso_format(self): expected = Date(2018, 10, 1) actual = Date.from_iso_format("2018-10-01") - self.assertEqual(expected, actual) + assert expected == actual def test_date_copy(self): d = Date(2010, 10, 1) d2 = copy.copy(d) - self.assertIsNot(d, d2) - self.assertEqual(d, d2) + assert d != d2 + assert d == d2 def test_date_deep_copy(self): d = Date(2010, 10, 1) d2 = copy.deepcopy(d) - self.assertIsNot(d, d2) - self.assertEqual(d, d2) + assert d != d2 + assert d == d2 @pytest.mark.parametrize(("tz", "expected"), ( From 94ce893c6a8660add901950370222a4c8e10a818 Mon Sep 17 00:00:00 2001 From: Prychantovskyi <75383953+Prychantovskyi@users.noreply.github.com> Date: Fri, 22 Jul 2022 09:53:56 +0100 Subject: [PATCH 10/19] Add files via upload --- .../common/spatial/test_cartesian_point.py | 26 +++++----- tests/unit/common/spatial/test_point.py | 18 +++---- tests/unit/common/spatial/test_wgs84_point.py | 48 +++++++++---------- 3 files changed, 46 insertions(+), 46 deletions(-) diff --git a/tests/unit/common/spatial/test_cartesian_point.py b/tests/unit/common/spatial/test_cartesian_point.py index 5ec40ac5e..8e22f1a9e 100644 --- a/tests/unit/common/spatial/test_cartesian_point.py +++ b/tests/unit/common/spatial/test_cartesian_point.py @@ -16,29 +16,29 @@ # limitations under the License. -from unittest import TestCase +import pytest from neo4j.spatial import CartesianPoint -class CartesianPointTestCase(TestCase): +class CartesianPointTestCase: def test_alias_3d(self): x, y, z = 3.2, 4.0, -1.2 p = CartesianPoint((x, y, z)) - self.assertTrue(hasattr(p, "x")) - self.assertEqual(p.x, x) - self.assertTrue(hasattr(p, "y")) - self.assertEqual(p.y, y) - self.assertTrue(hasattr(p, "z")) - self.assertEqual(p.z, z) + assert hasattr(p, "x") + assert p.x == x + assert hasattr(p, "y") + assert p.y == y + assert hasattr(p, "z") + assert p.z == z def test_alias_2d(self): x, y = 3.2, 4.0 p = CartesianPoint((x, y)) - self.assertTrue(hasattr(p, "x")) - self.assertEqual(p.x, x) - self.assertTrue(hasattr(p, "y")) - self.assertEqual(p.y, y) - with self.assertRaises(AttributeError): + assert hasattr(p, "x") + assert p.x == x + assert hasattr(p, "y") + assert p.y == y + with pytest.raises(AttributeError): p.z diff --git a/tests/unit/common/spatial/test_point.py b/tests/unit/common/spatial/test_point.py index 3122e2de3..0b46d25dc 100644 --- a/tests/unit/common/spatial/test_point.py +++ b/tests/unit/common/spatial/test_point.py @@ -16,7 +16,7 @@ # limitations under the License. -from unittest import TestCase +import pytest from neo4j._spatial import ( Point, @@ -24,17 +24,17 @@ ) -class PointTestCase(TestCase): +class PointTestCase: def test_wrong_type_arguments(self): for argument in (("a", "b"), ({"x": 1.0, "y": 2.0})): - with self.subTest(): - with self.assertRaises(ValueError): + with pytest.subtests(): + with pytest.raises(ValueError): Point(argument) def test_number_arguments(self): for argument in ((1, 2), (1.2, 2.1)): - with self.subTest(): + with pytest.subtests(): p = Point(argument) assert tuple(p) == argument @@ -42,11 +42,11 @@ def test_immutable_coordinates(self): MyPoint = point_type("MyPoint", ["x", "y"], {2: 1234}) coordinates = (.1, 0) p = MyPoint(coordinates) - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError): p.x = 2.0 - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError): p.y = 2.0 - with self.assertRaises(TypeError): + with pytest.raises(TypeError): p[0] = 2.0 - with self.assertRaises(TypeError): + with pytest.raises(TypeError): p[1] = 2.0 diff --git a/tests/unit/common/spatial/test_wgs84_point.py b/tests/unit/common/spatial/test_wgs84_point.py index 540cfd2c4..aad72ab6c 100644 --- a/tests/unit/common/spatial/test_wgs84_point.py +++ b/tests/unit/common/spatial/test_wgs84_point.py @@ -16,47 +16,47 @@ # limitations under the License. -from unittest import TestCase +import pytest from neo4j.spatial import WGS84Point -class WGS84PointTestCase(TestCase): +class WGS84PointTestCase: def test_alias_3d(self): x, y, z = 3.2, 4.0, -1.2 p = WGS84Point((x, y, z)) - self.assertTrue(hasattr(p, "longitude")) - self.assertEqual(p.longitude, x) - self.assertTrue(hasattr(p, "x")) - self.assertEqual(p.x, x) + assert hasattr(p, "longitude") + assert p.longitude == x + assert hasattr(p, "x") + assert p.x == x - self.assertTrue(hasattr(p, "latitude")) - self.assertEqual(p.latitude, y) - self.assertTrue(hasattr(p, "y")) - self.assertEqual(p.y, y) + assert hasattr(p, "latitude") + assert p.latitude == y + assert hasattr(p, "y") + assert p.y == y - self.assertTrue(hasattr(p, "height")) - self.assertEqual(p.height, z) - self.assertTrue(hasattr(p, "z")) - self.assertEqual(p.z, z) + assert hasattr(p, "height") + assert p.height == z + assert hasattr(p, "z") + assert p.z == z def test_alias_2d(self): x, y = 3.2, 4.0 p = WGS84Point((x, y)) - self.assertTrue(hasattr(p, "longitude")) - self.assertEqual(p.longitude, x) - self.assertTrue(hasattr(p, "x")) - self.assertEqual(p.x, x) + assert hasattr(p, "longitude") + assert p.longitude == x + assert hasattr(p, "x") + assert p.x == x - self.assertTrue(hasattr(p, "latitude")) - self.assertEqual(p.latitude, y) - self.assertTrue(hasattr(p, "y")) - self.assertEqual(p.y, y) + assert hasattr(p, "latitude") + assert p.latitude == y + assert hasattr(p, "y") + assert p.y == y - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError): p.height - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError): p.z From c77e2b35b33587b63016a6d0bb7c54b81c3932cd Mon Sep 17 00:00:00 2001 From: Prychantovskyi <75383953+Prychantovskyi@users.noreply.github.com> Date: Fri, 22 Jul 2022 09:54:36 +0100 Subject: [PATCH 11/19] Add files via upload --- neo4j/time/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/neo4j/time/__init__.py b/neo4j/time/__init__.py index d30d705e3..5d0edf244 100644 --- a/neo4j/time/__init__.py +++ b/neo4j/time/__init__.py @@ -253,6 +253,7 @@ class Clock: """ + _Clock__implementations = None __implementations = None def __new__(cls): From cf63125c7cbd2683fc254030bfcb40f1d2dbfd86 Mon Sep 17 00:00:00 2001 From: Prychantovskyi <75383953+Prychantovskyi@users.noreply.github.com> Date: Fri, 22 Jul 2022 10:38:12 +0100 Subject: [PATCH 12/19] Add files via upload --- neo4j/time/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/neo4j/time/__init__.py b/neo4j/time/__init__.py index 5d0edf244..d30d705e3 100644 --- a/neo4j/time/__init__.py +++ b/neo4j/time/__init__.py @@ -253,7 +253,6 @@ class Clock: """ - _Clock__implementations = None __implementations = None def __new__(cls): From 95b646073059fc0f93e609c2fc45956b711ff0e5 Mon Sep 17 00:00:00 2001 From: Prychantovskyi <75383953+Prychantovskyi@users.noreply.github.com> Date: Fri, 22 Jul 2022 10:53:57 +0100 Subject: [PATCH 13/19] Add files via upload --- tests/unit/common/time/test_clock.py | 1 - tests/unit/common/time/test_date.py | 16 +++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/unit/common/time/test_clock.py b/tests/unit/common/time/test_clock.py index 43b957548..c9fba6caa 100644 --- a/tests/unit/common/time/test_clock.py +++ b/tests/unit/common/time/test_clock.py @@ -16,7 +16,6 @@ # limitations under the License. - import pytest from neo4j.time._clock_implementations import ( diff --git a/tests/unit/common/time/test_date.py b/tests/unit/common/time/test_date.py index 5cb496767..deea78cdc 100644 --- a/tests/unit/common/time/test_date.py +++ b/tests/unit/common/time/test_date.py @@ -49,7 +49,7 @@ def test_zero_date(self): assert d.year == 0 assert d.month == 0 assert d.day == 0 - assert d == ZeroDate + assert d is ZeroDate def test_zero_ordinal(self): d = Date.from_ordinal(0) @@ -467,11 +467,13 @@ def test_date_not_equal_to_object(self): d1 = Date(2000, 1, 1) assert d1 != object() - def test_year_week_day(self): - for ordinal in range(Date(2001, 1, 1).to_ordinal(), - Date(2008, 1, 1).to_ordinal()): - assert Date.from_ordinal(ordinal).iso_calendar() \ - == date.fromordinal(ordinal).isocalendar() + @pytest.mark.parametrize("ordinal", ( + Date(2001, 1, 1).to_ordinal(), + Date(2008, 1, 1).to_ordinal(), + )) + def test_year_week_day(self, ordinal): + assert Date.from_ordinal(ordinal).iso_calendar() \ + == date.fromordinal(ordinal).isocalendar() def test_time_tuple(self): d = Date(2018, 4, 30) @@ -532,7 +534,7 @@ def test_from_iso_format(self): def test_date_copy(self): d = Date(2010, 10, 1) d2 = copy.copy(d) - assert d != d2 + assert d is not d2 assert d == d2 def test_date_deep_copy(self): From 9486c471616043637c75971c485861c632a6cde9 Mon Sep 17 00:00:00 2001 From: Prychantovskyi <75383953+Prychantovskyi@users.noreply.github.com> Date: Fri, 22 Jul 2022 10:54:19 +0100 Subject: [PATCH 14/19] Add files via upload --- tests/unit/common/spatial/test_point.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/tests/unit/common/spatial/test_point.py b/tests/unit/common/spatial/test_point.py index 0b46d25dc..a93af019e 100644 --- a/tests/unit/common/spatial/test_point.py +++ b/tests/unit/common/spatial/test_point.py @@ -26,17 +26,15 @@ class PointTestCase: - def test_wrong_type_arguments(self): - for argument in (("a", "b"), ({"x": 1.0, "y": 2.0})): - with pytest.subtests(): - with pytest.raises(ValueError): - Point(argument) - - def test_number_arguments(self): - for argument in ((1, 2), (1.2, 2.1)): - with pytest.subtests(): - p = Point(argument) - assert tuple(p) == argument + @pytest.mark.parametrize("argument", ("a", "b"), ({"x": 1.0, "y": 2.0})) + def test_wrong_type_arguments(self, argument): + with pytest.raises(ValueError): + Point(argument) + + @pytest.mark.parametrize((1, 2), (1.2, 2.1)) + def test_wrong_type_arguments(self, argument): + p = Point(argument) + assert tuple(p) == argument def test_immutable_coordinates(self): MyPoint = point_type("MyPoint", ["x", "y"], {2: 1234}) From a5da8651807ed35d4f7e13307997ee7368cd7e94 Mon Sep 17 00:00:00 2001 From: Prychantovskyi <75383953+Prychantovskyi@users.noreply.github.com> Date: Fri, 22 Jul 2022 10:56:17 +0100 Subject: [PATCH 15/19] Add files via upload --- tests/unit/common/spatial/test_point.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/common/spatial/test_point.py b/tests/unit/common/spatial/test_point.py index a93af019e..ddb313b25 100644 --- a/tests/unit/common/spatial/test_point.py +++ b/tests/unit/common/spatial/test_point.py @@ -32,7 +32,7 @@ def test_wrong_type_arguments(self, argument): Point(argument) @pytest.mark.parametrize((1, 2), (1.2, 2.1)) - def test_wrong_type_arguments(self, argument): + def test_number_arguments(self, argument): p = Point(argument) assert tuple(p) == argument From a206be7031d54ee8281d17cf6ae29b9a3266d55d Mon Sep 17 00:00:00 2001 From: Prychantovskyi <75383953+Prychantovskyi@users.noreply.github.com> Date: Fri, 22 Jul 2022 10:58:15 +0100 Subject: [PATCH 16/19] Add files via upload --- tests/unit/common/time/test_date.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/common/time/test_date.py b/tests/unit/common/time/test_date.py index deea78cdc..641f2d543 100644 --- a/tests/unit/common/time/test_date.py +++ b/tests/unit/common/time/test_date.py @@ -540,7 +540,7 @@ def test_date_copy(self): def test_date_deep_copy(self): d = Date(2010, 10, 1) d2 = copy.deepcopy(d) - assert d != d2 + assert d is not d2 assert d == d2 From f1cb90768dd28449e7e73b62cc9502cc3a2e06f8 Mon Sep 17 00:00:00 2001 From: Prychantovskyi <75383953+Prychantovskyi@users.noreply.github.com> Date: Fri, 22 Jul 2022 11:27:05 +0100 Subject: [PATCH 17/19] Add files via upload --- tests/unit/common/time/test_clocktime.py | 1 - tests/unit/common/time/test_date.py | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/unit/common/time/test_clocktime.py b/tests/unit/common/time/test_clocktime.py index 0d3d8077f..adb8554ca 100644 --- a/tests/unit/common/time/test_clocktime.py +++ b/tests/unit/common/time/test_clocktime.py @@ -16,7 +16,6 @@ # limitations under the License. - import pytest from neo4j.time import ( diff --git a/tests/unit/common/time/test_date.py b/tests/unit/common/time/test_date.py index 641f2d543..ac7c4d99b 100644 --- a/tests/unit/common/time/test_date.py +++ b/tests/unit/common/time/test_date.py @@ -57,7 +57,7 @@ def test_zero_ordinal(self): assert d.year == 0 assert d.month == 0 assert d.day == 0 - assert d == ZeroDate + assert d is ZeroDate def test_ordinal_at_start_of_1970(self): d = Date.from_ordinal(719163) @@ -238,7 +238,7 @@ def test_bad_from_clock_time(self): def test_is_leap_year(self): assert (Date.is_leap_year(2000)) - assert (Date.is_leap_year(2001)) + assert not (Date.is_leap_year(2001)) def test_days_in_year(self): assert Date.days_in_year(2000) == 366 @@ -477,8 +477,8 @@ def test_year_week_day(self, ordinal): def test_time_tuple(self): d = Date(2018, 4, 30) - assert d.time_tuple() == struct_time( - (2018, 4, 30, 0, 0, 0, 0, 120, -1)) + expected = struct_time((2018, 4, 30, 0, 0, 0, 0, 120, -1)) + assert d.time_tuple() == expected def test_to_clock_time(self): d = Date(2018, 4, 30) From d59e7c85df19fe3dd51ff9697038f3201570c258 Mon Sep 17 00:00:00 2001 From: Prychantovskyi <75383953+Prychantovskyi@users.noreply.github.com> Date: Fri, 22 Jul 2022 11:27:26 +0100 Subject: [PATCH 18/19] Add files via upload --- tests/unit/common/spatial/test_point.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/common/spatial/test_point.py b/tests/unit/common/spatial/test_point.py index ddb313b25..5ede95716 100644 --- a/tests/unit/common/spatial/test_point.py +++ b/tests/unit/common/spatial/test_point.py @@ -31,7 +31,7 @@ def test_wrong_type_arguments(self, argument): with pytest.raises(ValueError): Point(argument) - @pytest.mark.parametrize((1, 2), (1.2, 2.1)) + @pytest.mark.parametrize("argument", (1, 2), (1.2, 2.1)) def test_number_arguments(self, argument): p = Point(argument) assert tuple(p) == argument From 855c037311b51bd5d6ed45bc3f1a595969207a20 Mon Sep 17 00:00:00 2001 From: Prychantovskyi <75383953+Prychantovskyi@users.noreply.github.com> Date: Fri, 22 Jul 2022 11:39:47 +0100 Subject: [PATCH 19/19] Add files via upload --- tests/unit/common/time/test_date.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/common/time/test_date.py b/tests/unit/common/time/test_date.py index ac7c4d99b..f7d9c0c5e 100644 --- a/tests/unit/common/time/test_date.py +++ b/tests/unit/common/time/test_date.py @@ -237,8 +237,8 @@ def test_bad_from_clock_time(self): _ = Date.from_clock_time(object(), None) def test_is_leap_year(self): - assert (Date.is_leap_year(2000)) - assert not (Date.is_leap_year(2001)) + assert Date.is_leap_year(2000) + assert not Date.is_leap_year(2001) def test_days_in_year(self): assert Date.days_in_year(2000) == 366