diff --git a/xarray/coding/times.py b/xarray/coding/times.py index 77b2d2c7937..10a1c9f62e7 100644 --- a/xarray/coding/times.py +++ b/xarray/coding/times.py @@ -26,6 +26,7 @@ _STANDARD_CALENDARS = {"standard", "gregorian", "proleptic_gregorian"} _NS_PER_TIME_DELTA = { + "ns": int(1e0), "us": int(1e3), "ms": int(1e6), "s": int(1e9), @@ -35,7 +36,15 @@ } TIME_UNITS = frozenset( - ["days", "hours", "minutes", "seconds", "milliseconds", "microseconds"] + [ + "days", + "hours", + "minutes", + "seconds", + "milliseconds", + "microseconds", + "nanoseconds", + ] ) @@ -44,6 +53,7 @@ def _netcdf_to_numpy_timeunit(units): if not units.endswith("s"): units = "%ss" % units return { + "nanoseconds": "ns", "microseconds": "us", "milliseconds": "ms", "seconds": "s", diff --git a/xarray/tests/test_backends.py b/xarray/tests/test_backends.py index 1173fed0055..4a6e534c667 100644 --- a/xarray/tests/test_backends.py +++ b/xarray/tests/test_backends.py @@ -4601,3 +4601,40 @@ def test_extract_zarr_variable_encoding(): actual = backends.zarr.extract_zarr_variable_encoding( var, raise_on_invalid=True ) + + +@requires_netCDF4 +def test_cftime_nanosecond(tmp_path): + import netCDF4 + + time_data = xr.DataArray( + np.asarray( + ["2020-09-01T00:00:00.000000", "2020-09-01T00:00:00.586001"], + dtype=np.datetime64, + ), + name="time", + ) + filename = str(tmp_path / "time.nc") + + time_data.to_netcdf(filename) + with netCDF4.Dataset(filename, mode="r") as loaded: + # Prior to 0.10.9, the time encoding for objects with nano second + # deltas was in seconds, but use floating point to attempt to maintain + # the difference + assert loaded["time"].units.startswith("seconds") + assert loaded["time"].dtype == np.float64 + + time_data.encoding = { + "units": "nanoseconds since 2020-09-01", + "calendar": "proleptic_gregorian", + } + time_data.to_netcdf(filename) + with netCDF4.Dataset(filename, mode="r") as loaded: + # Prior to 0.10.9, the time encoding for objects with nano second + # deltas was in seconds, but use floating point to attempt to maintain + # the difference + assert loaded["time"].units.startswith("nanoseconds") + assert loaded["time"].dtype == np.int64 + + time_data_loaded = xr.load_dataarray(filename) + assert_equal(time_data, time_data_loaded) diff --git a/xarray/tests/test_coding_times.py b/xarray/tests/test_coding_times.py index 457e68f5593..037972ba258 100644 --- a/xarray/tests/test_coding_times.py +++ b/xarray/tests/test_coding_times.py @@ -58,6 +58,8 @@ ([0.5, 1.5], "hours since 1900-01-01T00:00:00"), (0, "milliseconds since 2000-01-01T00:00:00"), (0, "microseconds since 2000-01-01T00:00:00"), + # 2020/09/01: hmaarrfk: cftime/_cftime.pyx doesn't support nanosecond + # (0, "nanoseconds since 2000-01-01T00:00:00"), (np.int32(788961600), "seconds since 1981-01-01"), # GH2002 (12300 + np.arange(5), "hour since 1680-01-01 00:00:00.500000"), ] @@ -537,6 +539,7 @@ def test_infer_cftime_datetime_units(calendar, date_args, expected): ("1h", "hours", np.int64(1)), ("1ms", "milliseconds", np.int64(1)), ("1us", "microseconds", np.int64(1)), + ("1ns", "nanoseconds", np.int64(1)), (["NaT", "0s", "1s"], None, [np.nan, 0, 1]), (["30m", "60m"], "hours", [0.5, 1.0]), ("NaT", "days", np.nan),