Skip to content

Commit cb650e7

Browse files
4.0 temporal types timezone fixes (#413)
* fixed time zone info * fixed that time zone info is not removed with the from_native method * added test snippet
1 parent 2c14bde commit cb650e7

File tree

4 files changed

+155
-25
lines changed

4 files changed

+155
-25
lines changed

neo4j/time/__init__.py

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,6 @@
5454
DateTimeType,
5555
)
5656

57-
import logging
58-
from neo4j.debug import watch
59-
watch("neo4j")
60-
61-
log = logging.getLogger("neo4j")
62-
63-
6457
MIN_INT64 = -(2 ** 63)
6558
MAX_INT64 = (2 ** 63) - 1
6659

@@ -1157,10 +1150,15 @@ def to_native(self):
11571150
h, m, s = self.hour_minute_second
11581151
s, ns = nano_divmod(s, 1)
11591152
ms = int(nano_mul(ns, 1000000))
1160-
return time(h, m, s, ms)
1153+
tz = self.tzinfo
1154+
return time(h, m, s, ms, tz)
11611155

11621156
def iso_format(self):
1163-
return "%02d:%02d:%012.9f" % self.hour_minute_second
1157+
s = "%02d:%02d:%012.9f" % self.hour_minute_second
1158+
if self.tzinfo is not None:
1159+
offset = self.tzinfo.utcoffset(self)
1160+
s += "%+03d:%02d" % divmod(offset.total_seconds() // 60, 60)
1161+
return s
11641162

11651163
def __repr__(self):
11661164
if self.tzinfo is None:
@@ -1277,7 +1275,7 @@ def parse(cls, date_string, format):
12771275
def from_native(cls, dt):
12781276
""" Convert from a native Python `datetime.datetime` value.
12791277
"""
1280-
return cls.combine(Date.from_native(dt.date()), Time.from_native(dt.time()))
1278+
return cls.combine(Date.from_native(dt.date()), Time.from_native(dt.timetz()))
12811279

12821280
@classmethod
12831281
def from_clock_time(cls, clock_time, epoch):
@@ -1423,12 +1421,27 @@ def __sub__(self, other):
14231421
# INSTANCE METHODS #
14241422

14251423
def date(self):
1424+
"""The the date
1425+
1426+
:return: the date
1427+
:rtype: :class:`neo4j.time.Date`
1428+
"""
14261429
return self.__date
14271430

14281431
def time(self):
1432+
"""The the time without time zone info
1433+
1434+
:return: the time without time zone info
1435+
:rtype: :class:`neo4j.time.Time`
1436+
"""
14291437
return self.__time.replace(tzinfo=None)
14301438

14311439
def timetz(self):
1440+
"""The the time with time zone info
1441+
1442+
:return: the time with time zone info
1443+
:rtype: :class:`neo4j.time.Time`
1444+
"""
14321445
return self.__time
14331446

14341447
def replace(self, **kwargs):
@@ -1478,7 +1491,8 @@ def to_native(self):
14781491
h, m, s = self.hour_minute_second
14791492
s, ns = nano_divmod(s, 1)
14801493
ms = int(nano_mul(ns, 1000000))
1481-
return datetime(y, mo, d, h, m, s, ms)
1494+
tz = self.tzinfo
1495+
return datetime(y, mo, d, h, m, s, ms, tz)
14821496

14831497
def weekday(self):
14841498
return self.__date.weekday()
@@ -1490,11 +1504,7 @@ def iso_calendar(self):
14901504
return self.__date.iso_calendar()
14911505

14921506
def iso_format(self, sep="T"):
1493-
s = "%s%s%s" % (self.date().iso_format(), sep, self.time().iso_format())
1494-
if self.tzinfo is not None:
1495-
offset = self.tzinfo.utcoffset(self)
1496-
s += "%+03d:%02d" % divmod(offset.total_seconds() // 60, 60)
1497-
return s
1507+
return "%s%s%s" % (self.date().iso_format(), sep, self.timetz().iso_format())
14981508

14991509
def __repr__(self):
15001510
if self.tzinfo is None:

tests/integration/test_temporal_types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ def test_time_parameter_case2(session):
383383
# python -m pytest tests/integration/test_temporal_types.py -s -v -k test_time_parameter_case2
384384
t1 = session.run("RETURN time('07:54:02.129790999+00:00')").single().value()
385385
assert isinstance(t1, Time)
386-
# assert t1.iso_format() == "07:54:02.129790999+00:00" # TODO: Broken, does not show time_zone_delta +00:00
386+
assert t1.iso_format() == "07:54:02.129790999+00:00"
387387
time_zone_delta = t1.utc_offset()
388388
assert isinstance(time_zone_delta, datetime.timedelta)
389389
assert time_zone_delta == datetime.timedelta(0)

tests/unit/time/test_datetime.py

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -338,10 +338,65 @@ def test_from_iso_format_with_negative_long_tz(self):
338338
self.assertEqual(expected, actual)
339339

340340

341-
def test_potential_rounding_error():
342-
# python -m pytest tests/unit/time/test_datetime.py -s -v -k test_potential_rounding_error
341+
def test_iso_format_with_time_zone_case_1():
342+
# python -m pytest tests/unit/time/test_datetime.py -s -v -k test_iso_format_with_time_zone_case_1
343343
expected = DateTime(2019, 10, 30, 7, 54, 2.129790999, tzinfo=timezone_utc)
344344
assert expected.iso_format() == "2019-10-30T07:54:02.129790999+00:00"
345-
345+
assert expected.tzinfo == FixedOffset(0)
346346
actual = DateTime.from_iso_format("2019-10-30T07:54:02.129790999+00:00")
347347
assert expected == actual
348+
349+
350+
def test_iso_format_with_time_zone_case_2():
351+
# python -m pytest tests/unit/time/test_datetime.py -s -v -k test_iso_format_with_time_zone_case_2
352+
expected = DateTime.from_iso_format("2019-10-30T07:54:02.129790999+01:00")
353+
assert expected.tzinfo == FixedOffset(60)
354+
assert expected.iso_format() == "2019-10-30T07:54:02.129790999+01:00"
355+
356+
357+
def test_to_native_case_1():
358+
# python -m pytest tests/unit/time/test_datetime.py -s -v -k test_to_native_case_1
359+
dt = DateTime.from_iso_format("2019-10-30T12:34:56.789123456")
360+
native = dt.to_native()
361+
assert native.hour == dt.hour
362+
assert native.minute == dt.minute
363+
assert nano_add(native.second, nano_div(native.microsecond, 1000000)) == 56.789123
364+
assert native.tzinfo is None
365+
assert native.isoformat() == "2019-10-30T12:34:56.789123"
366+
367+
368+
def test_to_native_case_2():
369+
# python -m pytest tests/unit/time/test_datetime.py -s -v -k test_to_native_case_2
370+
dt = DateTime.from_iso_format("2019-10-30T12:34:56.789123456+00:00")
371+
native = dt.to_native()
372+
assert native.hour == dt.hour
373+
assert native.minute == dt.minute
374+
assert nano_add(native.second, nano_div(native.microsecond, 1000000)) == 56.789123
375+
assert native.tzinfo == FixedOffset(0)
376+
assert native.isoformat() == "2019-10-30T12:34:56.789123+00:00"
377+
378+
379+
def test_from_native_case_1():
380+
# python -m pytest tests/unit/time/test_datetime.py -s -v -k test_from_native_case_1
381+
native = datetime(2018, 10, 1, 12, 34, 56, 789123)
382+
dt = DateTime.from_native(native)
383+
assert dt.year == native.year
384+
assert dt.month == native.month
385+
assert dt.day == native.day
386+
assert dt.hour == native.hour
387+
assert dt.minute == native.minute
388+
assert dt.second == nano_add(native.second, nano_div(native.microsecond, 1000000))
389+
assert dt.tzinfo is None
390+
391+
392+
def test_from_native_case_2():
393+
# python -m pytest tests/unit/time/test_datetime.py -s -v -k test_from_native_case_2
394+
native = datetime(2018, 10, 1, 12, 34, 56, 789123, FixedOffset(0))
395+
dt = DateTime.from_native(native)
396+
assert dt.year == native.year
397+
assert dt.month == native.month
398+
assert dt.day == native.day
399+
assert dt.hour == native.hour
400+
assert dt.minute == native.minute
401+
assert dt.second == nano_add(native.second, nano_div(native.microsecond, 1000000))
402+
assert dt.tzinfo == FixedOffset(0)

tests/unit/time/test_time.py

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,20 @@
2222
from datetime import time
2323
from unittest import TestCase
2424

25-
from pytz import timezone, FixedOffset
25+
from pytz import (
26+
timezone,
27+
FixedOffset,
28+
)
2629

2730
from neo4j.time import Time
28-
from neo4j.time.arithmetic import nano_add, nano_div
31+
from neo4j.time.arithmetic import (
32+
nano_add,
33+
nano_div,
34+
)
2935

3036

31-
eastern = timezone("US/Eastern")
37+
timezone_us_eastern = timezone("US/Eastern")
38+
timezone_utc = timezone("UTC")
3239

3340

3441
class TimeTestCase(TestCase):
@@ -71,9 +78,9 @@ def test_now_without_tz(self):
7178
self.assertIsInstance(t, Time)
7279

7380
def test_now_with_tz(self):
74-
t = Time.now(tz=eastern)
81+
t = Time.now(tz=timezone_us_eastern)
7582
self.assertIsInstance(t, Time)
76-
self.assertEqual(t.tzinfo, eastern)
83+
self.assertEqual(t.tzinfo, timezone_us_eastern)
7784

7885
def test_utc_now(self):
7986
t = Time.utc_now()
@@ -150,3 +157,61 @@ def test_from_iso_format_with_negative_long_tz(self):
150157
expected = Time(12, 34, 56.123456789, tzinfo=FixedOffset(-754))
151158
actual = Time.from_iso_format("12:34:56.123456789-12:34:56.123456")
152159
self.assertEqual(expected, actual)
160+
161+
162+
def test_iso_format_with_time_zone_case_1():
163+
# python -m pytest tests/unit/time/test_time.py -s -v -k test_iso_format_with_time_zone_case_1
164+
expected = Time(7, 54, 2.129790999, tzinfo=timezone_utc)
165+
assert expected.iso_format() == "07:54:02.129790999+00:00"
166+
assert expected.tzinfo == FixedOffset(0)
167+
actual = Time.from_iso_format("07:54:02.129790999+00:00")
168+
assert expected == actual
169+
170+
171+
def test_iso_format_with_time_zone_case_2():
172+
# python -m pytest tests/unit/time/test_time.py -s -v -k test_iso_format_with_time_zone_case_2
173+
expected = Time.from_iso_format("07:54:02.129790999+01:00")
174+
assert expected.tzinfo == FixedOffset(60)
175+
assert expected.iso_format() == "07:54:02.129790999+01:00"
176+
177+
178+
def test_to_native_case_1():
179+
# python -m pytest tests/unit/time/test_time.py -s -v -k test_to_native_case_1
180+
t = Time(12, 34, 56.789123456)
181+
native = t.to_native()
182+
assert native.hour == t.hour
183+
assert native.minute == t.minute
184+
assert nano_add(native.second, nano_div(native.microsecond, 1000000)) == 56.789123
185+
assert native.tzinfo is None
186+
assert native.isoformat() == "12:34:56.789123"
187+
188+
189+
def test_to_native_case_2():
190+
# python -m pytest tests/unit/time/test_time.py -s -v -k test_to_native_case_2
191+
t = Time(12, 34, 56.789123456, tzinfo=timezone_utc)
192+
native = t.to_native()
193+
assert native.hour == t.hour
194+
assert native.minute == t.minute
195+
assert nano_add(native.second, nano_div(native.microsecond, 1000000)) == 56.789123
196+
assert native.tzinfo == FixedOffset(0)
197+
assert native.isoformat() == "12:34:56.789123+00:00"
198+
199+
200+
def test_from_native_case_1():
201+
# python -m pytest tests/unit/time/test_time.py -s -v -k test_from_native_case_1
202+
native = time(12, 34, 56, 789123)
203+
t = Time.from_native(native)
204+
assert t.hour == native.hour
205+
assert t.minute == native.minute
206+
assert t.second == nano_add(native.second, nano_div(native.microsecond, 1000000))
207+
assert t.tzinfo is None
208+
209+
210+
def test_from_native_case_2():
211+
# python -m pytest tests/unit/time/test_time.py -s -v -k test_from_native_case_2
212+
native = time(12, 34, 56, 789123, FixedOffset(0))
213+
t = Time.from_native(native)
214+
assert t.hour == native.hour
215+
assert t.minute == native.minute
216+
assert t.second == nano_add(native.second, nano_div(native.microsecond, 1000000))
217+
assert t.tzinfo == FixedOffset(0)

0 commit comments

Comments
 (0)