Skip to content

Commit 5123085

Browse files
authored
Merge pull request #602 from dcs4cop/pont-555-tz-naive-index
Tile server: ensure timezone-naive time selector
2 parents b575ead + 19061e7 commit 5123085

File tree

3 files changed

+79
-3
lines changed

3 files changed

+79
-3
lines changed

test/core/test_tile.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import unittest
22

33
import numpy as np
4+
import pandas as pd
45
import xarray as xr
56

67
from xcube.core.tile import get_var_valid_range
78
from xcube.core.tile import parse_non_spatial_labels
9+
from xcube.core.tile import _ensure_time_compatible
810

911

1012
class GetVarValidRangeTest(unittest.TestCase):
@@ -102,3 +104,49 @@ def test_invalid(self):
102104
coords=self.coords)
103105
self.assertEqual("'jetzt' is not a valid value for dimension 'time'",
104106
f'{cm.exception}')
107+
108+
def test_ensure_timezone_naive(self):
109+
da_tznaive = xr.DataArray(
110+
np.zeros((3,3,3)),
111+
coords=self.coords,
112+
dims=self.dims)
113+
labels = parse_non_spatial_labels(dict(time='2000-01-02T00:00:00Z'),
114+
dims=da_tznaive.dims,
115+
coords=da_tznaive.coords,
116+
var=da_tznaive)
117+
self.assertIsNone(pd.Timestamp(labels['time']).tzinfo)
118+
119+
120+
class EnsureTimeCompatibleTest(unittest.TestCase):
121+
da_tznaive = xr.DataArray(
122+
np.arange(1, 4),
123+
coords=dict(time=np.arange('2000-01-01', '2000-01-04',
124+
dtype=np.datetime64)),
125+
dims=['time'])
126+
da_tzaware = xr.DataArray(
127+
np.arange(1, 4),
128+
coords=dict(time=pd.date_range("2000-01-01", "2000-01-03", tz='UTC')),
129+
dims=['time'])
130+
labels_tznaive = dict(time='2000-01-02')
131+
labels_tzaware = dict(time='2000-01-02T00:00:00Z')
132+
133+
def test_both_tznaive(self):
134+
self.assertEqual(self.labels_tznaive,
135+
_ensure_time_compatible(self.da_tznaive,
136+
self.labels_tznaive))
137+
138+
def test_both_tzaware(self):
139+
self.assertEqual(self.labels_tzaware,
140+
_ensure_time_compatible(self.da_tzaware,
141+
self.labels_tzaware))
142+
143+
def test_tznaive_array_tzaware_indexer(self):
144+
self.assertTrue(
145+
_are_times_equal(
146+
self.labels_tznaive,
147+
_ensure_time_compatible(self.da_tznaive,
148+
self.labels_tzaware)))
149+
150+
151+
def _are_times_equal(labels1, labels2):
152+
return pd.Timestamp(labels1['time']) == pd.Timestamp(labels2['time'])

xcube/core/tile.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,29 @@ def _get_var_2d_array(var: xr.DataArray,
250250
return array
251251

252252

253+
def _ensure_time_compatible(var: xr.DataArray,
254+
labels: Dict[str, Any]) -> Dict[str, Any]:
255+
"""Ensure that labels['time'] is timezone-naive, if necessary.
256+
If var has a 'time' dimension of type datetime64 and labels has a 'time'
257+
key with a timezone-aware value, return a modified labels dictionary with
258+
a timezone-naive time value. Otherwise return the original labels.
259+
"""
260+
if _has_datetime64_time(var) and \
261+
'time' in labels and pd.Timestamp(labels['time']).tzinfo is not None:
262+
naive_time = pd.Timestamp(labels['time']).tz_convert(None)
263+
return dict(labels, time=naive_time)
264+
else:
265+
return labels
266+
267+
268+
def _has_datetime64_time(var: xr.DataArray) -> bool:
269+
"""Report whether var has a time dimension with type datetime64"""
270+
return 'time' in var.dims and \
271+
hasattr(var['time'], 'dtype') and \
272+
hasattr(var['time'].dtype, 'type') and \
273+
var['time'].dtype.type is np.datetime64
274+
275+
253276
def get_var_cmap_params(var: xr.DataArray,
254277
cmap_name: Optional[str],
255278
cmap_range: Tuple[Optional[float], Optional[float]],
@@ -304,7 +327,8 @@ def parse_non_spatial_labels(
304327
dims: Sequence[Hashable],
305328
coords: Mapping[Hashable, xr.DataArray],
306329
allow_slices: bool = False,
307-
exception_type: type = ValueError
330+
exception_type: type = ValueError,
331+
var: xr.DataArray = None
308332
) -> Mapping[str, Any]:
309333
xy_var_names = get_dataset_xy_var_names(coords, must_exist=False)
310334
if xy_var_names is None:
@@ -360,4 +384,7 @@ def to_datetime(datetime_str: str, dim_var: xr.DataArray):
360384
raise exception_type(f'{label_str!r} is not a valid'
361385
f' value for dimension {dim!r}') from e
362386

363-
return parsed_labels
387+
if var is not None:
388+
return _ensure_time_compatible(var, parsed_labels)
389+
else:
390+
return parsed_labels

xcube/webapi/controllers/tiles.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ def get_dataset_tile(ctx: ServiceContext,
7979
var.dims,
8080
var.coords,
8181
allow_slices=False,
82-
exception_type=ServiceBadRequestError)
82+
exception_type=ServiceBadRequestError,
83+
var=var)
8384

8485
return get_ml_dataset_tile(ml_dataset,
8586
var_name,

0 commit comments

Comments
 (0)