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: