From 0a26f45564be84bccabf1823213576c215de280d Mon Sep 17 00:00:00 2001 From: martin bendsoe Date: Tue, 9 Jun 2020 13:09:12 +0200 Subject: [PATCH 1/3] fixed time zone info --- neo4j/time/__init__.py | 40 ++++++++++++++--------- tests/unit/time/test_datetime.py | 35 ++++++++++++++++++-- tests/unit/time/test_time.py | 55 +++++++++++++++++++++++++++++--- 3 files changed, 107 insertions(+), 23 deletions(-) diff --git a/neo4j/time/__init__.py b/neo4j/time/__init__.py index 2e541a35c..f1d45ae43 100644 --- a/neo4j/time/__init__.py +++ b/neo4j/time/__init__.py @@ -54,13 +54,6 @@ DateTimeType, ) -import logging -from neo4j.debug import watch -watch("neo4j") - -log = logging.getLogger("neo4j") - - MIN_INT64 = -(2 ** 63) MAX_INT64 = (2 ** 63) - 1 @@ -1157,10 +1150,15 @@ def to_native(self): h, m, s = self.hour_minute_second s, ns = nano_divmod(s, 1) ms = int(nano_mul(ns, 1000000)) - return time(h, m, s, ms) + tz = self.tzinfo + return time(h, m, s, ms, tz) def iso_format(self): - return "%02d:%02d:%012.9f" % self.hour_minute_second + s = "%02d:%02d:%012.9f" % self.hour_minute_second + if self.tzinfo is not None: + offset = self.tzinfo.utcoffset(self) + s += "%+03d:%02d" % divmod(offset.total_seconds() // 60, 60) + return s def __repr__(self): if self.tzinfo is None: @@ -1423,12 +1421,27 @@ def __sub__(self, other): # INSTANCE METHODS # def date(self): + """The the date + + :return: the date + :rtype: :class:`neo4j.time.Date` + """ return self.__date def time(self): + """The the time without time zone info + + :return: the time without time zone info + :rtype: :class:`neo4j.time.Time` + """ return self.__time.replace(tzinfo=None) def timetz(self): + """The the time with time zone info + + :return: the time with time zone info + :rtype: :class:`neo4j.time.Time` + """ return self.__time def replace(self, **kwargs): @@ -1478,7 +1491,8 @@ def to_native(self): h, m, s = self.hour_minute_second s, ns = nano_divmod(s, 1) ms = int(nano_mul(ns, 1000000)) - return datetime(y, mo, d, h, m, s, ms) + tz = self.tzinfo + return datetime(y, mo, d, h, m, s, ms, tz) def weekday(self): return self.__date.weekday() @@ -1490,11 +1504,7 @@ def iso_calendar(self): return self.__date.iso_calendar() def iso_format(self, sep="T"): - s = "%s%s%s" % (self.date().iso_format(), sep, self.time().iso_format()) - if self.tzinfo is not None: - offset = self.tzinfo.utcoffset(self) - s += "%+03d:%02d" % divmod(offset.total_seconds() // 60, 60) - return s + return "%s%s%s" % (self.date().iso_format(), sep, self.timetz().iso_format()) def __repr__(self): if self.tzinfo is None: diff --git a/tests/unit/time/test_datetime.py b/tests/unit/time/test_datetime.py index 2c471063a..9f1c8d875 100644 --- a/tests/unit/time/test_datetime.py +++ b/tests/unit/time/test_datetime.py @@ -338,10 +338,39 @@ def test_from_iso_format_with_negative_long_tz(self): self.assertEqual(expected, actual) -def test_potential_rounding_error(): - # python -m pytest tests/unit/time/test_datetime.py -s -v -k test_potential_rounding_error +def test_iso_format_with_time_zone_case_1(): + # python -m pytest tests/unit/time/test_datetime.py -s -v -k test_iso_format_with_time_zone_case_1 expected = DateTime(2019, 10, 30, 7, 54, 2.129790999, tzinfo=timezone_utc) assert expected.iso_format() == "2019-10-30T07:54:02.129790999+00:00" - + assert expected.tzinfo == FixedOffset(0) actual = DateTime.from_iso_format("2019-10-30T07:54:02.129790999+00:00") assert expected == actual + + +def test_iso_format_with_time_zone_case_2(): + # python -m pytest tests/unit/time/test_datetime.py -s -v -k test_iso_format_with_time_zone_case_2 + expected = DateTime.from_iso_format("2019-10-30T07:54:02.129790999+01:00") + assert expected.tzinfo == FixedOffset(60) + assert expected.iso_format() == "2019-10-30T07:54:02.129790999+01:00" + + +def test_to_native_case_1(): + # python -m pytest tests/unit/time/test_datetime.py -s -v -k test_to_native_case_1 + dt = DateTime.from_iso_format("2019-10-30T12:34:56.789123456") + native = dt.to_native() + assert native.hour == dt.hour + assert native.minute == dt.minute + assert nano_add(native.second, nano_div(native.microsecond, 1000000)) == 56.789123 + assert native.tzinfo is None + assert native.isoformat() == "2019-10-30T12:34:56.789123" + + +def test_to_native_case_2(): + # python -m pytest tests/unit/time/test_datetime.py -s -v -k test_to_native_case_2 + dt = DateTime.from_iso_format("2019-10-30T12:34:56.789123456+00:00") + native = dt.to_native() + assert native.hour == dt.hour + assert native.minute == dt.minute + assert nano_add(native.second, nano_div(native.microsecond, 1000000)) == 56.789123 + assert native.tzinfo == FixedOffset(0) + assert native.isoformat() == "2019-10-30T12:34:56.789123+00:00" diff --git a/tests/unit/time/test_time.py b/tests/unit/time/test_time.py index d9a98e3e2..a8e40a3d2 100644 --- a/tests/unit/time/test_time.py +++ b/tests/unit/time/test_time.py @@ -22,13 +22,20 @@ from datetime import time from unittest import TestCase -from pytz import timezone, FixedOffset +from pytz import ( + timezone, + FixedOffset, +) from neo4j.time import Time -from neo4j.time.arithmetic import nano_add, nano_div +from neo4j.time.arithmetic import ( + nano_add, + nano_div, +) -eastern = timezone("US/Eastern") +timezone_us_eastern = timezone("US/Eastern") +timezone_utc = timezone("UTC") class TimeTestCase(TestCase): @@ -71,9 +78,9 @@ def test_now_without_tz(self): self.assertIsInstance(t, Time) def test_now_with_tz(self): - t = Time.now(tz=eastern) + t = Time.now(tz=timezone_us_eastern) self.assertIsInstance(t, Time) - self.assertEqual(t.tzinfo, eastern) + self.assertEqual(t.tzinfo, timezone_us_eastern) def test_utc_now(self): t = Time.utc_now() @@ -150,3 +157,41 @@ def test_from_iso_format_with_negative_long_tz(self): expected = Time(12, 34, 56.123456789, tzinfo=FixedOffset(-754)) actual = Time.from_iso_format("12:34:56.123456789-12:34:56.123456") self.assertEqual(expected, actual) + + +def test_iso_format_with_time_zone_case_1(): + # python -m pytest tests/unit/time/test_time.py -s -v -k test_iso_format_with_time_zone_case_1 + expected = Time(7, 54, 2.129790999, tzinfo=timezone_utc) + assert expected.iso_format() == "07:54:02.129790999+00:00" + assert expected.tzinfo == FixedOffset(0) + actual = Time.from_iso_format("07:54:02.129790999+00:00") + assert expected == actual + + +def test_iso_format_with_time_zone_case_2(): + # python -m pytest tests/unit/time/test_time.py -s -v -k test_iso_format_with_time_zone_case_2 + expected = Time.from_iso_format("07:54:02.129790999+01:00") + assert expected.tzinfo == FixedOffset(60) + assert expected.iso_format() == "07:54:02.129790999+01:00" + + +def test_to_native_case_1(): + # python -m pytest tests/unit/time/test_time.py -s -v -k test_to_native_case_1 + t = Time(12, 34, 56.789123456) + native = t.to_native() + assert native.hour == t.hour + assert native.minute == t.minute + assert nano_add(native.second, nano_div(native.microsecond, 1000000)) == 56.789123 + assert native.tzinfo is None + assert native.isoformat() == "12:34:56.789123" + + +def test_to_native_case_2(): + # python -m pytest tests/unit/time/test_time.py -s -v -k test_to_native_case_2 + t = Time(12, 34, 56.789123456, tzinfo=timezone_utc) + native = t.to_native() + assert native.hour == t.hour + assert native.minute == t.minute + assert nano_add(native.second, nano_div(native.microsecond, 1000000)) == 56.789123 + assert native.tzinfo == FixedOffset(0) + assert native.isoformat() == "12:34:56.789123+00:00" From ddf0428a6d627d99e4d21d3cc9c302ac267f2a60 Mon Sep 17 00:00:00 2001 From: martin bendsoe Date: Tue, 9 Jun 2020 13:43:35 +0200 Subject: [PATCH 2/3] fixed that time zone info is not removed with the from_native method --- neo4j/time/__init__.py | 2 +- tests/unit/time/test_datetime.py | 26 ++++++++++++++++++++++++++ tests/unit/time/test_time.py | 20 ++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/neo4j/time/__init__.py b/neo4j/time/__init__.py index f1d45ae43..e32b9b6cf 100644 --- a/neo4j/time/__init__.py +++ b/neo4j/time/__init__.py @@ -1275,7 +1275,7 @@ def parse(cls, date_string, format): def from_native(cls, dt): """ Convert from a native Python `datetime.datetime` value. """ - return cls.combine(Date.from_native(dt.date()), Time.from_native(dt.time())) + return cls.combine(Date.from_native(dt.date()), Time.from_native(dt.timetz())) @classmethod def from_clock_time(cls, clock_time, epoch): diff --git a/tests/unit/time/test_datetime.py b/tests/unit/time/test_datetime.py index 9f1c8d875..38eb50574 100644 --- a/tests/unit/time/test_datetime.py +++ b/tests/unit/time/test_datetime.py @@ -374,3 +374,29 @@ def test_to_native_case_2(): assert nano_add(native.second, nano_div(native.microsecond, 1000000)) == 56.789123 assert native.tzinfo == FixedOffset(0) assert native.isoformat() == "2019-10-30T12:34:56.789123+00:00" + + +def test_from_native_case_1(): + # python -m pytest tests/unit/time/test_datetime.py -s -v -k test_from_native_case_1 + native = datetime(2018, 10, 1, 12, 34, 56, 789123) + dt = DateTime.from_native(native) + assert dt.year == native.year + assert dt.month == native.month + assert dt.day == native.day + assert dt.hour == native.hour + assert dt.minute == native.minute + assert dt.second == nano_add(native.second, nano_div(native.microsecond, 1000000)) + assert dt.tzinfo is None + + +def test_from_native_case_2(): + # python -m pytest tests/unit/time/test_datetime.py -s -v -k test_from_native_case_2 + native = datetime(2018, 10, 1, 12, 34, 56, 789123, FixedOffset(0)) + dt = DateTime.from_native(native) + assert dt.year == native.year + assert dt.month == native.month + assert dt.day == native.day + assert dt.hour == native.hour + assert dt.minute == native.minute + assert dt.second == nano_add(native.second, nano_div(native.microsecond, 1000000)) + assert dt.tzinfo == FixedOffset(0) diff --git a/tests/unit/time/test_time.py b/tests/unit/time/test_time.py index a8e40a3d2..2aaced315 100644 --- a/tests/unit/time/test_time.py +++ b/tests/unit/time/test_time.py @@ -195,3 +195,23 @@ def test_to_native_case_2(): assert nano_add(native.second, nano_div(native.microsecond, 1000000)) == 56.789123 assert native.tzinfo == FixedOffset(0) assert native.isoformat() == "12:34:56.789123+00:00" + + +def test_from_native_case_1(): + # python -m pytest tests/unit/time/test_time.py -s -v -k test_from_native_case_1 + native = time(12, 34, 56, 789123) + t = Time.from_native(native) + assert t.hour == native.hour + assert t.minute == native.minute + assert t.second == nano_add(native.second, nano_div(native.microsecond, 1000000)) + assert t.tzinfo is None + + +def test_from_native_case_2(): + # python -m pytest tests/unit/time/test_time.py -s -v -k test_from_native_case_2 + native = time(12, 34, 56, 789123, FixedOffset(0)) + t = Time.from_native(native) + assert t.hour == native.hour + assert t.minute == native.minute + assert t.second == nano_add(native.second, nano_div(native.microsecond, 1000000)) + assert t.tzinfo == FixedOffset(0) From 3b5c2e255ab1a6addf37d1ce387edbe881ab725a Mon Sep 17 00:00:00 2001 From: martin bendsoe Date: Tue, 9 Jun 2020 13:55:56 +0200 Subject: [PATCH 3/3] added test snippet --- tests/integration/test_temporal_types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_temporal_types.py b/tests/integration/test_temporal_types.py index 5443043b7..2ae4c622e 100644 --- a/tests/integration/test_temporal_types.py +++ b/tests/integration/test_temporal_types.py @@ -383,7 +383,7 @@ def test_time_parameter_case2(session): # python -m pytest tests/integration/test_temporal_types.py -s -v -k test_time_parameter_case2 t1 = session.run("RETURN time('07:54:02.129790999+00:00')").single().value() assert isinstance(t1, Time) - # assert t1.iso_format() == "07:54:02.129790999+00:00" # TODO: Broken, does not show time_zone_delta +00:00 + assert t1.iso_format() == "07:54:02.129790999+00:00" time_zone_delta = t1.utc_offset() assert isinstance(time_zone_delta, datetime.timedelta) assert time_zone_delta == datetime.timedelta(0)