Skip to content

Commit ca5050d

Browse files
committed
set default resolution to "s", which actually means, use pandas lowest resolution, fix code and tests to allow this
1 parent 1d03a43 commit ca5050d

File tree

7 files changed

+63
-29
lines changed

7 files changed

+63
-29
lines changed

xarray/coding/times.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from xarray.core.common import contains_cftime_datetimes, is_np_datetime_like
2525
from xarray.core.duck_array_ops import asarray, ravel, reshape
2626
from xarray.core.formatting import first_n_items, format_timestamp, last_item
27+
from xarray.core.options import _get_datetime_resolution
2728
from xarray.core.pdcompat import _timestamp_as_unit, default_precision_timestamp
2829
from xarray.core.utils import emit_user_level_warning
2930
from xarray.core.variable import Variable
@@ -98,6 +99,13 @@ def _is_numpy_compatible_time_range(times):
9899
tmin = times.min()
99100
tmax = times.max()
100101
try:
102+
# before relaxing the nanosecond constrained
103+
# this raised OutOfBoundsDatetime for
104+
# times < 1678 and times > 2262
105+
# this isn't the case anymore for other resolutions like "s"
106+
# now, we raise for dates before 1582-10-15
107+
_check_date_is_after_shift(tmin, "standard")
108+
_check_date_is_after_shift(tmax, "standard")
101109
convert_time_or_go_back(tmin, pd.Timestamp)
102110
convert_time_or_go_back(tmax, pd.Timestamp)
103111
except pd.errors.OutOfBoundsDatetime:
@@ -290,7 +298,7 @@ def _check_date_is_after_shift(date: pd.Timestamp, calendar: str) -> None:
290298
# proleptic_gregorian and standard/gregorian are only equivalent
291299
# if reference date and date range is >= 1582-10-15
292300
if calendar != "proleptic_gregorian":
293-
if date < pd.Timestamp("1582-10-15"):
301+
if date < type(date)(1582, 10, 15):
294302
raise OutOfBoundsDatetime(
295303
f"Dates before 1582-10-15 cannot be decoded "
296304
f"with pandas using {calendar!r} calendar."
@@ -318,6 +326,7 @@ def _decode_datetime_with_pandas(
318326
try:
319327
time_unit, ref_date = _unpack_time_unit_and_ref_date(units)
320328
ref_date = _align_reference_date_and_unit(ref_date, time_unit)
329+
ref_date = _align_reference_date_and_unit(ref_date, _get_datetime_resolution())
321330
except ValueError as err:
322331
# ValueError is raised by pd.Timestamp for non-ISO timestamp
323332
# strings, in which case we fall back to using cftime

xarray/core/options.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ class set_options:
267267
warn_for_unclosed_files : bool, default: False
268268
Whether or not to issue a warning when unclosed files are
269269
deallocated. This is mostly useful for debugging.
270-
time_resolution : {"s", "ms", "us", "ns"}, default: "ns"
270+
time_resolution : {"s", "ms", "us", "ns"}, default: "s"
271271
Time resolution used for CF encoding/decoding.
272272
273273
Examples

xarray/tests/__init__.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from xarray import Dataset
1919
from xarray.core.duck_array_ops import allclose_or_equiv # noqa: F401
2020
from xarray.core.extension_array import PandasExtensionArray
21-
from xarray.core.options import set_options
21+
from xarray.core.options import _get_datetime_resolution, set_options
2222
from xarray.core.variable import IndexVariable
2323
from xarray.testing import ( # noqa: F401
2424
assert_chunks_equal,
@@ -323,7 +323,10 @@ def create_test_data(
323323
f'Not enough letters for filling this dimension size ({_dims["dim3"]})'
324324
)
325325
obj["dim3"] = ("dim3", list(string.ascii_lowercase[0 : _dims["dim3"]]))
326-
obj["time"] = ("time", pd.date_range("2000-01-01", periods=20, unit="s"))
326+
obj["time"] = (
327+
"time",
328+
pd.date_range("2000-01-01", periods=20, unit=f"{_get_datetime_resolution()}"),
329+
)
327330
for v, dims in sorted(_vars.items()):
328331
data = rs.normal(size=tuple(_dims[d] for d in dims))
329332
obj[v] = (dims, data)

xarray/tests/test_backends.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
from xarray.coding.variables import SerializationWarning
5454
from xarray.conventions import encode_dataset_coordinates
5555
from xarray.core import indexing
56-
from xarray.core.options import set_options
56+
from xarray.core.options import _get_datetime_resolution, set_options
5757
from xarray.core.utils import module_available
5858
from xarray.namedarray.pycompat import array_type
5959
from xarray.tests import (
@@ -1590,8 +1590,9 @@ def test_open_encodings(self) -> None:
15901590

15911591
expected = Dataset()
15921592

1593-
# todo: check, if specifying "s" is enough
1594-
time = pd.date_range("1999-01-05", periods=10, unit="s")
1593+
time = pd.date_range(
1594+
"1999-01-05", periods=10, unit=f"{_get_datetime_resolution()}"
1595+
)
15951596
encoding = {"units": units, "dtype": np.dtype("int32")}
15961597
expected["time"] = ("time", time, {}, encoding)
15971598

xarray/tests/test_coding_times.py

+31-13
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from xarray.coding.variables import SerializationWarning
4040
from xarray.conventions import _update_bounds_attributes, cf_encoder
4141
from xarray.core.common import contains_cftime_datetimes
42+
from xarray.core.options import _get_datetime_resolution
4243
from xarray.core.utils import is_duck_dask_array
4344
from xarray.testing import assert_equal, assert_identical
4445
from xarray.tests import (
@@ -134,7 +135,9 @@ def test_cf_datetime(num_dates, units, calendar) -> None:
134135
max_y = np.ravel(np.atleast_1d(expected))[np.nanargmax(num_dates)] # .year
135136
typ = type(min_y)
136137
border = typ(1582, 10, 15)
137-
if calendar == "proleptic_gregorian" or (min_y >= border and max_y >= border):
138+
if (calendar == "proleptic_gregorian" and _get_datetime_resolution() != "ns") or (
139+
min_y >= border and max_y >= border
140+
):
138141
expected = cftime_to_nptime(expected)
139142

140143
with warnings.catch_warnings():
@@ -214,12 +217,15 @@ def test_decode_standard_calendar_inside_timestamp_range(calendar) -> None:
214217
import cftime
215218

216219
units = "days since 0001-01-01"
217-
unit = cast(Literal["s", "ms", "us", "ns"], "us")
220+
unit = cast(Literal["s", "ms", "us", "ns"], _get_datetime_resolution())
218221
times = pd.date_range("2001-04-01-00", end="2001-04-30-23", unit=unit, freq="h")
222+
# to_pydatetime() will return microsecond
219223
time = cftime.date2num(times.to_pydatetime(), units, calendar=calendar)
220224
expected = times.values
221-
if calendar == "proleptic_gregorian":
222-
unit = "s"
225+
# for cftime we get "us" resolution
226+
# ns resolution is handled by cftime, too (OutOfBounds)
227+
if calendar != "proleptic_gregorian" or _get_datetime_resolution() == "ns":
228+
unit = "us"
223229
expected_dtype = np.dtype(f"M8[{unit}]")
224230
actual = decode_cf_datetime(time, units, calendar=calendar)
225231
assert actual.dtype == expected_dtype
@@ -268,7 +274,7 @@ def test_decode_dates_outside_timestamp_range(calendar) -> None:
268274
time, units, calendar=calendar, only_use_cftime_datetimes=True
269275
)
270276
# special case proleptic_gregorian
271-
if calendar == "proleptic_gregorian":
277+
if calendar == "proleptic_gregorian" and _get_datetime_resolution() != "ns":
272278
expected = expected.astype("=M8[us]")
273279
expected_date_type = type(expected[0])
274280

@@ -289,7 +295,11 @@ def test_decode_standard_calendar_single_element_inside_timestamp_range(
289295
calendar,
290296
) -> None:
291297
units = "days since 0001-01-01"
292-
unit = "s" if calendar == "proleptic_gregorian" else "us"
298+
unit = (
299+
_get_datetime_resolution()
300+
if (calendar == "proleptic_gregorian" and _get_datetime_resolution() != "ns")
301+
else "us"
302+
)
293303
for num_time in [735368, [735368], [[735368]]]:
294304
with warnings.catch_warnings():
295305
warnings.filterwarnings("ignore", "Unable to decode time axis")
@@ -337,7 +347,11 @@ def test_decode_standard_calendar_multidim_time_inside_timestamp_range(
337347
import cftime
338348

339349
units = "days since 0001-01-01"
340-
unit = "s" if calendar == "proleptic_gregorian" else "us"
350+
unit = (
351+
_get_datetime_resolution()
352+
if (calendar == "proleptic_gregorian" and _get_datetime_resolution() != "ns")
353+
else "us"
354+
)
341355
times1 = pd.date_range("2001-04-01", end="2001-04-05", freq="D")
342356
times2 = pd.date_range("2001-05-01", end="2001-05-05", freq="D")
343357
time1 = cftime.date2num(times1.to_pydatetime(), units, calendar=calendar)
@@ -426,8 +440,8 @@ def test_decode_multidim_time_outside_timestamp_range(calendar) -> None:
426440
actual = decode_cf_datetime(mdim_time, units, calendar=calendar)
427441

428442
dtype: np.dtype
429-
if calendar == "proleptic_gregorian":
430-
dtype = np.dtype("=M8[s]")
443+
if calendar == "proleptic_gregorian" and _get_datetime_resolution() != "ns":
444+
dtype = np.dtype(f"=M8[{_get_datetime_resolution()}]")
431445
expected1 = expected1.astype(dtype)
432446
expected2 = expected2.astype(dtype)
433447
else:
@@ -528,7 +542,7 @@ def test_decoded_cf_datetime_array_2d() -> None:
528542
("x", "y"), np.array([[0, 1], [2, 3]]), {"units": "days since 2000-01-01"}
529543
)
530544
result = CFDatetimeCoder().decode(variable)
531-
assert result.dtype == "datetime64[s]"
545+
assert result.dtype == f"datetime64[{_get_datetime_resolution()}]"
532546
expected = pd.date_range("2000-01-01", periods=4).values.reshape(2, 2)
533547
assert_array_equal(np.asarray(result), expected)
534548

@@ -697,7 +711,7 @@ def test_decode_cf(calendar) -> None:
697711
if calendar not in _STANDARD_CALENDARS:
698712
assert ds.test.dtype == np.dtype("O")
699713
else:
700-
assert ds.test.dtype == np.dtype("M8[s]")
714+
assert ds.test.dtype == np.dtype(f"M8[{_get_datetime_resolution()}]")
701715

702716

703717
def test_decode_cf_time_bounds() -> None:
@@ -722,7 +736,7 @@ def test_decode_cf_time_bounds() -> None:
722736
"calendar": "standard",
723737
}
724738
dsc = decode_cf(ds)
725-
assert dsc.time_bnds.dtype == np.dtype("M8[s]")
739+
assert dsc.time_bnds.dtype == np.dtype(f"M8[{_get_datetime_resolution()}]")
726740
dsc = decode_cf(ds, decode_times=False)
727741
assert dsc.time_bnds.dtype == np.dtype("int64")
728742

@@ -1299,7 +1313,11 @@ def test_roundtrip_datetime64_nanosecond_precision(
12991313
assert encoded_var.data.dtype == dtype
13001314

13011315
decoded_var = conventions.decode_cf_variable("foo", encoded_var)
1302-
assert decoded_var.dtype == np.dtype(f"=M8[{timeunit}]")
1316+
if _get_datetime_resolution() == "ns":
1317+
dtypeunit = "ns"
1318+
else:
1319+
dtypeunit = timeunit
1320+
assert decoded_var.dtype == np.dtype(f"=M8[{dtypeunit}]")
13031321
assert (
13041322
decoded_var.encoding["units"]
13051323
== f"{_numpy_to_netcdf_timeunit(timeunit)} since 1970-01-01 00:00:00"

xarray/tests/test_conventions.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from xarray.backends.common import WritableCFDataStore
2020
from xarray.backends.memory import InMemoryDataStore
2121
from xarray.conventions import decode_cf
22+
from xarray.core.options import _get_datetime_resolution
2223
from xarray.testing import assert_identical
2324
from xarray.tests import (
2425
assert_array_equal,
@@ -364,7 +365,7 @@ def test_dataset_repr_with_netcdf4_datetimes(self) -> None:
364365

365366
attrs = {"units": "days since 1900-01-01"}
366367
ds = decode_cf(Dataset({"time": ("time", [0, 1], attrs)}))
367-
assert "(time) datetime64[s]" in repr(ds)
368+
assert f"(time) datetime64[{_get_datetime_resolution()}]" in repr(ds)
368369

369370
@requires_cftime
370371
def test_decode_cf_datetime_transition_to_invalid(self) -> None:
@@ -447,13 +448,13 @@ def test_decode_cf_time_kwargs(self) -> None:
447448

448449
dsc = conventions.decode_cf(ds)
449450
assert dsc.timedelta.dtype == np.dtype("m8[ns]")
450-
assert dsc.time.dtype == np.dtype("M8[s]")
451+
assert dsc.time.dtype == np.dtype(f"M8[{_get_datetime_resolution()}]")
451452
dsc = conventions.decode_cf(ds, decode_times=False)
452453
assert dsc.timedelta.dtype == np.dtype("int64")
453454
assert dsc.time.dtype == np.dtype("int64")
454455
dsc = conventions.decode_cf(ds, decode_times=True, decode_timedelta=False)
455456
assert dsc.timedelta.dtype == np.dtype("int64")
456-
assert dsc.time.dtype == np.dtype("M8[s]")
457+
assert dsc.time.dtype == np.dtype(f"M8[{_get_datetime_resolution()}]")
457458
dsc = conventions.decode_cf(ds, decode_times=False, decode_timedelta=True)
458459
assert dsc.timedelta.dtype == np.dtype("m8[ns]")
459460
assert dsc.time.dtype == np.dtype("int64")

xarray/tests/test_dataset.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from xarray.core.common import duck_array_ops, full_like
4040
from xarray.core.coordinates import Coordinates, DatasetCoordinates
4141
from xarray.core.indexes import Index, PandasIndex
42+
from xarray.core.options import _get_datetime_resolution
4243
from xarray.core.types import ArrayLike
4344
from xarray.core.utils import is_scalar
4445
from xarray.groupers import TimeResampler
@@ -290,7 +291,7 @@ def test_repr(self) -> None:
290291
Coordinates:
291292
* dim2 (dim2) float64 72B 0.0 0.5 1.0 1.5 2.0 2.5 3.0 3.5 4.0
292293
* dim3 (dim3) {} 40B 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j'
293-
* time (time) datetime64[s] 160B 2000-01-01 2000-01-02 ... 2000-01-20
294+
* time (time) datetime64[{}] 160B 2000-01-01 2000-01-02 ... 2000-01-20
294295
numbers (dim3) int64 80B 0 1 2 0 0 1 1 2 2 3
295296
Dimensions without coordinates: dim1
296297
Data variables:
@@ -299,7 +300,8 @@ def test_repr(self) -> None:
299300
var3 (dim3, dim1) float64 640B 0.5565 -0.2121 0.4563 ... -0.2452 -0.3616
300301
Attributes:
301302
foo: bar""".format(
302-
data["dim3"].dtype
303+
data["dim3"].dtype,
304+
_get_datetime_resolution(),
303305
)
304306
)
305307
actual = "\n".join(x.rstrip() for x in repr(data).split("\n"))
@@ -442,8 +444,8 @@ def test_info(self) -> None:
442444
ds.info(buf=buf)
443445

444446
expected = dedent(
445-
"""\
446-
xarray.Dataset {
447+
f"""\
448+
xarray.Dataset {{
447449
dimensions:
448450
\tdim2 = 9 ;
449451
\ttime = 20 ;
@@ -452,7 +454,7 @@ def test_info(self) -> None:
452454
453455
variables:
454456
\tfloat64 dim2(dim2) ;
455-
\tdatetime64[s] time(time) ;
457+
\tdatetime64[{_get_datetime_resolution()}] time(time) ;
456458
\tfloat64 var1(dim1, dim2) ;
457459
\t\tvar1:foo = variable ;
458460
\tfloat64 var2(dim1, dim2) ;
@@ -464,7 +466,7 @@ def test_info(self) -> None:
464466
// global attributes:
465467
\t:unicode_attr = ba® ;
466468
\t:string_attr = bar ;
467-
}"""
469+
}}"""
468470
)
469471
actual = buf.getvalue()
470472
assert expected == actual

0 commit comments

Comments
 (0)