diff --git a/neo4j/time/hydration.py b/neo4j/time/hydration.py index 313ed161..0edbe610 100644 --- a/neo4j/time/hydration.py +++ b/neo4j/time/hydration.py @@ -101,7 +101,8 @@ def dehydrate_time(value): else: raise TypeError("Value must be a neo4j.time.Time or a datetime.time") if value.tzinfo: - return Structure(b"T", nanoseconds, value.tzinfo.utcoffset(value).seconds) + return Structure(b"T", nanoseconds, + int(value.tzinfo.utcoffset(value).total_seconds())) else: return Structure(b"t", nanoseconds) diff --git a/tests/integration/examples/test_temporal_types_example.py b/tests/integration/examples/test_temporal_types_example.py new file mode 100644 index 00000000..c2b61734 --- /dev/null +++ b/tests/integration/examples/test_temporal_types_example.py @@ -0,0 +1,316 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +# Copyright (c) "Neo4j" +# Neo4j Sweden AB [http://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 +# +# http://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. + +# python -m pytest tests/integration/examples/test_temporal_types_example.py -s -v + + +def _echo(tx, x): + return tx.run("RETURN $x AS fieldName", x=x).single() + + +def test_datetime(driver): + # tag::temporal-types-datetime-import[] + from datetime import datetime + + from neo4j.time import DateTime + import pytz + # end::temporal-types-datetime-import[] + + # tag::temporal-types-datetime[] + # Create datetimes to be used as query parameters + # Python's builtin datetimes works as well. However, they don't support + # the full feature-set of Neo4j's durations: it has no nanosecond precision. + py_dt = datetime(2021, month=11, day=2, hour=7, minute=47, microsecond=4) + py_dt = pytz.timezone("US/Eastern").localize(py_dt) + + # A DateTime can be created from a native datetime + dt = DateTime.from_native(py_dt) + # or directly + dt = DateTime(year=2021, month=11, day=2, hour=7, minute=47, + second=0.000004123) + dt = pytz.timezone("US/Eastern").localize(dt) + # end::temporal-types-datetime[] + + in_dt = dt # stored for later assertions + + with driver.session() as session: + record = session.read_transaction(_echo, dt) + + # tag::temporal-types-datetime[] + + # Reading a DateTime from a record + dt = record.get("fieldName") # type: DateTime + str(dt) # '2021-11-02T07:47:09.232260000-04:00' + + # converting DateTime to native datetime (lossy) + native = dt.to_native() # type: datetime + # end::temporal-types-datetime[] + + assert isinstance(dt, DateTime) + assert str(dt) == "2021-11-02T07:47:00.000004123-04:00" + assert dt == in_dt + assert isinstance(native, datetime) + assert native == py_dt + + with driver.session() as session: + record = session.read_transaction(_echo, py_dt) + + dt = record.get("fieldName") + assert isinstance(dt, DateTime) + assert dt == in_dt.to_native() + + +def test_date(driver): + # tag::temporal-types-date-import[] + from datetime import date + + from neo4j.time import Date + # end::temporal-types-date-import[] + + # tag::temporal-types-date[] + # Create dates to be used as query parameters + # Python's builtin dates works as well. + py_d = date(year=2021, month=11, day=2) + + # A Date can be created from a native date + d = Date.from_native(py_d) + # or directly + d = Date(year=2021, month=11, day=2) + # end::temporal-types-date[] + + assert d == Date.from_native(py_d) + + in_d = d # stored for later assertions + + with driver.session() as session: + record = session.read_transaction(_echo, d) + + # tag::temporal-types-date[] + + # Reading a Date from a record + d = record.get("fieldName") # type: Date + str(d) # '2021-11-02' + + # converting Date to native date + native = d.to_native() # type: date + # end::temporal-types-date[] + + assert isinstance(d, Date) + assert str(d) == "2021-11-02" + assert d == in_d + assert isinstance(native, date) + assert native == py_d + + with driver.session() as session: + record = session.read_transaction(_echo, py_d) + + d = record.get("fieldName") + assert isinstance(d, Date) + assert d == in_d.to_native() + + +def test_time(driver): + # tag::temporal-types-time-import[] + from datetime import time + + from neo4j.time import Time + import pytz + # end::temporal-types-time-import[] + + # tag::temporal-types-time[] + # Create datetimes to be used as query parameters + # Python's builtin datetimes works as well. However, they don't support + # the full feature-set of Neo4j's durations: it has no nanosecond precision. + py_t = time(hour=7, minute=47, microsecond=4, tzinfo=pytz.FixedOffset(-240)) + + # A Time can be created from a native time + t = Time.from_native(py_t) + # or directly + t = Time(hour=7, minute=47, second=0.000004123, + tzinfo=pytz.FixedOffset(-240)) + # end::temporal-types-time[] + + in_t = t # stored for later assertions + + with driver.session() as session: + record = session.read_transaction(_echo, t) + + # tag::temporal-types-time[] + + # Reading a Time from a record + t = record.get("fieldName") # type: Time + str(t) # 'T07:47:09.232260000-04:00' + + # converting Time to native time (lossy) + native = t.to_native() # type: time + # end::temporal-types-time[] + + assert isinstance(t, Time) + assert str(t) == "07:47:00.000004123-04:00" + assert t == in_t + assert isinstance(native, time) + assert native == py_t + + with driver.session() as session: + record = session.read_transaction(_echo, py_t) + + t = record.get("fieldName") + assert isinstance(t, Time) + assert t == in_t.to_native() + + +def test_local_datetime(driver): + # tag::temporal-types-local-datetime-import[] + from datetime import datetime + + from neo4j.time import DateTime + import pytz + # end::temporal-types-local-datetime-import[] + + # tag::temporal-types-local-datetime[] + # Create datetimes to be used as query parameters + # Python's builtin datetimes works as well. However, they don't support + # the full feature-set of Neo4j's durations: it has no nanosecond precision. + py_dt = datetime(2021, month=11, day=2, hour=7, minute=47, microsecond=4) + + # A DateTime can be created from a native datetime + dt = DateTime.from_native(py_dt) + # or directly + dt = DateTime(year=2021, month=11, day=2, hour=7, minute=47, + second=0.000004123) + # end::temporal-types-local-datetime[] + + in_dt = dt # stored for later assertions + + with driver.session() as session: + record = session.read_transaction(_echo, dt) + + # tag::temporal-types-local-datetime[] + + # Reading a DateTime from a record + dt = record.get("fieldName") # type: DateTime + str(dt) # '2021-11-02T07:47:09.232260000' + + # converting DateTime to native datetime (lossy) + native = dt.to_native() # type: datetime + # end::temporal-types-local-datetime[] + + assert isinstance(dt, DateTime) + assert str(dt) == "2021-11-02T07:47:00.000004123" + assert dt == in_dt + assert isinstance(native, datetime) + assert native == py_dt + + with driver.session() as session: + record = session.read_transaction(_echo, py_dt) + + dt = record.get("fieldName") + assert isinstance(dt, DateTime) + assert dt == in_dt.to_native() + + +def test_local_time(driver): + # tag::temporal-types-local-time-import[] + from datetime import time + + from neo4j.time import Time + import pytz + # end::temporal-types-local-time-import[] + + # tag::temporal-types-local-time[] + # Create datetimes to be used as query parameters + # Python's builtin datetimes works as well. However, they don't support + # the full feature-set of Neo4j's durations: it has no nanosecond precision. + py_t = time(hour=7, minute=47, microsecond=4) + + # A Time can be created from a native time + t = Time.from_native(py_t) + # or directly + t = Time(hour=7, minute=47, second=0.000004123) + # end::temporal-types-local-time[] + + in_t = t # stored for later assertions + + with driver.session() as session: + record = session.read_transaction(_echo, t) + + # tag::temporal-types-local-time[] + + # Reading a Time from a record + t = record.get("fieldName") # type: Time + str(t) # 'T07:47:09.232260000' + + # converting Time to native time (lossy) + native = t.to_native() # type: time + # end::temporal-types-local-time[] + + assert isinstance(t, Time) + assert str(t) == "07:47:00.000004123" + assert t == in_t + assert isinstance(native, time) + assert native == py_t + + with driver.session() as session: + record = session.read_transaction(_echo, py_t) + + t = record.get("fieldName") + assert isinstance(t, Time) + assert t == in_t.to_native() + + +def test_duration_example(driver): + # tag::temporal-types-duration-import[] + from datetime import timedelta + + from neo4j.time import Duration + # end::temporal-types-duration-import[] + + # tag::temporal-types-duration[] + # Creating durations to be used as query parameters + duration = Duration(years=1, days=2, seconds=3, nanoseconds=4) + # Python's builtin timedeltas works as well. However, they don't support + # the full feature-set of Neo4j's durations, + # e.g., no nanoseconds and no months. + py_duration = timedelta(days=2, seconds=3, microseconds=4) + # end::temporal-types-duration[] + + in_duration = duration # stored for later assertions + + with driver.session() as session: + record = session.read_transaction(_echo, duration) + + # tag::temporal-types-duration[] + + # Reading a Duration from a record + duration = record.get("fieldName") # type: Duration + str(duration) # 'P1Y2DT3.000000004S' + # end::temporal-types-duration[] + + assert isinstance(duration, Duration) + assert str(duration) == 'P1Y2DT3.000000004S' + assert duration == in_duration + + with driver.session() as session: + record = session.read_transaction(_echo, py_duration) + + duration = record.get("fieldName") + assert isinstance(duration, Duration) + assert str(duration) == 'P2DT3.000004S' + assert Duration() + py_duration == duration