You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by sp...@apache.org on 2019/07/23 15:26:13 UTC
[tinkerpop] 01/01: TINKERPOP-2264 Fixed g:Date serialization for
python.
This is an automated email from the ASF dual-hosted git repository.
spmallette pushed a commit to branch TINKERPOP-2264
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 82a857082d7b996baa1bf7c100ac1bd77ace524a
Author: Stephen Mallette <sp...@genoprime.com>
AuthorDate: Tue Jul 23 11:23:01 2019 -0400
TINKERPOP-2264 Fixed g:Date serialization for python.
Got timezone of the local system out of the derser mix and standardized around what's always been expected in Java's Date.getTime().
---
CHANGELOG.asciidoc | 1 +
docs/src/dev/io/graphson.asciidoc | 4 ++++
docs/src/upgrade/release-3.3.x.asciidoc | 9 +++++++++
.../gremlin_python/structure/io/graphsonV2d0.py | 15 +++++++--------
.../gremlin_python/structure/io/graphsonV3d0.py | 15 +++++++--------
.../jython/tests/structure/io/test_graphsonV2d0.py | 19 ++++++++++++-------
.../jython/tests/structure/io/test_graphsonV3d0.py | 13 ++++++-------
7 files changed, 46 insertions(+), 30 deletions(-)
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index e9da7f2..7adbbae 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -27,6 +27,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
* Deprecated remote traversal side-effect retrieval and related infrastructure.
* Bump to Groovy 2.4.17.
* Bump to Jackson Databind 2.9.9.1.
+* Fixed bug with Python in `g:Date` of GraphSON where local time zone was being used during serialization/deserialization.
* Improved error messaging when an attempt is made to serialize multi-properties to GraphML.
* Improved exception and messaging for gt/gte/lt/lte when one of the object isn't a `Comparable`.
* Added test infrastructure to check for storage iterator leak.
diff --git a/docs/src/dev/io/graphson.asciidoc b/docs/src/dev/io/graphson.asciidoc
index c34eea9..8191e3a 100644
--- a/docs/src/dev/io/graphson.asciidoc
+++ b/docs/src/dev/io/graphson.asciidoc
@@ -1161,6 +1161,8 @@ types. By default, TinkerPop types will have the namespace "g" (or "gx" for "ext
==== Date
+Representing a millisecond-precision offset from the unix epoch. In Java, it is simply `Date.getTime()`.
+
[source,json]
----
{
@@ -3846,6 +3848,8 @@ not have `String` keys (e.g. `g.V().out().groupCount()`).
==== Date
+Representing a millisecond-precision offset from the unix epoch. In Java, it is simply `Date.getTime()`.
+
[source,json]
----
{
diff --git a/docs/src/upgrade/release-3.3.x.asciidoc b/docs/src/upgrade/release-3.3.x.asciidoc
index 5887c74..18fa5c0 100644
--- a/docs/src/upgrade/release-3.3.x.asciidoc
+++ b/docs/src/upgrade/release-3.3.x.asciidoc
@@ -57,6 +57,15 @@ gremlin> g.V().hasLabel("person").
See: link:https://issues.apache.org/jira/browse/TINKERPOP-1084[TINKERPOP-1084]
+==== Python DateTime
+
+With GraphSON, the `g:Date` is meant to represent a millisecond-precision offset from the unix epoch, but earlier
+version of Gremlin Python were using the timezone of the local system to handle serialization and deserialization,
+thus resulting in incorrect conversion. This issue is now resolved. It may be necessary to remove workarounds that have
+been introduced to combat this problem.
+
+See: https://issues.apache.org/jira/browse/TINKERPOP-2264[TINKERPOP-2264]
+
==== JavaScript withComputer()
Gremlin-Javascript now supports `withComputer()` syntax, which means that it is now possible in Javascript to utilize
diff --git a/gremlin-python/src/main/jython/gremlin_python/structure/io/graphsonV2d0.py b/gremlin-python/src/main/jython/gremlin_python/structure/io/graphsonV2d0.py
index 7515726..b1d08b6 100644
--- a/gremlin-python/src/main/jython/gremlin_python/structure/io/graphsonV2d0.py
+++ b/gremlin-python/src/main/jython/gremlin_python/structure/io/graphsonV2d0.py
@@ -339,18 +339,17 @@ class DateIO(_GraphSONTypeIO):
python_type = datetime.datetime
graphson_type = "g:Date"
graphson_base_type = "Date"
+ epoch = datetime.datetime(1970, 1, 1)
@classmethod
def dictify(cls, obj, writer):
- # Java timestamp expects miliseconds
+ # Java timestamp expects milliseconds.
if six.PY3:
- pts = obj.timestamp()
+ pts = (obj - cls.epoch) / datetime.timedelta(seconds=1)
else:
- # Hack for legacy Python
- # timestamp() in Python 3.3
- pts = time.mktime((obj.year, obj.month, obj.day,
- obj.hour, obj.minute, obj.second,
- -1, -1, -1)) + obj.microsecond / 1e6
+ # Hack for legacy Python - timestamp() in Python 3.3
+ pts = (time.mktime(obj.timetuple()) + obj.microsecond / 1e6) - \
+ (time.mktime(cls.epoch.timetuple()))
# Have to use int because of legacy Python
ts = int(round(pts * 1000))
@@ -359,7 +358,7 @@ class DateIO(_GraphSONTypeIO):
@classmethod
def objectify(cls, ts, reader):
# Python timestamp expects seconds
- return datetime.datetime.fromtimestamp(ts / 1000.0)
+ return datetime.datetime.utcfromtimestamp(ts / 1000.0)
# Based on current implementation, this class must always be declared before FloatIO.
diff --git a/gremlin-python/src/main/jython/gremlin_python/structure/io/graphsonV3d0.py b/gremlin-python/src/main/jython/gremlin_python/structure/io/graphsonV3d0.py
index a900ac8..0e37d22 100644
--- a/gremlin-python/src/main/jython/gremlin_python/structure/io/graphsonV3d0.py
+++ b/gremlin-python/src/main/jython/gremlin_python/structure/io/graphsonV3d0.py
@@ -348,19 +348,18 @@ class DateIO(_GraphSONTypeIO):
python_type = datetime.datetime
graphson_type = "g:Date"
graphson_base_type = "Date"
+ epoch = datetime.datetime(1970, 1, 1)
@classmethod
def dictify(cls, obj, writer):
+ # Java timestamp expects milliseconds.
if six.PY3:
- pts = obj.timestamp()
+ pts = (obj - cls.epoch) / datetime.timedelta(seconds=1)
else:
- # Hack for legacy Python
- # timestamp() in Python 3.3
- pts = time.mktime((obj.year, obj.month, obj.day,
- obj.hour, obj.minute, obj.second,
- -1, -1, -1)) + obj.microsecond / 1e6
+ # Hack for legacy Python - timestamp() in Python 3.3
+ pts = (time.mktime(obj.timetuple()) + obj.microsecond / 1e6) - \
+ (time.mktime(cls.epoch.timetuple()))
- # Java timestamp expects miliseconds
# Have to use int because of legacy Python
ts = int(round(pts * 1000))
return GraphSONUtil.typedValue(cls.graphson_base_type, ts)
@@ -368,7 +367,7 @@ class DateIO(_GraphSONTypeIO):
@classmethod
def objectify(cls, ts, reader):
# Python timestamp expects seconds
- return datetime.datetime.fromtimestamp(ts / 1000.0)
+ return datetime.datetime.utcfromtimestamp(ts / 1000.0)
# Based on current implementation, this class must always be declared before FloatIO.
diff --git a/gremlin-python/src/main/jython/tests/structure/io/test_graphsonV2d0.py b/gremlin-python/src/main/jython/tests/structure/io/test_graphsonV2d0.py
index b3ff0b3..11b6045 100644
--- a/gremlin-python/src/main/jython/tests/structure/io/test_graphsonV2d0.py
+++ b/gremlin-python/src/main/jython/tests/structure/io/test_graphsonV2d0.py
@@ -245,11 +245,10 @@ class TestGraphSONReader(object):
def test_datetime(self):
expected = datetime.datetime(2016, 12, 14, 16, 14, 36, 295000)
- pts = time.mktime((expected.year, expected.month, expected.day,
- expected.hour, expected.minute, expected.second,
- -1, -1, -1)) + expected.microsecond / 1e6
- timestamp = int(round(pts * 1000))
- dt = self.graphson_reader.readObject(json.dumps({"@type": "g:Date", "@value": timestamp}))
+ pts = time.mktime(expected.timetuple()) + expected.microsecond / 1e6 - \
+ (time.mktime(datetime.datetime(1970, 1, 1).timetuple()))
+ ts = int(round(pts * 1000))
+ dt = self.graphson_reader.readObject(json.dumps({"@type": "g:Date", "@value": ts}))
assert isinstance(dt, datetime.datetime)
# TINKERPOP-1848
assert dt == expected
@@ -440,7 +439,7 @@ class TestGraphSONWriter(object):
def test_datetime(self):
expected = json.dumps({"@type": "g:Date", "@value": 1481750076295}, separators=(',', ':'))
- dt = datetime.datetime.fromtimestamp(1481750076295 / 1000.0)
+ dt = datetime.datetime.utcfromtimestamp(1481750076295 / 1000.0)
output = self.graphson_writer.writeObject(dt)
assert expected == output
@@ -488,18 +487,22 @@ class TestFunctionalGraphSONIO(object):
ts_prop = g.V(vid).properties('ts').toList()[0]
assert isinstance(ts_prop.value, timestamp)
assert ts_prop.value == ts
+ except OSError:
+ assert False, "Error making request"
finally:
g.V(vid).drop().iterate()
def test_datetime(self, remote_connection_v2):
g = Graph().traversal().withRemote(remote_connection_v2)
- dt = datetime.datetime.fromtimestamp(1481750076295 / 1000)
+ dt = datetime.datetime.utcfromtimestamp(1481750076295 / 1000)
resp = g.addV('test_vertex').property('dt', dt).toList()
vid = resp[0].id
try:
dt_prop = g.V(vid).properties('dt').toList()[0]
assert isinstance(dt_prop.value, datetime.datetime)
assert dt_prop.value == dt
+ except OSError:
+ assert False, "Error making request"
finally:
g.V(vid).drop().iterate()
@@ -512,5 +515,7 @@ class TestFunctionalGraphSONIO(object):
uid_prop = g.V(vid).properties('uuid').toList()[0]
assert isinstance(uid_prop.value, uuid.UUID)
assert uid_prop.value == uid
+ except OSError:
+ assert False, "Error making request"
finally:
g.V(vid).drop().iterate()
diff --git a/gremlin-python/src/main/jython/tests/structure/io/test_graphsonV3d0.py b/gremlin-python/src/main/jython/tests/structure/io/test_graphsonV3d0.py
index 73a3912..14a8308 100644
--- a/gremlin-python/src/main/jython/tests/structure/io/test_graphsonV3d0.py
+++ b/gremlin-python/src/main/jython/tests/structure/io/test_graphsonV3d0.py
@@ -283,11 +283,10 @@ class TestGraphSONReader(object):
def test_datetime(self):
expected = datetime.datetime(2016, 12, 14, 16, 14, 36, 295000)
- pts = time.mktime((expected.year, expected.month, expected.day,
- expected.hour, expected.minute, expected.second,
- -1, -1, -1)) + expected.microsecond / 1e6
- timestamp = int(round(pts * 1000))
- dt = self.graphson_reader.readObject(json.dumps({"@type": "g:Date", "@value": timestamp}))
+ pts = time.mktime(expected.timetuple()) + expected.microsecond / 1e6 - \
+ (time.mktime(datetime.datetime(1970, 1, 1).timetuple()))
+ ts = int(round(pts * 1000))
+ dt = self.graphson_reader.readObject(json.dumps({"@type": "g:Date", "@value": ts}))
assert isinstance(dt, datetime.datetime)
# TINKERPOP-1848
assert dt == expected
@@ -495,7 +494,7 @@ class TestGraphSONWriter(object):
def test_datetime(self):
expected = json.dumps({"@type": "g:Date", "@value": 1481750076295}, separators=(',', ':'))
- dt = datetime.datetime.fromtimestamp(1481750076295 / 1000.0)
+ dt = datetime.datetime.utcfromtimestamp(1481750076295 / 1000.0)
output = self.graphson_writer.writeObject(dt)
assert expected == output
@@ -548,7 +547,7 @@ class TestFunctionalGraphSONIO(object):
def test_datetime(self, remote_connection):
g = Graph().traversal().withRemote(remote_connection)
- dt = datetime.datetime.fromtimestamp(1481750076295 / 1000)
+ dt = datetime.datetime.utcfromtimestamp(1481750076295 / 1000)
resp = g.addV('test_vertex').property('dt', dt).toList()
vid = resp[0].id
try: