Skip to content

Support serializing datetimes with datetime.timezone #914

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]

Expand Down
11 changes: 10 additions & 1 deletion src/neo4j/_codec/hydration/v1/temporal.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
datetime,
time,
timedelta,
timezone,
)

from ...._optional_deps import (
Expand All @@ -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)

Expand Down Expand Up @@ -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:
Expand Down
6 changes: 5 additions & 1 deletion src/neo4j/_codec/hydration/v2/temporal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand Down
25 changes: 25 additions & 0 deletions tests/unit/common/codec/hydration/v1/test_temporal_dehydration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
37 changes: 37 additions & 0 deletions tests/unit/common/codec/hydration/v2/test_temporal_dehydration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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))
Expand Down