diff --git a/pyproject.toml b/pyproject.toml index 08387ce8..b3ce62b0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ Homepage = "https://github.com/neo4j/neo4j-python-driver" [project.optional-dependencies] numpy = ["numpy >= 1.7.0, < 2.0.0"] pandas = [ - "pandas >= 1.1.0, < 2.0.0", + "pandas >= 1.1.0, < 3.0.0", "numpy >= 1.7.0, < 2.0.0", ] diff --git a/src/neo4j/_codec/hydration/v1/temporal.py b/src/neo4j/_codec/hydration/v1/temporal.py index d4796755..0ad5f8c7 100644 --- a/src/neo4j/_codec/hydration/v1/temporal.py +++ b/src/neo4j/_codec/hydration/v1/temporal.py @@ -20,6 +20,7 @@ datetime, time, timedelta, + timezone, ) from ...._optional_deps import ( @@ -38,6 +39,9 @@ from ...packstream import Structure +ANY_BUILTIN_DATETIME = datetime(1970, 1, 1) + + def get_date_unix_epoch(): return Date(1970, 1, 1) @@ -172,10 +176,15 @@ def seconds_and_nanoseconds(dt): seconds, nanoseconds = seconds_and_nanoseconds(value) return Structure(b"f", seconds, nanoseconds, tz.key) else: + if isinstance(tz, timezone): + # offset of the timezone is constant, so any date will do + offset = tz.utcoffset(ANY_BUILTIN_DATETIME) + else: + offset = tz.utcoffset(value) # with time offset seconds, nanoseconds = seconds_and_nanoseconds(value) return Structure(b"F", seconds, nanoseconds, - int(tz.utcoffset(value).total_seconds())) + int(offset.total_seconds())) if np is not None: diff --git a/src/neo4j/_codec/hydration/v2/temporal.py b/src/neo4j/_codec/hydration/v2/temporal.py index d15b3753..ab6135fe 100644 --- a/src/neo4j/_codec/hydration/v2/temporal.py +++ b/src/neo4j/_codec/hydration/v2/temporal.py @@ -83,8 +83,12 @@ def seconds_and_nanoseconds(dt): return Structure(b"i", seconds, nanoseconds, tz.key) else: # with time offset + if isinstance(tz, timezone): + # offset of the timezone is constant, so any date will do + offset = tz.utcoffset(datetime(1970, 1, 1)) + else: + offset = tz.utcoffset(value) seconds, nanoseconds = seconds_and_nanoseconds(value) - offset = tz.utcoffset(value) if offset.microseconds: raise ValueError("Bolt protocol does not support sub-second " "UTC offsets.") diff --git a/tests/unit/common/codec/hydration/v1/test_temporal_dehydration.py b/tests/unit/common/codec/hydration/v1/test_temporal_dehydration.py index c783cefe..350ba50b 100644 --- a/tests/unit/common/codec/hydration/v1/test_temporal_dehydration.py +++ b/tests/unit/common/codec/hydration/v1/test_temporal_dehydration.py @@ -129,6 +129,18 @@ def test_native_date_time_fixed_offset(self, assert_transforms): pytz.FixedOffset(60)) assert_transforms(dt, Structure(b"F", 1539344261, 474716000, 3600)) + def test_date_time_fixed_native_offset(self, assert_transforms): + dt = DateTime(2018, 10, 12, 11, 37, 41, 474716862, + datetime.timezone(datetime.timedelta(minutes=60))) + assert_transforms(dt, Structure(b"F", 1539344261, 474716862, 3600)) + + def test_native_date_time_fixed_native_offset(self, assert_transforms): + dt = datetime.datetime( + 2018, 10, 12, 11, 37, 41, 474716, + datetime.timezone(datetime.timedelta(minutes=60)) + ) + assert_transforms(dt, Structure(b"F", 1539344261, 474716000, 3600)) + def test_pandas_date_time_fixed_offset(self, assert_transforms): dt = pd.Timestamp("2018-10-12T11:37:41.474716862+0100") assert_transforms(dt, Structure(b"F", 1539344261, 474716862, 3600)) @@ -143,6 +155,19 @@ def test_native_date_time_fixed_negative_offset(self, assert_transforms): pytz.FixedOffset(-60)) assert_transforms(dt, Structure(b"F", 1539344261, 474716000, -3600)) + def test_date_time_fixed_negative_native_offset(self, assert_transforms): + dt = DateTime(2018, 10, 12, 11, 37, 41, 474716862, + datetime.timezone(datetime.timedelta(minutes=-60))) + assert_transforms(dt, Structure(b"F", 1539344261, 474716862, -3600)) + + def test_native_date_time_fixed_negative_native_offset(self, + assert_transforms): + dt = datetime.datetime( + 2018, 10, 12, 11, 37, 41, 474716, + datetime.timezone(datetime.timedelta(minutes=-60)) + ) + assert_transforms(dt, Structure(b"F", 1539344261, 474716000, -3600)) + def test_pandas_date_time_fixed_negative_offset(self, assert_transforms): dt = pd.Timestamp("2018-10-12T11:37:41.474716862-0100") assert_transforms(dt, Structure(b"F", 1539344261, 474716862, -3600)) diff --git a/tests/unit/common/codec/hydration/v1/test_temporal_dehydration_utc_patch.py b/tests/unit/common/codec/hydration/v1/test_temporal_dehydration_utc_patch.py index 66ba4b0f..4d04156f 100644 --- a/tests/unit/common/codec/hydration/v1/test_temporal_dehydration_utc_patch.py +++ b/tests/unit/common/codec/hydration/v1/test_temporal_dehydration_utc_patch.py @@ -34,9 +34,13 @@ def __new__(mcs, name, bases, attrs): for test_func in ( "test_date_time_fixed_offset", "test_native_date_time_fixed_offset", + "test_date_time_fixed_native_offset", + "test_native_date_time_fixed_native_offset", "test_pandas_date_time_fixed_offset", "test_date_time_fixed_negative_offset", "test_native_date_time_fixed_negative_offset", + "test_date_time_fixed_negative_native_offset", + "test_native_date_time_fixed_negative_native_offset", "test_pandas_date_time_fixed_negative_offset", "test_date_time_zone_id", "test_native_date_time_zone_id", diff --git a/tests/unit/common/codec/hydration/v2/test_temporal_dehydration.py b/tests/unit/common/codec/hydration/v2/test_temporal_dehydration.py index 7cad75d5..79c1d894 100644 --- a/tests/unit/common/codec/hydration/v2/test_temporal_dehydration.py +++ b/tests/unit/common/codec/hydration/v2/test_temporal_dehydration.py @@ -52,6 +52,24 @@ def test_native_date_time_fixed_offset(self, assert_transforms): Structure(b"I", 1539340661, 474716000, 3600) ) + def test_date_time_fixed_native_offset(self, assert_transforms): + dt = DateTime(2018, 10, 12, 11, 37, 41, 474716862, + datetime.timezone(datetime.timedelta(minutes=60))) + assert_transforms( + dt, + Structure(b"I", 1539340661, 474716862, 3600) + ) + + def test_native_date_time_fixed_native_offset(self, assert_transforms): + dt = datetime.datetime( + 2018, 10, 12, 11, 37, 41, 474716, + datetime.timezone(datetime.timedelta(minutes=60)) + ) + assert_transforms( + dt, + Structure(b"I", 1539340661, 474716000, 3600) + ) + def test_pandas_date_time_fixed_offset(self, assert_transforms): dt = pd.Timestamp("2018-10-12T11:37:41.474716862+0100") assert_transforms(dt, Structure(b"I", 1539340661, 474716862, 3600)) @@ -72,6 +90,25 @@ def test_native_date_time_fixed_negative_offset(self, assert_transforms): Structure(b"I", 1539347861, 474716000, -3600) ) + def test_date_time_fixed_negative_native_offset(self, assert_transforms): + dt = DateTime(2018, 10, 12, 11, 37, 41, 474716862, + datetime.timezone(datetime.timedelta(minutes=-60))) + assert_transforms( + dt, + Structure(b"I", 1539347861, 474716862, -3600) + ) + + def test_native_date_time_fixed_negative_native_offset(self, + assert_transforms): + dt = datetime.datetime( + 2018, 10, 12, 11, 37, 41, 474716, + datetime.timezone(datetime.timedelta(minutes=-60)) + ) + assert_transforms( + dt, + Structure(b"I", 1539347861, 474716000, -3600) + ) + def test_pandas_date_time_fixed_negative_offset(self, assert_transforms): dt = pd.Timestamp("2018-10-12T11:37:41.474716862-0100") assert_transforms(dt, Structure(b"I", 1539347861, 474716862, -3600))