Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 51 additions & 11 deletions cf/data/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -10294,31 +10294,71 @@ def outerproduct(self, e, inplace=False, i=False):

return d

@daskified(_DASKIFIED_VERBOSE)
@_deprecated_kwarg_check("i")
@_inplace_enabled(default=False)
def change_calendar(self, calendar, inplace=False, i=False):
"""Change the calendar of the data array elements.
"""Change the calendar of date-time array elements.

Reinterprets the existing date-times for the new calendar by
adjusting the underlying numerical values relative to the
reference date-time defined by the units.

If a date-time value is not allowed in the new calendar then
an exception is raised when the data array is accessed.

.. seealso:: `override_calendar`, `Units`

:Parameters:

calendar: `str`
The new calendar, as recognised by the CF conventions.

*Parameter example:*
``'proleptic_gregorian'``

Changing the calendar could result in a change of reference time
data array values.
{{inplace: `bool`, optional}}

Not to be confused with using the `override_calendar` method or
resetting `d.Units`. `override_calendar` is different because the
new calendar need not be equivalent to the original ones and the
data array elements will not be changed to reflect the new
units. Resetting `d.Units` will
{{i: deprecated at version 3.0.0}}

:Returns:

`Data` or `None`
The new data with updated calendar, or `None` if the
operation was in-place.

**Examples**

>>> d = cf.Data([0, 1, 2, 3, 4], 'days since 2004-02-27')
>>> print(d.array)
[0 1 2 3 4]
>>> print(d.datetime_as_string)
['2004-02-27 00:00:00' '2004-02-28 00:00:00' '2004-02-29 00:00:00'
'2004-03-01 00:00:00' '2004-03-02 00:00:00']
>>> e = d.change_calendar('360_day')
>>> print(e.array)
[0 1 2 4 5]
>>> print(e.datetime_as_string)
['2004-02-27 00:00:00' '2004-02-28 00:00:00' '2004-02-29 00:00:00'
'2004-03-01 00:00:00' '2004-03-02 00:00:00']

>>> d.change_calendar('noleap').array
Traceback (most recent call last):
...
ValueError: invalid day number provided in cftime.DatetimeNoLeap(2004, 2, 29, 0, 0, 0, 0, has_year_zero=True)

"""
d = _inplace_enabled_define_and_cleanup(self)

if not self.Units.isreftime:
units = self.Units
if not units.isreftime:
raise ValueError(
"Can't change calendar of non-reference time "
"units: {!r}".format(self.Units)
f"units: {units!r}"
)

d._asdatetime(inplace=True)
d.override_units(Units(self.Units.units, calendar), inplace=True)
d.override_calendar(calendar, inplace=True)
d._asreftime(inplace=True)

return d
Expand Down
14 changes: 14 additions & 0 deletions cf/test/test_Data.py
Original file line number Diff line number Diff line change
Expand Up @@ -3842,6 +3842,20 @@ def test_Data__bool__(self):
with self.assertRaises(ValueError):
bool(cf.Data([1, 2]))

def test_Data_change_calendar(self):
d = cf.Data(
[0, 1, 2, 3, 4], "days since 2004-02-27", calendar="standard"
)
e = d.change_calendar("360_day")
self.assertTrue(np.allclose(e.array, [0, 1, 2, 4, 5]))
self.assertEqual(e.Units, cf.Units("days since 2004-02-27", "360_day"))

# An Exception should be raised when a date is stored that is
# invalid to the calendar (e.g. 29th of February in the noleap
# calendar).
with self.assertRaises(ValueError):
e = d.change_calendar("noleap").array


if __name__ == "__main__":
print("Run date:", datetime.datetime.now())
Expand Down