Repository: jsonpickle/jsonpickle
Pull request #556 (2025-03-19 06:22:46)

numpy: add support for nanosecond-resolution datetime64 objects
Extend NumpyGenericHandler to special-case datetime64 dtypes with nanosecond resolution. This handling seems to only be needed for nanosecond resolution.  Other units do not require special handling.

Closes: #555


Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 36a3979..b317251 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -4,6 +4,8 @@ Upcoming
       You can re-enable yaml support using ``jsonpickle.ext.yaml.register()``.
       (#550) (+551)
     * Documentation warnings from ``furo`` have been fixed.
+    * Numpy datetime64 objects with nanosecond precision are now supported.
+      (+556) (#555)
 
 v4.0.2
 ======
diff --git a/jsonpickle/ext/numpy.py b/jsonpickle/ext/numpy.py
index ae58631..2b037f7 100644
--- a/jsonpickle/ext/numpy.py
+++ b/jsonpickle/ext/numpy.py
@@ -58,6 +58,17 @@ class NumpyGenericHandler(NumpyBaseHandler):
         return self.restore_dtype(data).type(value)
 
 
+class NumpyDatetimeHandler(NumpyGenericHandler):
+    """Extend NumpyGenericHandler to handle nanosecond-resolution datetime64"""
+
+    def restore(self, data):
+        value = self.context.restore(data['value'], reset=False)
+        dtype = data['dtype']
+        if dtype.endswith('[ns]'):
+            return self.restore_dtype(data).type(value, 'ns')
+        return self.restore_dtype(data).type(value)
+
+
 class UnpickleableNumpyGenericHandler(NumpyGenericHandler):
     """
     From issue #381, this is used for simplifying the output of numpy arrays
@@ -364,6 +375,7 @@ def register_handlers(
     register(np.dtype(np.float32).__class__, NumpyDTypeHandler, base=True)
     register(np.dtype(np.int32).__class__, NumpyDTypeHandler, base=True)
     register(np.dtype(np.datetime64).__class__, NumpyDTypeHandler, base=True)
+    register(np.datetime64, NumpyDatetimeHandler, base=True)
 
 
 def unregister_handlers():


Test patch:
diff --git a/tests/numpy_test.py b/tests/numpy_test.py
index 889ce68..446a75a 100644
--- a/tests/numpy_test.py
+++ b/tests/numpy_test.py
@@ -373,3 +373,34 @@ def test_np_poly1d():
     # issue 391, test poly1d roundtrip
     obj = np.poly1d([1, 2, 3])
     assert obj == jsonpickle.decode(jsonpickle.encode(obj))
+
+
+def test_np_datetime64_units():
+    """Ensure that we can roundtrip a numpy datetime64 with varying precisions"""
+    obj = np.datetime64('2000', 'ns')
+    encoded = jsonpickle.encode(obj)
+    assert obj == jsonpickle.decode(encoded)
+
+    obj = np.datetime64('2001', 'ms')
+    encoded = jsonpickle.encode(obj)
+    assert obj == jsonpickle.decode(encoded)
+
+    obj = np.datetime64('2002', 's')
+    encoded = jsonpickle.encode(obj)
+    assert obj == jsonpickle.decode(encoded)
+
+    obj = np.datetime64('2003', 'm')
+    encoded = jsonpickle.encode(obj)
+    assert obj == jsonpickle.decode(encoded)
+
+    obj = np.datetime64('2004', 'h')
+    encoded = jsonpickle.encode(obj)
+    assert obj == jsonpickle.decode(encoded)
+
+    obj = np.datetime64('2005', 'D')
+    encoded = jsonpickle.encode(obj)
+    assert obj == jsonpickle.decode(encoded)
+
+    obj = np.datetime64('2006', 'Y')
+    encoded = jsonpickle.encode(obj)
+    assert obj == jsonpickle.decode(encoded)