diff --git a/cf_units/__init__.py b/cf_units/__init__.py index f6e72d61..a62e93a5 100644 --- a/cf_units/__init__.py +++ b/cf_units/__init__.py @@ -1746,6 +1746,31 @@ def __ne__(self, other): """ return not self == other + def change_calendar(self, calendar): + """ + Returns a new unit with the requested calendar, modifying the reference + date if necessary. Only works with calendars that represent the real + world (standard, proleptic_gregorian, julian) and with short time + intervals (days or less). + + For example: + + >>> from cf_units import Unit + >>> u = Unit('days since 1500-01-01', calendar='proleptic_gregorian') + >>> u.change_calendar('standard') + Unit('days since 1499-12-23T00:00:00', calendar='standard') + + """ + if not self.is_time_reference(): + raise ValueError("unit is not a time reference") + + ref_date = self.num2date(0) + new_ref_date = ref_date.change_calendar(calendar) + time_units = self.origin.split(_OP_SINCE)[0] + new_origin = _OP_SINCE.join([time_units, new_ref_date.isoformat()]) + + return Unit(new_origin, calendar=calendar) + def convert(self, value, other, ctype=FLOAT64, inplace=False): """ Converts a single value or NumPy array of values from the current unit diff --git a/cf_units/tests/unit/unit/test_Unit.py b/cf_units/tests/unit/unit/test_Unit.py index 83e93788..9866d31d 100644 --- a/cf_units/tests/unit/unit/test_Unit.py +++ b/cf_units/tests/unit/unit/test_Unit.py @@ -31,6 +31,51 @@ def test_hash_replacement(self): self.assertEqual(u, expected) +class Test_change_calendar(unittest.TestCase): + def test_modern_standard_to_proleptic_gregorian(self): + u = Unit("hours since 1970-01-01 00:00:00", calendar="standard") + expected = Unit( + "hours since 1970-01-01 00:00:00", calendar="proleptic_gregorian" + ) + result = u.change_calendar("proleptic_gregorian") + self.assertEqual(result, expected) + + def test_ancient_standard_to_proleptic_gregorian(self): + u = Unit("hours since 1500-01-01 00:00:00", calendar="standard") + expected = Unit( + "hours since 1500-01-10 00:00:00", calendar="proleptic_gregorian" + ) + result = u.change_calendar("proleptic_gregorian") + self.assertEqual(result, expected) + + def test_no_change(self): + u = Unit("hours since 1500-01-01 00:00:00", calendar="standard") + result = u.change_calendar("standard") + self.assertEqual(result, u) + # Docstring states that a new unit is returned, so check these are not + # the same object. + self.assertIsNot(result, u) + + def test_long_time_interval(self): + u = Unit("months since 1970-01-01", calendar="standard") + with self.assertRaisesRegex(ValueError, "cannot be processed"): + u.change_calendar("proleptic_gregorian") + + def test_wrong_calendar(self): + u = Unit("days since 1900-01-01", calendar="360_day") + with self.assertRaisesRegex( + ValueError, "change_calendar only works for real-world calendars" + ): + u.change_calendar("standard") + + def test_non_time_unit(self): + u = Unit("m") + with self.assertRaisesRegex( + ValueError, "unit is not a time reference" + ): + u.change_calendar("standard") + + class Test_convert__calendar(unittest.TestCase): class MyStr(str): pass