Skip to content

Commit

Permalink
Convert calendar to lowercase in standard calendar checks (pydata#5180)
Browse files Browse the repository at this point in the history
  • Loading branch information
spencerkclark authored Apr 18, 2021
1 parent aeb7d4d commit 44f4ae1
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 5 deletions.
5 changes: 5 additions & 0 deletions doc/whats-new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ Bug fixes
By `Justus Magin <https://github.com/keewis>`_.
- Decode values as signed if attribute `_Unsigned = "false"` (:issue:`4954`)
By `Tobias Kölling <https://github.com/d70-t>`_.
- Ensure standard calendar dates encoded with a calendar attribute with some or
all uppercase letters can be decoded or encoded to or from
``np.datetime64[ns]`` dates with or without ``cftime`` installed
(:issue:`5093`, :pull:`5180`). By `Spencer Clark
<https://github.com/spencerkclark>`_.

Documentation
~~~~~~~~~~~~~
Expand Down
12 changes: 8 additions & 4 deletions xarray/coding/times.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@
)


def _is_standard_calendar(calendar):
return calendar.lower() in _STANDARD_CALENDARS


def _netcdf_to_numpy_timeunit(units):
units = units.lower()
if not units.endswith("s"):
Expand Down Expand Up @@ -166,7 +170,7 @@ def _decode_datetime_with_cftime(num_dates, units, calendar):


def _decode_datetime_with_pandas(flat_num_dates, units, calendar):
if calendar not in _STANDARD_CALENDARS:
if not _is_standard_calendar(calendar):
raise OutOfBoundsDatetime(
"Cannot decode times from a non-standard calendar, {!r}, using "
"pandas.".format(calendar)
Expand Down Expand Up @@ -237,7 +241,7 @@ def decode_cf_datetime(num_dates, units, calendar=None, use_cftime=None):
dates[np.nanargmin(num_dates)].year < 1678
or dates[np.nanargmax(num_dates)].year >= 2262
):
if calendar in _STANDARD_CALENDARS:
if _is_standard_calendar(calendar):
warnings.warn(
"Unable to decode time axis into full "
"numpy.datetime64 objects, continuing using "
Expand All @@ -247,7 +251,7 @@ def decode_cf_datetime(num_dates, units, calendar=None, use_cftime=None):
stacklevel=3,
)
else:
if calendar in _STANDARD_CALENDARS:
if _is_standard_calendar(calendar):
dates = cftime_to_nptime(dates)
elif use_cftime:
dates = _decode_datetime_with_cftime(flat_num_dates, units, calendar)
Expand Down Expand Up @@ -450,7 +454,7 @@ def encode_cf_datetime(dates, units=None, calendar=None):

delta, ref_date = _unpack_netcdf_time_units(units)
try:
if calendar not in _STANDARD_CALENDARS or dates.dtype.kind == "O":
if not _is_standard_calendar(calendar) or dates.dtype.kind == "O":
# parse with cftime instead
raise OutOfBoundsDatetime
assert dates.dtype == "datetime64[ns]"
Expand Down
22 changes: 21 additions & 1 deletion xarray/tests/test_coding_times.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from xarray.coding.variables import SerializationWarning
from xarray.conventions import _update_bounds_attributes, cf_encoder
from xarray.core.common import contains_cftime_datetimes
from xarray.testing import assert_equal
from xarray.testing import assert_equal, assert_identical

from . import (
arm_xfail,
Expand Down Expand Up @@ -1049,3 +1049,23 @@ def test__encode_datetime_with_cftime():
expected = cftime.date2num(times, encoding_units, calendar)
result = _encode_datetime_with_cftime(times, encoding_units, calendar)
np.testing.assert_equal(result, expected)


@pytest.mark.parametrize("calendar", ["gregorian", "Gregorian", "GREGORIAN"])
def test_decode_encode_roundtrip_with_non_lowercase_letters(calendar):
# See GH 5093.
times = [0, 1]
units = "days since 2000-01-01"
attrs = {"calendar": calendar, "units": units}
variable = Variable(["time"], times, attrs)
decoded = conventions.decode_cf_variable("time", variable)
encoded = conventions.encode_cf_variable(decoded)

# Previously this would erroneously be an array of cftime.datetime
# objects. We check here that it is decoded properly to np.datetime64.
assert np.issubdtype(decoded.dtype, np.datetime64)

# Use assert_identical to ensure that the calendar attribute maintained its
# original form throughout the roundtripping process, uppercase letters and
# all.
assert_identical(variable, encoded)

0 comments on commit 44f4ae1

Please sign in to comment.