From 48ce001c277c0f5528f0fdd4db5d89c399971f28 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Thu, 21 Jul 2022 17:10:11 +0100 Subject: [PATCH 1/5] Remove in-house long time interval checking. --- cf_units/__init__.py | 39 --------------------- cf_units/tests/integration/test_date2num.py | 9 ----- cf_units/tests/unit/unit/test_Unit.py | 30 ---------------- 3 files changed, 78 deletions(-) diff --git a/cf_units/__init__.py b/cf_units/__init__.py index a62e93a5..10850173 100644 --- a/cf_units/__init__.py +++ b/cf_units/__init__.py @@ -964,36 +964,6 @@ def is_time_reference(self): """ return self.calendar is not None - def is_long_time_interval(self): - """ - Defines whether this unit describes a time unit with a long time - interval ("months" or "years"). These long time intervals *are* - supported by `UDUNITS2` but are not supported by `cftime`. This - discrepancy means we cannot run self.num2date() on a time unit with - a long time interval. - - Returns: - Boolean. - - For example: - - >>> import cf_units - >>> u = cf_units.Unit('days since epoch') - >>> u.is_long_time_interval() - False - >>> u = cf_units.Unit('years since epoch') - >>> u.is_long_time_interval() - True - - """ - result = False - long_time_intervals = ["year", "month"] - if self.is_time_reference(): - result = any( - interval in self.origin for interval in long_time_intervals - ) - return result - def title(self, value): """ Return the unit value as a title string. @@ -1929,15 +1899,6 @@ def cftime_unit(self): if self.calendar is None: raise ValueError("Unit has undefined calendar") - # `cftime` cannot parse long time intervals ("months" or "years"). - if self.is_long_time_interval(): - interval = self.origin.split(" ")[0] - emsg = ( - 'Time units with interval of "months", "years" ' - '(or singular of these) cannot be processed, got "{!s}".' - ) - raise ValueError(emsg.format(interval)) - # # ensure to strip out non-parsable 'UTC' postfix, which # is generated by UDUNITS-2 formatted output diff --git a/cf_units/tests/integration/test_date2num.py b/cf_units/tests/integration/test_date2num.py index 68e7c967..2a20998c 100644 --- a/cf_units/tests/integration/test_date2num.py +++ b/cf_units/tests/integration/test_date2num.py @@ -8,7 +8,6 @@ import datetime import numpy as np -import pytest from cf_units import date2num @@ -60,11 +59,3 @@ def test_discard_microsecond(self): res = date2num(date, self.unit, self.calendar) assert exp == res - - def test_long_time_interval(self): - # This test should fail with an error that we need to catch properly. - unit = "years since 1970-01-01" - date = datetime.datetime(1970, 1, 1, 0, 0, 5) - exp_emsg = 'interval of "months", "years" .* got "years".' - with pytest.raises(ValueError, match=exp_emsg): - date2num(date, unit, self.calendar) diff --git a/cf_units/tests/unit/unit/test_Unit.py b/cf_units/tests/unit/unit/test_Unit.py index b96e2abb..dc03700b 100644 --- a/cf_units/tests/unit/unit/test_Unit.py +++ b/cf_units/tests/unit/unit/test_Unit.py @@ -56,11 +56,6 @@ def test_no_change(self): # the same object. assert result is not u - def test_long_time_interval(self): - u = Unit("months since 1970-01-01", calendar="standard") - with pytest.raises(ValueError, match="cannot be processed"): - u.change_calendar("proleptic_gregorian") - def test_wrong_calendar(self): u = Unit("days since 1900-01-01", calendar="360_day") with pytest.raises( @@ -273,31 +268,6 @@ def test_type_conversion(self): np.testing.assert_array_almost_equal(self.rads_array, result) -class Test_is_long_time_interval: - def test_short_time_interval(self): - # A short time interval is a time interval from seconds to days. - unit = Unit("seconds since epoch") - result = unit.is_long_time_interval() - assert not result - - def test_long_time_interval(self): - # A long time interval is a time interval of months or years. - unit = Unit("months since epoch") - result = unit.is_long_time_interval() - assert result - - def test_calendar(self): - # Check that a different calendar does not affect the result. - unit = Unit("months since epoch", calendar=cf_units.CALENDAR_360_DAY) - result = unit.is_long_time_interval() - assert result - - def test_not_time_unit(self): - unit = Unit("K") - result = unit.is_long_time_interval() - assert not result - - class Test_format: def test_invalid_ut_unit(self): # https://github.com/SciTools/cf-units/issues/133 flagged up that From 947fbca630e6b421b443322bc68771e968f69733 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Wed, 14 Dec 2022 17:49:43 +0000 Subject: [PATCH 2/5] Restore is_long_time_interval() with a DeprecationWarning. --- cf_units/__init__.py | 43 +++++++++++++++++++++++++++ cf_units/tests/unit/unit/test_Unit.py | 32 ++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/cf_units/__init__.py b/cf_units/__init__.py index 10850173..dc570c1b 100644 --- a/cf_units/__init__.py +++ b/cf_units/__init__.py @@ -16,6 +16,7 @@ import copy from contextlib import contextmanager +from warnings import warn import cftime import numpy as np @@ -964,6 +965,48 @@ def is_time_reference(self): """ return self.calendar is not None + def is_long_time_interval(self): + """ + Defines whether this unit describes a time unit with a long time + interval ("months" or "years"). These long time intervals *are* + supported by `UDUNITS2` but are not supported by `cftime`. This + discrepancy means we cannot run self.num2date() on a time unit with + a long time interval. + + .. deprecated:: 3.2.0 + + Invalid long time intervals are now defended against within + cftime - do not use this routine, as cftime knows best what it can + and cannot support. + + Returns: + Boolean. + + For example: + + >>> import cf_units + >>> u = cf_units.Unit('days since epoch') + >>> u.is_long_time_interval() + False + >>> u = cf_units.Unit('years since epoch') + >>> u.is_long_time_interval() + True + + """ + deprecation = ( + "This method is no longer needed due to cftime's improved " + "handling of long time intervals." + ) + warn(deprecation, DeprecationWarning, stacklevel=2) + + result = False + long_time_intervals = ["year", "month"] + if self.is_time_reference(): + result = any( + interval in self.origin for interval in long_time_intervals + ) + return result + def title(self, value): """ Return the unit value as a title string. diff --git a/cf_units/tests/unit/unit/test_Unit.py b/cf_units/tests/unit/unit/test_Unit.py index dc03700b..dfc68d64 100644 --- a/cf_units/tests/unit/unit/test_Unit.py +++ b/cf_units/tests/unit/unit/test_Unit.py @@ -268,6 +268,38 @@ def test_type_conversion(self): np.testing.assert_array_almost_equal(self.rads_array, result) +class Test_is_long_time_interval: + def test_deprecated(self): + unit = Unit("seconds since epoch") + with pytest.warns( + DeprecationWarning, match="This method is no longer needed" + ): + _ = unit.is_long_time_interval() + + def test_short_time_interval(self): + # A short time interval is a time interval from seconds to days. + unit = Unit("seconds since epoch") + result = unit.is_long_time_interval() + assert not result + + def test_long_time_interval(self): + # A long time interval is a time interval of months or years. + unit = Unit("months since epoch") + result = unit.is_long_time_interval() + assert result + + def test_calendar(self): + # Check that a different calendar does not affect the result. + unit = Unit("months since epoch", calendar=cf_units.CALENDAR_360_DAY) + result = unit.is_long_time_interval() + assert result + + def test_not_time_unit(self): + unit = Unit("K") + result = unit.is_long_time_interval() + assert not result + + class Test_format: def test_invalid_ut_unit(self): # https://github.com/SciTools/cf-units/issues/133 flagged up that From 730805ae79422cb1fe743f7526b93d42ee64c6ac Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Wed, 14 Dec 2022 17:54:13 +0000 Subject: [PATCH 3/5] Make test_deprecated a staticmethod. --- cf_units/tests/unit/unit/test_Unit.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cf_units/tests/unit/unit/test_Unit.py b/cf_units/tests/unit/unit/test_Unit.py index dfc68d64..ce42f3a6 100644 --- a/cf_units/tests/unit/unit/test_Unit.py +++ b/cf_units/tests/unit/unit/test_Unit.py @@ -269,7 +269,8 @@ def test_type_conversion(self): class Test_is_long_time_interval: - def test_deprecated(self): + @staticmethod + def test_deprecated(): unit = Unit("seconds since epoch") with pytest.warns( DeprecationWarning, match="This method is no longer needed" From 77d7123b68ad67068216d94373daef7d93f04a02 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Wed, 2 Aug 2023 17:22:09 +0100 Subject: [PATCH 4/5] Fix to problem introduced in conflict resolution. --- cf_units/tests/integration/test_date2num.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cf_units/tests/integration/test_date2num.py b/cf_units/tests/integration/test_date2num.py index 43692618..020f1421 100644 --- a/cf_units/tests/integration/test_date2num.py +++ b/cf_units/tests/integration/test_date2num.py @@ -7,6 +7,7 @@ import datetime import numpy as np +import pytest from cf_units import date2num From f27e4332c5c81ca4e7f8d83bd98ee88d647e580f Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Thu, 12 Oct 2023 11:43:57 +0100 Subject: [PATCH 5/5] Advance deprecation version. Co-authored-by: Bill Little --- cf_units/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cf_units/__init__.py b/cf_units/__init__.py index 248ab1cb..33366729 100644 --- a/cf_units/__init__.py +++ b/cf_units/__init__.py @@ -874,7 +874,7 @@ def is_long_time_interval(self): discrepancy means we cannot run self.num2date() on a time unit with a long time interval. - .. deprecated:: 3.2.0 + .. deprecated:: 3.3.0 Invalid long time intervals are now defended against within cftime - do not use this routine, as cftime knows best what it can