Skip to content

Commit 8e18c81

Browse files
jakirkhamalimanfoo
authored andcommitted
Cast datetime and timedelta to signed 64-bit int (#344)
* Cast datetime and timedelta to signed 64-bit int The `NaT` type is represented as `-0`. As a result, casting to an unsigned integral fails and throws an error. However casting to a signed integral type does not have this problem and proceeds without issues. * Update datetime/timedelta test to use signed ints * Test encode/decode of a datetime/timedelta array Use a structured array with datetime and timedelta values and a fill value of NaT to test a bunch of different workarounds for encoding and decoding datetime and timedelta values and array. * Note improved `NaT` handling in the changelog * Use `dtype` to cast `NaT` explicitly * test NaT as fill_value for zarr.full() * Drop extra whitespace Should fix the flake8 error seen on CI. ref: https://travis-ci.org/zarr-developers/zarr/jobs/465075634
1 parent 8ebb16c commit 8e18c81

File tree

5 files changed

+69
-3
lines changed

5 files changed

+69
-3
lines changed

docs/release.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ Maintenance
4444
* CI and test environments have been upgraded to include Python 3.7, drop Python 3.4, and
4545
upgrade all pinned package requirements. :issue:`308`.
4646

47+
* Corrects handling of ``NaT`` in ``datetime64`` and ``timedelta64`` in various
48+
compressors (by :user:`John Kirkham <jakirkham>`; :issue:`344`).
49+
50+
Acknowledgments
51+
~~~~~~~~~~~~~~~
4752

4853
.. _release_2.2.0:
4954

zarr/meta.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,6 @@ def encode_fill_value(v, dtype):
209209
elif dtype.kind == 'U':
210210
return v
211211
elif dtype.kind in 'mM':
212-
return int(v.view('u8'))
212+
return int(v.view('i8'))
213213
else:
214214
return v

zarr/tests/test_core.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -955,8 +955,9 @@ def test_dtypes(self):
955955
dtype = '{}8[{}]'.format(base_type, resolution)
956956
z = self.create_array(shape=100, dtype=dtype, fill_value=0)
957957
assert z.dtype == np.dtype(dtype)
958-
a = np.random.randint(0, np.iinfo('u8').max, size=z.shape[0],
959-
dtype='u8').view(dtype)
958+
a = np.random.randint(np.iinfo('i8').min, np.iinfo('i8').max,
959+
size=z.shape[0],
960+
dtype='i8').view(dtype)
960961
z[:] = a
961962
assert_array_equal(a, z[:])
962963

zarr/tests/test_creation.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,12 @@ def test_full():
135135
z = full(100, chunks=10, fill_value=np.nan, dtype='f8')
136136
assert np.all(np.isnan(z[:]))
137137

138+
# NaT
139+
z = full(100, chunks=10, fill_value='NaT', dtype='M8[s]')
140+
assert np.all(np.isnat(z[:]))
141+
z = full(100, chunks=10, fill_value='NaT', dtype='m8[s]')
142+
assert np.all(np.isnat(z[:]))
143+
138144
# byte string dtype
139145
v = b'xxx'
140146
z = full(100, chunks=10, fill_value=v, dtype='S3')

zarr/tests/test_meta.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,60 @@ def test_encode_decode_array_2():
116116
assert [df.get_config()] == meta_dec['filters']
117117

118118

119+
def test_encode_decode_array_datetime_timedelta():
120+
121+
# some variations
122+
for k in ['m8[s]', 'M8[s]']:
123+
compressor = Blosc(cname='lz4', clevel=3, shuffle=2)
124+
dtype = np.dtype(k)
125+
fill_value = dtype.type("NaT")
126+
meta = dict(
127+
shape=(100, 100),
128+
chunks=(10, 10),
129+
dtype=dtype,
130+
compressor=compressor.get_config(),
131+
fill_value=fill_value,
132+
order=dtype.char,
133+
filters=[]
134+
)
135+
136+
meta_json = '''{
137+
"chunks": [10, 10],
138+
"compressor": {
139+
"id": "blosc",
140+
"clevel": 3,
141+
"cname": "lz4",
142+
"shuffle": 2,
143+
"blocksize": 0
144+
},
145+
"dtype": "%s",
146+
"fill_value": -9223372036854775808,
147+
"filters": [],
148+
"order": "%s",
149+
"shape": [100, 100],
150+
"zarr_format": %s
151+
}''' % (dtype.str, dtype.char, ZARR_FORMAT)
152+
153+
# test encoding
154+
meta_enc = encode_array_metadata(meta)
155+
assert_json_equal(meta_json, meta_enc)
156+
157+
# test decoding
158+
meta_dec = decode_array_metadata(meta_enc)
159+
assert ZARR_FORMAT == meta_dec['zarr_format']
160+
assert meta['shape'] == meta_dec['shape']
161+
assert meta['chunks'] == meta_dec['chunks']
162+
assert meta['dtype'] == meta_dec['dtype']
163+
assert meta['compressor'] == meta_dec['compressor']
164+
assert meta['order'] == meta_dec['order']
165+
# Based off of this SO answer: https://stackoverflow.com/a/49972198
166+
assert np.all(
167+
fill_value.view((np.uint8, fill_value.itemsize)) ==
168+
meta_dec['fill_value'].view((np.uint8, meta_dec['fill_value'].itemsize))
169+
)
170+
assert [] == meta_dec['filters']
171+
172+
119173
def test_encode_decode_array_dtype_shape():
120174

121175
meta = dict(

0 commit comments

Comments
 (0)