From 20a89abdc397cb9fbe322c7c04cd661ecfe571c3 Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Tue, 31 Mar 2026 01:22:08 +0100 Subject: [PATCH 1/2] Make strings of date units cftime-compatible. --- src/cfpint/_core.py | 10 +++++ src/cfpint/tests/test_basic.py | 72 ++++++++++++++++++++++++++++++---- 2 files changed, 75 insertions(+), 7 deletions(-) diff --git a/src/cfpint/_core.py b/src/cfpint/_core.py index 2e594f3..9f98cc6 100644 --- a/src/cfpint/_core.py +++ b/src/cfpint/_core.py @@ -90,6 +90,16 @@ def __str__(self): # TODO: we really ought to have this, but see temporary test_pintunit # if self.calendar_string not in (None, "default"): # result += f", calendar='{self.calendar_string!s}'" + if self.dimensionality == {"[time]": 1}: + # Date or time units *only* : replace time unit symbols with full names. + # Required for cftime compatibility of date units: Do the same to time + # (period) units also, for consistency. + sym_names = {"d": "days", "h": "hours", "min": "minutes", "s": "seconds"} + for sym, name in sym_names.items(): + if result == sym: + result = name + elif result.startswith(sym + " "): + result = result.replace(sym + " ", name + " ", 1) return result def __repr__(self): diff --git a/src/cfpint/tests/test_basic.py b/src/cfpint/tests/test_basic.py index 426f5fc..17cb122 100644 --- a/src/cfpint/tests/test_basic.py +++ b/src/cfpint/tests/test_basic.py @@ -1,15 +1,10 @@ +import cftime import pint import pytest from cf_units import Unit as CFU_Unit -# from cfpint import install_defaults from cfpint import Unit -# TODO: want to know why this didn't work -# @pytest.fixture(scope="session") -# def install(): -# install_defaults() - class TestBasicCflike: """Basic behaviours copied from cf_xarray.unit.""" @@ -136,7 +131,7 @@ def test_str_repr(self, calendar, method): kwargs = {} if calendar is None else {"calendar": calendar} date_unit = Unit("day since 1970", **kwargs) # TODO: fix this (d//days), but for now it is done in Iris ??? - expect = "d since 1970" + expect = "days since 1970" if calendar == "365_day" and method != "str": expect += "', calendar='365_day" if method == "str": @@ -145,3 +140,66 @@ def test_str_repr(self, calendar, method): expect = f"" result = repr(date_unit) assert result == expect + + +# Period symbols and their name expansions: N.B. minutes are "min" not "m" +_TIME_SYMBOLS_NAMES = {"d": "days", "h": "hours", "min": "minutes", "s": "seconds"} + + +class TestTimeunitStrings: + """Check the special handling of times and dates by Unit.str(). + + Ensures that strings of date units use cftime-compatible period names, + and that time-period units look the same way (for consistency). + """ + + @pytest.fixture(params=list(_TIME_SYMBOLS_NAMES)) + def period_symbol(self, request): + return request.param + + def test_timeperiod_strs(self, period_symbol): + # Test that time-period names appear in time-period unit strings. + # Create a unit based on the pint symbol for d/h/m/s + unit_str = period_symbol + unit = Unit(unit_str) + + # in all cases should have dimensions [time], and *not* be a "date" + assert unit.dimensionality == {"[time]": 1} + assert not unit.is_datelike() + + # Test that str() replaces the symbol with the name. + result = str(unit) + expected = _TIME_SYMBOLS_NAMES[period_symbol] + assert result == expected + + def test_dateunit_conversion(self, period_symbol): + # Test that time-period names appear in date unit strings. + # Create a unit based on the pint symbol for d/h/m/s + unit_str = period_symbol + " since 2000-01-01" + + # construct the unit + unit = Unit(unit_str) + + # in all cases should have dimensions [time], and be a "date" + # TODO: one day, 'date' may be coded as a separate *dimension* ? + assert unit.dimensionality == {"[time]": 1} + assert unit.is_datelike() + + # Test the expected string repr + result = str(unit) + # Expected content has a standardised name in place of the symbol. + expected = _TIME_SYMBOLS_NAMES[period_symbol] + " since 2000-01-01" + assert result == expected + + def test_dateunit_cftime_compatible(self, period_symbol): + # Check that date unit strings are valid cftime units. + + # Create a unit based on the pint symbol for d/h/m/s + unit_in = period_symbol + " since 2000-01-01" + + # convert to a unit string + unit_out = str(Unit(unit_in)) + + # Check that the date unit is compatible with cftime + date = cftime.num2date(0.0, str(unit_out)) + assert date == cftime.datetime(2000, 1, 1) From cee1cbda770355e4fb40bd0beba4e222eaa10f6c Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Tue, 31 Mar 2026 01:22:51 +0100 Subject: [PATCH 2/2] Temporarily avoid line-length clash between flake8 and black. --- .flake8 | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..2bcd70e --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 88