Skip to content

Commit 8bd52d0

Browse files
committed
fixed datetime serialization
1 parent e54c755 commit 8bd52d0

File tree

3 files changed

+47
-8
lines changed

3 files changed

+47
-8
lines changed

src/zarr/core/metadata.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -256,13 +256,21 @@ def _json_convert(o: Any) -> Any:
256256
if isinstance(o, np.dtype):
257257
return str(o)
258258
if np.isscalar(o):
259-
# convert numpy scalar to python type, and pass
260-
# python types through
261-
out = getattr(o, "item", lambda: o)()
262-
if isinstance(out, complex):
263-
# python complex types are not JSON serializable, so we use the
264-
# serialization defined in the zarr v3 spec
265-
return [out.real, out.imag]
259+
out: Any
260+
if hasattr(o, "dtype") and o.dtype.kind == "M" and hasattr(o, "view"):
261+
# https://github.com/zarr-developers/zarr-python/issues/2119
262+
# `.item()` on a datetime type might or might not return an
263+
# integer, depending on the value.
264+
# Explicitly cast to an int first, and then grab .item()
265+
out = o.view("i8").item()
266+
else:
267+
# convert numpy scalar to python type, and pass
268+
# python types through
269+
out = getattr(o, "item", lambda: o)()
270+
if isinstance(out, complex):
271+
# python complex types are not JSON serializable, so we use the
272+
# serialization defined in the zarr v3 spec
273+
return [out.real, out.imag]
266274
return out
267275
if isinstance(o, Enum):
268276
return o.name

src/zarr/testing/strategies.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import re
12
from typing import Any
23

34
import hypothesis.extra.numpy as npst
@@ -101,7 +102,14 @@ def arrays(
101102
root = Group.create(store)
102103
fill_value_args: tuple[Any, ...] = tuple()
103104
if nparray.dtype.kind == "M":
104-
fill_value_args = ("ns",)
105+
m = re.search("\[(.+)\]", nparray.dtype.str)
106+
if not m:
107+
raise ValueError(f"Couldn't find precision for dtype '{nparray.dtype}.")
108+
109+
fill_value_args = (
110+
# e.g. ns, D
111+
m.groups()[0],
112+
)
105113

106114
a = root.create_array(
107115
array_path,

tests/v3/test_metadata/test_v3.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
from __future__ import annotations
22

3+
import json
34
import re
45
from typing import TYPE_CHECKING, Literal
56

67
from zarr.abc.codec import Codec
78
from zarr.codecs.bytes import BytesCodec
9+
from zarr.core.buffer import default_buffer_prototype
810
from zarr.core.chunk_key_encodings import DefaultChunkKeyEncoding, V2ChunkKeyEncoding
911

1012
if TYPE_CHECKING:
@@ -230,3 +232,24 @@ def test_metadata_to_dict(
230232
observed.pop("chunk_key_encoding")
231233
expected.pop("chunk_key_encoding")
232234
assert observed == expected
235+
236+
237+
@pytest.mark.parametrize("fill_value", [-1, 0, 1, 2932897])
238+
@pytest.mark.parametrize("precision", ["ns", "D"])
239+
async def test_datetime_metadata(fill_value: int, precision: str):
240+
metadata_dict = {
241+
"zarr_format": 3,
242+
"node_type": "array",
243+
"shape": (1,),
244+
"chunk_grid": {"name": "regular", "configuration": {"chunk_shape": (1,)}},
245+
"data_type": f"<M8[{precision}]",
246+
"chunk_key_encoding": {"name": "default", "separator": "."},
247+
"codecs": (),
248+
"fill_value": np.datetime64(fill_value, precision),
249+
}
250+
metadata = ArrayV3Metadata.from_dict(metadata_dict)
251+
# ensure there isn't a TypeError here.
252+
d = metadata.to_buffer_dict(default_buffer_prototype())
253+
254+
result = json.loads(d["zarr.json"].to_bytes())
255+
assert result["fill_value"] == fill_value

0 commit comments

Comments
 (0)