From ba43c7251b0f8ad525d55c797643fd4b02403132 Mon Sep 17 00:00:00 2001 From: David Hassell Date: Fri, 18 Mar 2022 16:35:05 +0000 Subject: [PATCH 1/2] units/calendar tidy --- cf/data/data.py | 158 ++++++++++++++++++++++++++++++------------- cf/test/test_Data.py | 55 ++++++++------- 2 files changed, 138 insertions(+), 75 deletions(-) diff --git a/cf/data/data.py b/cf/data/data.py index 68523323d5..7def01f4c5 100644 --- a/cf/data/data.py +++ b/cf/data/data.py @@ -7722,7 +7722,7 @@ def get_units(self, default=ValueError()): The units. - **Examples:** + **Examples** >>> d.set_units('metres') >>> d.get_units() @@ -7803,7 +7803,7 @@ def set_calendar(self, calendar): def set_units(self, value): """Set the units. - .. seealso:: `del_units`, `get_units`, `has_units` + .. seealso:: `del_units`, `get_units`, `has_units`, `Units` :Parameters: @@ -9865,6 +9865,70 @@ def harden_mask(self): self._map_blocks(cf_harden_mask, dtype=self.dtype) self._hardmask = True + def has_calendar(self): + """Whether a calendar has been set. + + .. seealso:: `del_calendar`, `get_calendar`, `set_calendar`, + `has_units`, `Units` + + :Returns: + + `bool` + True if the calendar has been set, otherwise False. + + **Examples** + + >>> d = cf.Data(1, "days since 2000-1-1", calendar="noleap") + >>> d.has_calendar() + True + + >>> d = cf.Data(1, calendar="noleap") + >>> d.has_calendar() + True + + >>> d = cf.Data(1, "days since 2000-1-1") + >>> d.has_calendar() + False + + >>> d = cf.Data(1, "m") + >>> d.has_calendar() + False + + """ + return hasattr(self.Units, "calendar") + + def has_units(self): + """Whether units have been set. + + .. seealso:: `del_units`, `get_units`, `set_units`, + `has_calendar`, `Units` + + :Returns: + + `bool` + True if units have been set, otherwise False. + + **Examples** + + >>> d = cf.Data(1, "") + >>> d.has_units() + True + + >>> d = cf.Data(1, "m") + >>> d.has_units() + True + + >>> d = cf.Data(1) + >>> d.has_units() + False + + >>> d = cf.Data(1, calendar='noleap') + >>> d.has_units() + False + + """ + return hasattr(self.Units, "units") + def soften_mask(self): """Force the mask to soft. @@ -10819,7 +10883,7 @@ def del_calendar(self, default=ValueError()): """Delete the calendar. .. seealso:: `get_calendar`, `has_calendar`, `set_calendar`, - `del_units` + `del_units`, `Units` :Parameters: @@ -10834,38 +10898,42 @@ def del_calendar(self, default=ValueError()): `str` The value of the deleted calendar. - **Examples:** + **Examples** - >>> d.set_calendar('360_day') - >>> d.has_calendar() - True - >>> d.get_calendar() - '360_day' + >>> d = cf.Data(1, "days since 2000-1-1", calendar="noleap") >>> d.del_calendar() - >>> d.has_calendar() - False - >>> d.get_calendar() - ValueError: Can't get non-existent calendar - >>> print(d.get_calendar(None)) + 'noleap' + >>> print(d.del_calendar()) None - >>> print(d.del_calendar(None)) + + >>> d = cf.Data(1, "days since 2000-1-1") + >>> print(d.del_calendar()) None + >>> d = cf.Data(1, "m") + Traceback (most recent call last): + ... + ValueError: Units have no calendar + """ - calendar = getattr(self.Units, "calendar", None) + units = self.Units + if not units.isreftime: + return self._default(default, f"Units {units!r} have no calendar") - if calendar is not None: - self.override_calendar(None, inplace=True) - return calendar + calendar = getattr(units, "calendar", None) + if calendar is None: + return self._default( + default, f"{self.__class__.__name__} has no calendar" + ) - raise self._default( - default, f"{self.__class__.__name__} has no 'calendar' component" - ) + self.override_calendar(None, inplace=True) + return calendar def del_units(self, default=ValueError()): """Delete the units. - .. seealso:: `get_units`, `has_units`, `set_units`, `del_calendar` + .. seealso:: `get_units`, `has_units`, `set_units`, + `del_calendar`, `Units` :Parameters: @@ -10880,39 +10948,35 @@ def del_units(self, default=ValueError()): `str` The value of the deleted units. - **Examples:** + **Examples** - >>> d.set_units('metres') - >>> d.has_units() - True - >>> d.get_units() - 'metres' + >>> d = cf.Data(1, "m") >>> d.del_units() - >>> d.has_units() - False - >>> d.get_units() - ValueError: Can't get non-existent units - >>> print(d.get_units(None)) - None - >>> print(d.del_units(None)) - None - - """ - out = self.Units + 'm' + >>> d.Units + + >>> d.del_units() + Traceback (most recent call last): + ... + ValueError: Data has no units - units = getattr(out, "units", None) - calendar = getattr(out, "calendar", None) + >>> d = cf.Data(1, "days since 2000-1-1", calendar="noleap") + >>> d.del_units() + 'days since 2000-1-1' + >>> d.Units + - if calendar is not None: - self.Units = Units(None, calendar) - else: - del self.Units + """ + u = self.Units + units = getattr(u, "units", None) + calendar = getattr(u, "calendar", None) + self.override_units(Units(None, calendar), inplace=True) if units is not None: return units return self._default( - default, f"{self.__class__.__name__} has no 'units' component" + default, f"{self.__class__.__name__} has no units" ) @classmethod diff --git a/cf/test/test_Data.py b/cf/test/test_Data.py index 7c25131a7d..a18e7ef734 100644 --- a/cf/test/test_Data.py +++ b/cf/test/test_Data.py @@ -3592,67 +3592,66 @@ def test_Data_filled(self): d = cf.Data(["a", "b", "c"], mask=[1, 0, 0]) self.assertTrue((d.filled().array == ["", "b", "c"]).all()) - @unittest.skipIf(TEST_DASKIFIED_ONLY, "units-related problem") def test_Data_del_units(self): d = cf.Data(1) with self.assertRaises(ValueError): d.del_units() - d = cf.Data(1, "") - self.assertEqual(d.del_units(), "") d = cf.Data(1, "m") self.assertEqual(d.del_units(), "m") + with self.assertRaises(ValueError): + d.del_units() d = cf.Data(1, "days since 2000-1-1") - self.assertTrue(d.del_units(), "days since 2000-1-1") + self.assertEqual(d.del_units(), "days since 2000-1-1") + with self.assertRaises(ValueError): + d.del_units() d = cf.Data(1, "days since 2000-1-1", calendar="noleap") + self.assertEqual(d.del_units(), "days since 2000-1-1") + self.assertEqual(d.Units, cf.Units(None, "noleap")) with self.assertRaises(ValueError): d.del_units() def test_Data_del_calendar(self): - d = cf.Data(1) - with self.assertRaises(ValueError): - d.del_calendar() - - d = cf.Data(1, "") - with self.assertRaises(ValueError): - d.del_calendar() + for units in (None, "", "m", "days since 2000-1-1"): + d = cf.Data(1, units) + with self.assertRaises(ValueError): + d.del_calendar() - d = cf.Data(1, "m") + d = cf.Data(1, "days since 2000-1-1", calendar="noleap") + self.assertEqual(d.del_calendar(), "noleap") with self.assertRaises(ValueError): d.del_calendar() - d = cf.Data(1, "days since 2000-1-1") - with self.assertRaises(ValueError): - d.del_calendar() + def test_Data_get_calendar(self): + for units in (None, "", "m", "days since 2000-1-1"): + d = cf.Data(1, units) + with self.assertRaises(ValueError): + d.get_calendar() d = cf.Data(1, "days since 2000-1-1", calendar="noleap") - self.assertTrue(d.del_calendar(), "noleap") + self.assertTrue(d.get_calendar(), "noleap") - @unittest.skipIf(TEST_DASKIFIED_ONLY, "units-related problem") def test_Data_has_units(self): - d = cf.Data(1) - self.assertFalse(d.has_units()) d = cf.Data(1, "") self.assertTrue(d.has_units()) d = cf.Data(1, "m") self.assertTrue(d.has_units()) - @unittest.skipIf(TEST_DASKIFIED_ONLY, "units-related problem") - def test_Data_has_calendar(self): d = cf.Data(1) - self.assertFalse(d.has_calendar()) - d = cf.Data(1, "") - self.assertFalse(d.has_calendar()) - d = cf.Data(1, "m") - self.assertFalse(d.has_calendar()) + self.assertFalse(d.has_units()) + d = cf.Data(1, calendar="noleap") + self.assertFalse(d.has_units()) - d = cf.Data(1, "days since 2000-1-1") - self.assertFalse(d.has_calendar()) + def test_Data_has_calendar(self): d = cf.Data(1, "days since 2000-1-1", calendar="noleap") self.assertTrue(d.has_calendar()) + for units in (None, "", "m", "days since 2000-1-1"): + d = cf.Data(1, units) + self.assertFalse(d.has_calendar()) + def test_Data_where(self): a = np.arange(10) d = cf.Data(a) From 9ad43e3e1f3beaf9cf19fd44884ffe25aa4f76c2 Mon Sep 17 00:00:00 2001 From: David Hassell Date: Mon, 21 Mar 2022 14:58:14 +0000 Subject: [PATCH 2/2] units/calendar compatibility with cfdm --- cf/data/data.py | 10 ++++++---- cf/test/test_Data.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/cf/data/data.py b/cf/data/data.py index 7def01f4c5..c93d48f7d4 100644 --- a/cf/data/data.py +++ b/cf/data/data.py @@ -7775,7 +7775,8 @@ def get_calendar(self, default=ValueError()): def set_calendar(self, calendar): """Set the calendar. - .. seealso:: `del_calendar`, `get_calendar` + .. seealso:: `override_calendar`, `override_units`, + `del_calendar`, `get_calendar` :Parameters: @@ -7786,7 +7787,7 @@ def set_calendar(self, calendar): `None` - **Examples:** + **Examples** >>> d.set_calendar('none') >>> d.get_calendar @@ -7803,7 +7804,8 @@ def set_calendar(self, calendar): def set_units(self, value): """Set the units. - .. seealso:: `del_units`, `get_units`, `has_units`, `Units` + .. seealso:: `override_units`, `del_units`, `get_units`, + `has_units`, `Units` :Parameters: @@ -7814,7 +7816,7 @@ def set_units(self, value): `None` - **Examples:** + **Examples** >>> d.set_units('watt') >>> d.get_units() diff --git a/cf/test/test_Data.py b/cf/test/test_Data.py index a18e7ef734..7e991b8f68 100644 --- a/cf/test/test_Data.py +++ b/cf/test/test_Data.py @@ -3890,6 +3890,43 @@ def test_Data_rechunk(self): self.assertEqual(e.chunks, ((4,), (5,))) self.assertTrue(e.equals(d)) + def test_Data_get_units(self): + for units in ("", "m", "days since 2000-01-01"): + d = cf.Data(1, units) + self.assertEqual(d.get_units(), units) + + d = cf.Data(1) + with self.assertRaises(ValueError): + d.get_units() + + def test_Data_set_calendar(self): + d = cf.Data(1, "days since 2000-01-01") + d.set_calendar("standard") + + with self.assertRaises(ValueError): + d.set_calendar("noleap") + + d = cf.Data(1, "m") + d.set_calendar("noleap") + self.assertEqual(d.Units, cf.Units("m")) + + def test_Data_set_units(self): + for units in (None, "", "m", "days since 2000-01-01"): + d = cf.Data(1, units) + self.assertEqual(d.Units, cf.Units(units)) + + d = cf.Data(1, "m") + d.set_units("km") + self.assertEqual(d.array, 0.001) + + d = cf.Data(1, "days since 2000-01-01", calendar="noleap") + d.set_units("days since 1999-12-31") + self.assertEqual(d.array, 2) + + # Can't set to Units that are not equivalent + with self.assertRaises(ValueError): + d.set_units("km") + if __name__ == "__main__": print("Run date:", datetime.datetime.now())