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())
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): _ = Date(1976, 6, 31)
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): _ = Date(1977, 2, 29)
def _hydrate_date(self, days): """ Hydrator for `Date` values. :param days: :return: Date """ return Date.from_ordinal(UNIX_EPOCH_DATE_ORDINAL + days)
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)
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)
def hydrate_date(days): """ Hydrator for `Date` values. :param days: :return: Date """ return Date.from_ordinal(unix_epoch_date_ordinal + days)
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))
def test_can_retain_offset_from_end_of_month(self): d = Date(1976, 1, -1) self.assertEqual(d, Date(1976, 1, 31)) d += Duration(months=1) self.assertEqual(d, Date(1976, 2, 29)) d += Duration(months=1) self.assertEqual(d, Date(1976, 3, 31)) d += Duration(months=1) self.assertEqual(d, Date(1976, 4, 30)) d += Duration(months=1) self.assertEqual(d, Date(1976, 5, 31)) d += Duration(months=1) self.assertEqual(d, Date(1976, 6, 30))
def hydrate_datetime(seconds, nanoseconds, tz=None): """ Hydrator for `DateTime` and `LocalDateTime` values. :param seconds: :param nanoseconds: :param tz: :return: datetime """ minutes, seconds = map(int, divmod(seconds, 60)) hours, minutes = map(int, divmod(minutes, 60)) days, hours = map(int, divmod(hours, 24)) seconds = (1000000000 * seconds + nanoseconds) / 1000000000 t = DateTime.combine(Date.from_ordinal(UNIX_EPOCH_DATE_ORDINAL + days), Time(hours, minutes, seconds)) if tz is None: return t if isinstance(tz, int): tz_offset_minutes, tz_offset_seconds = divmod(tz, 60) zone = FixedOffset(tz_offset_minutes) else: zone = timezone(tz) return zone.localize(t)
def test_iso_weekday(self): d = Date(2018, 4, 30) self.assertEqual(d.iso_weekday(), 1)
def _hydrate(self, obj, inst=None, version=None): from neotime import Duration, Date, Time, DateTime from pytz import FixedOffset, timezone from py2neo.data import Node, Relationship, Path from py2neo.data.spatial import Point unbound_relationship = namedtuple("UnboundRelationship", ["id", "type", "properties"]) unix_epoch_date = Date(1970, 1, 1) unix_epoch_date_ordinal = unix_epoch_date.to_ordinal() def hydrate_object(o): if isinstance(o, Structure): tag = o.tag if isinstance(o.tag, bytes) else bytes( bytearray([o.tag])) try: f = functions[tag] except KeyError: # If we don't recognise the structure type, just return it as-is return o else: return f(*o.fields) elif isinstance(o, list): return list(map(hydrate_object, o)) elif isinstance(o, dict): return {key: hydrate_object(value) for key, value in o.items()} else: return o def hydrate_node(identity, labels, properties): return Node.hydrate(self.graph, identity, labels, hydrate_object(properties), into=inst) def hydrate_relationship(identity, start_node_id, end_node_id, r_type, properties): return Relationship.hydrate(self.graph, identity, start_node_id, end_node_id, r_type, hydrate_object(properties), into=inst) def hydrate_path(nodes, relationships, sequence): nodes = [ Node.hydrate(self.graph, n_id, n_label, hydrate_object(n_properties)) for n_id, n_label, n_properties in nodes ] u_rels = [] for r_id, r_type, r_properties in relationships: u_rel = unbound_relationship(r_id, r_type, hydrate_object(r_properties)) u_rels.append(u_rel) return Path.hydrate(self.graph, nodes, u_rels, sequence) def hydrate_date(days): """ Hydrator for `Date` values. :param days: :return: Date """ return Date.from_ordinal(unix_epoch_date_ordinal + days) def hydrate_time(nanoseconds, tz=None): """ Hydrator for `Time` and `LocalTime` values. :param nanoseconds: :param tz: :return: Time """ seconds, nanoseconds = map(int, divmod(nanoseconds, 1000000000)) minutes, seconds = map(int, divmod(seconds, 60)) hours, minutes = map(int, divmod(minutes, 60)) seconds = (1000000000 * seconds + nanoseconds) / 1000000000 t = Time(hours, minutes, seconds) if tz is None: return t tz_offset_minutes, tz_offset_seconds = divmod(tz, 60) zone = FixedOffset(tz_offset_minutes) return zone.localize(t) def hydrate_datetime(seconds, nanoseconds, tz=None): """ Hydrator for `DateTime` and `LocalDateTime` values. :param seconds: :param nanoseconds: :param tz: :return: datetime """ minutes, seconds = map(int, divmod(seconds, 60)) hours, minutes = map(int, divmod(minutes, 60)) days, hours = map(int, divmod(hours, 24)) seconds = (1000000000 * seconds + nanoseconds) / 1000000000 t = DateTime.combine( Date.from_ordinal(unix_epoch_date_ordinal + days), Time(hours, minutes, seconds)) if tz is None: return t if isinstance(tz, int): tz_offset_minutes, tz_offset_seconds = divmod(tz, 60) zone = FixedOffset(tz_offset_minutes) else: zone = timezone(tz) return zone.localize(t) def hydrate_duration(months, days, seconds, nanoseconds): """ Hydrator for `Duration` values. :param months: :param days: :param seconds: :param nanoseconds: :return: `duration` namedtuple """ return Duration(months=months, days=days, seconds=seconds, nanoseconds=nanoseconds) def hydrate_point(srid, *coordinates): """ Create a new instance of a Point subclass from a raw set of fields. The subclass chosen is determined by the given SRID; a ValueError will be raised if no such subclass can be found. """ try: point_class, dim = Point.class_for_srid(srid) except KeyError: point = Point(coordinates) point.srid = srid return point else: if len(coordinates) != dim: raise ValueError( "SRID %d requires %d coordinates (%d provided)" % (srid, dim, len(coordinates))) return point_class(coordinates) functions = { b"N": hydrate_node, b"R": hydrate_relationship, b"P": hydrate_path, } if version >= (2, 0): functions.update({ b"D": hydrate_date, b"T": hydrate_time, # time zone offset b"t": hydrate_time, # no time zone b"F": hydrate_datetime, # time zone offset b"f": hydrate_datetime, # time zone name b"d": hydrate_datetime, # no time zone b"E": hydrate_duration, b"X": hydrate_point, b"Y": hydrate_point, }) return hydrate_object(obj)
def test_date(cls): from neotime import Date b, unpacked = pack_and_unpack(cls(1970, 1, 1), version=(2, 0)) assert b == b"\xB1D\x00" assert unpacked == Date(1970, 1, 1)
def dehydrate(self, data, version=None): """ Dehydrate to PackStream. """ from datetime import date, time, datetime, timedelta from neotime import Duration, Date, Time, DateTime from pytz import utc from py2neo.data.spatial import Point unix_epoch_date = Date(1970, 1, 1) if version is None: v = (1, 0) elif isinstance(version, tuple): v = version else: v = (version, 0) def dehydrate_object(x): t = type(x) if t in functions: f = functions[t] return f(x) elif x is None or x is True or x is False or isinstance(x, float) or isinstance(x, string_types): return x elif isinstance(x, integer_types): if x < INT64_MIN or x > INT64_MAX: raise ValueError("Integers must be within the signed 64-bit range") return x elif isinstance(x, bytearray): return x elif isinstance(x, Mapping): d = {} for key in x: if not isinstance(key, string_types): raise TypeError("Dictionary keys must be strings") d[key] = dehydrate_object(x[key]) return d elif isinstance(x, Sequence): return list(map(dehydrate_object, x)) else: raise TypeError("PackStream parameters of type %s are not supported" % type(x).__name__) def dehydrate_date(value): """ Dehydrator for `date` values. :param value: :type value: Date :return: """ return Structure(b"D", value.toordinal() - unix_epoch_date.toordinal()) def dehydrate_time(value): """ Dehydrator for `time` values. :param value: :type value: Time :return: """ if isinstance(value, Time): nanoseconds = int(value.ticks * 1000000000) elif isinstance(value, time): nanoseconds = (3600000000000 * value.hour + 60000000000 * value.minute + 1000000000 * value.second + 1000 * value.microsecond) else: raise TypeError("Value must be a neotime.Time or a datetime.time") if value.tzinfo: return Structure(b"T", nanoseconds, value.tzinfo.utcoffset(value).seconds) else: return Structure(b"t", nanoseconds) def dehydrate_datetime(value): """ Dehydrator for `datetime` values. :param value: :type value: datetime :return: """ def seconds_and_nanoseconds(dt): if isinstance(dt, datetime): dt = DateTime.from_native(dt) zone_epoch = DateTime(1970, 1, 1, tzinfo=dt.tzinfo) t = dt.to_clock_time() - zone_epoch.to_clock_time() return t.seconds, t.nanoseconds tz = value.tzinfo if tz is None: # without time zone value = utc.localize(value) seconds, nanoseconds = seconds_and_nanoseconds(value) return Structure(b"d", seconds, nanoseconds) elif hasattr(tz, "zone") and tz.zone: # with named time zone seconds, nanoseconds = seconds_and_nanoseconds(value) return Structure(b"f", seconds, nanoseconds, tz.zone) else: # with time offset seconds, nanoseconds = seconds_and_nanoseconds(value) return Structure(b"F", seconds, nanoseconds, tz.utcoffset(value).seconds) def dehydrate_duration(value): """ Dehydrator for `duration` values. :param value: :type value: Duration :return: """ return Structure(b"E", value.months, value.days, value.seconds, int(1000000000 * value.subseconds)) def dehydrate_timedelta(value): """ Dehydrator for `timedelta` values. :param value: :type value: timedelta :return: """ months = 0 days = value.days seconds = value.seconds nanoseconds = 1000 * value.microseconds return Structure(b"E", months, days, seconds, nanoseconds) def dehydrate_point(value): """ Dehydrator for Point data. :param value: :type value: Point :return: """ dim = len(value) if dim == 2: return Structure(b"X", value.srid, *value) elif dim == 3: return Structure(b"Y", value.srid, *value) else: raise ValueError("Cannot dehydrate Point with %d dimensions" % dim) functions = {} # graph types cannot be used as parameters if v >= (2, 0): functions.update({ Date: dehydrate_date, date: dehydrate_date, Time: dehydrate_time, time: dehydrate_time, DateTime: dehydrate_datetime, datetime: dehydrate_datetime, Duration: dehydrate_duration, timedelta: dehydrate_timedelta, Point: dehydrate_point, }) functions.update({ cls: dehydrate_point for cls in Point.__subclasses__() }) return dehydrate_object(data)
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)
def test_date_greater_than_object(self): d = Date(2000, 1, 1) with self.assertRaises(TypeError): _ = d > object()
def test_date_equal(self): d1 = Date(2000, 1, 1) d2 = Date(2000, 1, 1) self.assertEqual(d1, d2)
def test_date(graph): skip_if_no_temporal_support(graph) i = Date(2014, 8, 6) o = graph.evaluate("RETURN $x", x=i) assert o == i
def test_date_not_equal(self): d1 = Date(2000, 1, 1) d2 = Date(2000, 1, 2) self.assertNotEqual(d1, d2)
# See the License for the specific language governing permissions and # limitations under the License. from __future__ import division """ This module defines temporal data types. """ from datetime import date, time, datetime, timedelta from neotime import Duration, Date, Time, DateTime from pytz import FixedOffset, timezone, utc from neobolt.packstream import Structure UNIX_EPOCH_DATE = Date(1970, 1, 1) UNIX_EPOCH_DATETIME_UTC = DateTime(1970, 1, 1, 0, 0, 0, utc) def hydrate_date(days): """ Hydrator for `Date` values. :param days: :return: Date """ return UNIX_EPOCH_DATE + Duration(days=days) def dehydrate_date(value): """ Dehydrator for `date` values.
def test_weekday(self): d = Date(2018, 4, 30) self.assertEqual(d.weekday(), 0)
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): _ = d.to_clock_time(object())
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)))
def test_date_not_equal_to_object(self): d1 = Date(2000, 1, 1) self.assertNotEqual(d1, object())
def test_date_property(self): a = Node(d=Date(1970, 1, 1)) r = cypher_repr(a) self.assertEqual("({d: date('1970-01-01')})", r)
def test_str(self): self.assertEqual(str(Date(2018, 4, 30)), "2018-04-30") self.assertEqual(str(Date(0, 0, 0)), "0000-00-00")
PACKED_UINT_8 = [struct_pack(">B", value) for value in range(0x100)] PACKED_UINT_16 = [struct_pack(">H", value) for value in range(0x10000)] UNPACKED_UINT_8 = {bytes(bytearray([x])): x for x in range(0x100)} UNPACKED_UINT_16 = {struct_pack(">H", x): x for x in range(0x10000)} UNPACKED_MARKERS = {b"\xC0": None, b"\xC2": False, b"\xC3": True} UNPACKED_MARKERS.update({bytes(bytearray([z])): z for z in range(0x00, 0x80)}) UNPACKED_MARKERS.update({bytes(bytearray([z + 256])): z for z in range(-0x10, 0x00)}) INT64_MIN = -(2 ** 63) INT64_MAX = 2 ** 63 UNIX_EPOCH_DATE_ORDINAL = Date(1970, 1, 1).to_ordinal() unbound_relationship = namedtuple("UnboundRelationship", ["id", "type", "properties"]) class Structure(object): def __init__(self, tag, *fields): self.tag = tag self.fields = list(fields) def __repr__(self): return "Structure[#%02X](%s)" % (self.tag, ", ".join(map(repr, self.fields))) def __eq__(self, other):
def test_repr(self): self.assertEqual(repr(Date(2018, 4, 30)), "neotime.Date(2018, 4, 30)") self.assertEqual(repr(Date(0, 0, 0)), "neotime.ZeroDate")
def pack_into(buffer, *values, **kwargs): """ Pack values into a buffer. :param buffer: :param values: :param kwargs: :return: """ from datetime import date, time, datetime, timedelta from neotime import Date, Time, DateTime, Duration from pytz import utc from py2neo.data.spatial import Point version = kwargs.get("version", ()) unix_epoch_date = Date(1970, 1, 1) write_bytes = buffer.write def write_header(size, tiny, small=None, medium=None, large=None): if 0x0 <= size <= 0xF and tiny is not None: write_bytes(bytearray([tiny + size])) elif size < 0x100 and small is not None: write_bytes(bytearray([small])) write_bytes(PACKED_UINT_8[size]) elif size < 0x10000 and medium is not None: write_bytes(bytearray([medium])) write_bytes(PACKED_UINT_16[size]) elif size < 0x100000000 and large is not None: write_bytes(bytearray([large])) write_bytes(struct_pack(">I", size)) else: raise ValueError("Collection too large") def write_time(t): try: nanoseconds = int(t.ticks * 1000000000) except AttributeError: nanoseconds = (3600000000000 * t.hour + 60000000000 * t.minute + 1000000000 * t.second + 1000 * t.microsecond) if t.tzinfo: write_bytes(b"\xB2T") pack_into(buffer, nanoseconds, t.tzinfo.utcoffset(t).seconds) else: write_bytes(b"\xB1t") pack_into(buffer, nanoseconds) def seconds_and_nanoseconds(dt): if isinstance(dt, datetime): dt = DateTime.from_native(dt) zone_epoch = DateTime(1970, 1, 1, tzinfo=dt.tzinfo) t = dt.to_clock_time() - zone_epoch.to_clock_time() return t.seconds, t.nanoseconds def write_datetime(dt): tz = dt.tzinfo if tz is None: # without time zone local_dt = utc.localize(dt) seconds, nanoseconds = seconds_and_nanoseconds(local_dt) write_bytes(b"\xB2d") pack_into(buffer, seconds, nanoseconds) elif hasattr(tz, "zone") and tz.zone: # with named time zone seconds, nanoseconds = seconds_and_nanoseconds(dt) write_bytes(b"\xB3f") pack_into(buffer, seconds, nanoseconds, tz.zone) else: # with time offset seconds, nanoseconds = seconds_and_nanoseconds(dt) write_bytes(b"\xB3F") pack_into(buffer, seconds, nanoseconds, tz.utcoffset(dt).seconds) def write_point(p): dim = len(p) write_bytes(bytearray([0xB1 + dim])) if dim == 2: write_bytes(b"X") elif dim == 3: write_bytes(b"Y") else: raise ValueError("Cannot dehydrate Point with %d dimensions" % dim) pack_into(buffer, p.srid, *p) for value in values: # None if value is None: write_bytes(b"\xC0") # NULL # Boolean elif value is True: write_bytes(b"\xC3") elif value is False: write_bytes(b"\xC2") # Float (only double precision is supported) elif isinstance(value, float): write_bytes(b"\xC1") write_bytes(struct_pack(">d", value)) # Integer elif isinstance(value, integer_types): if -0x10 <= value < 0x80: write_bytes(PACKED_UINT_8[value % 0x100]) elif -0x80 <= value < -0x10: write_bytes(b"\xC8") write_bytes(PACKED_UINT_8[value % 0x100]) elif -0x8000 <= value < 0x8000: write_bytes(b"\xC9") write_bytes(PACKED_UINT_16[value % 0x10000]) elif -0x80000000 <= value < 0x80000000: write_bytes(b"\xCA") write_bytes(struct_pack(">i", value)) elif INT64_MIN <= value < INT64_MAX: write_bytes(b"\xCB") write_bytes(struct_pack(">q", value)) else: raise ValueError("Integer %s out of range" % value) # String (from bytes) elif isinstance(value, bytes): write_header(len(value), 0x80, 0xD0, 0xD1, 0xD2) write_bytes(value) # String (from unicode) elif isinstance(value, UNICODE): encoded = value.encode("utf-8") write_header(len(encoded), 0x80, 0xD0, 0xD1, 0xD2) write_bytes(encoded) # Byte array elif isinstance(value, bytes_types): write_header(len(value), None, 0xCC, 0xCD, 0xCE) write_bytes(bytes(value)) # List elif isinstance(value, list) or type(value) is tuple: write_header(len(value), 0x90, 0xD4, 0xD5, 0xD6) pack_into(buffer, *value, version=version) # Dictionary elif isinstance(value, dict): write_header(len(value), 0xA0, 0xD8, 0xD9, 0xDA) for key, item in value.items(): if isinstance(key, (bytes, UNICODE)): pack_into(buffer, key, item, version=version) else: raise TypeError("Dictionary key {!r} is not a string".format(key)) # Bolt 2 introduced temporal and spatial types elif version < (2, 0): raise TypeError("Values of type %s are not supported " "by Bolt %s" % (type(value), ".".join(version))) # DateTime # # Note: The built-in datetime.datetime class extends the # datetime.date class, so this needs to be listed first # to avoid objects being encoded incorrectly. # elif isinstance(value, (datetime, DateTime)): write_datetime(value) # Date elif isinstance(value, (date, Date)): write_bytes(b"\xB1D") pack_into(buffer, value.toordinal() - unix_epoch_date.toordinal()) # Time elif isinstance(value, (time, Time)): write_time(value) # TimeDelta elif isinstance(value, timedelta): write_bytes(b"\xB4E") pack_into(buffer, 0, # months value.days, # days value.seconds, # seconds 1000 * value.microseconds) # nanoseconds # Duration elif isinstance(value, Duration): write_bytes(b"\xB4E") pack_into(buffer, value.months, # months value.days, # days value.seconds, # seconds int(1000000000 * value.subseconds)) # nanoseconds # Point elif isinstance(value, Point): write_point(value) # Other else: raise TypeError("Values of type %s are not supported" % type(value))
def test_format(self): d = Date(2018, 4, 30) with self.assertRaises(NotImplementedError): _ = d.__format__("")