diff --git a/ci/azure/install.yml b/ci/azure/install.yml index 8d72ffce124..2911e227172 100644 --- a/ci/azure/install.yml +++ b/ci/azure/install.yml @@ -15,16 +15,18 @@ steps: --no-deps \ --pre \ --upgrade \ - numpy \ matplotlib \ pandas \ scipy + # numpy \ # FIXME https://github.com/pydata/xarray/issues/3409 pip install \ --no-deps \ --upgrade \ git+https://github.com/dask/dask \ git+https://github.com/dask/distributed \ - git+https://github.com/zarr-developers/zarr + git+https://github.com/zarr-developers/zarr \ + git+https://github.com/Unidata/cftime.git@refs/pull/127/merge + # git+https://github.com/Unidata/cftime # FIXME PR 127 not merged yet condition: eq(variables['UPSTREAM_DEV'], 'true') displayName: Install upstream dev dependencies diff --git a/ci/min_deps_check.py b/ci/min_deps_check.py index 3bdd48ca76d..a5ba90679b7 100755 --- a/ci/min_deps_check.py +++ b/ci/min_deps_check.py @@ -6,7 +6,7 @@ import sys from concurrent.futures import ThreadPoolExecutor from datetime import datetime, timedelta -from typing import Dict, Iterator, Tuple +from typing import Dict, Iterator, Optional, Tuple import yaml @@ -34,10 +34,14 @@ def error(msg: str) -> None: print("ERROR:", msg) -def parse_requirements(fname) -> Iterator[Tuple[str, int, int]]: +def warning(msg: str) -> None: + print("WARNING:", msg) + + +def parse_requirements(fname) -> Iterator[Tuple[str, int, int, Optional[int]]]: """Load requirements/py36-min-all-deps.yml - Yield (package name, major version, minor version) + Yield (package name, major version, minor version, [patch version]) """ global has_errors @@ -52,15 +56,18 @@ def parse_requirements(fname) -> Iterator[Tuple[str, int, int]]: if pkg.endswith("<") or pkg.endswith(">") or eq != "=": error("package should be pinned with exact version: " + row) continue + try: - major, minor = version.split(".") - except ValueError: - error("expected major.minor (without patch): " + row) - continue - try: - yield pkg, int(major), int(minor) + version_tup = tuple(int(x) for x in version.split(".")) except ValueError: - error("failed to parse version: " + row) + raise ValueError("non-numerical version: " + row) + + if len(version_tup) == 2: + yield (pkg, *version_tup, None) # type: ignore + elif len(version_tup) == 3: + yield (pkg, *version_tup) # type: ignore + else: + raise ValueError("expected major.minor or major.minor.patch: " + row) def query_conda(pkg: str) -> Dict[Tuple[int, int], datetime]: @@ -80,9 +87,9 @@ def query_conda(pkg: str) -> Dict[Tuple[int, int], datetime]: label = label.strip() if label == "file name": value = value.strip()[len(pkg) :] - major, minor = value.split("-")[1].split(".")[:2] - major = int(major) - minor = int(minor) + smajor, sminor = value.split("-")[1].split(".")[:2] + major = int(smajor) + minor = int(sminor) if label == "timestamp": assert major is not None assert minor is not None @@ -109,17 +116,15 @@ def query_conda(pkg: str) -> Dict[Tuple[int, int], datetime]: def process_pkg( - pkg: str, req_major: int, req_minor: int -) -> Tuple[str, int, int, str, int, int, str, str]: + pkg: str, req_major: int, req_minor: int, req_patch: Optional[int] +) -> Tuple[str, str, str, str, str, str]: """Compare package version from requirements file to available versions in conda. Return row to build pandas dataframe: - package name - - major version in requirements file - - minor version in requirements file + - major.minor.[patch] version in requirements file - publication date of version in requirements file (YYYY-MM-DD) - - major version suggested by policy - - minor version suggested by policy + - major.minor version suggested by policy - publication date of version suggested by policy (YYYY-MM-DD) - status ("<", "=", "> (!)") """ @@ -130,7 +135,7 @@ def process_pkg( req_published = versions[req_major, req_minor] except KeyError: error("not found in conda: " + pkg) - return pkg, req_major, req_minor, "-", 0, 0, "-", "(!)" + return pkg, fmt_version(req_major, req_minor, req_patch), "-", "-", "-", "(!)" policy_months = POLICY_MONTHS.get(pkg, POLICY_MONTHS_DEFAULT) policy_published = datetime.now() - timedelta(days=policy_months * 30) @@ -153,30 +158,39 @@ def process_pkg( else: status = "=" + if req_patch is not None: + warning("patch version should not appear in requirements file: " + pkg) + status += " (w)" + return ( pkg, - req_major, - req_minor, + fmt_version(req_major, req_minor, req_patch), req_published.strftime("%Y-%m-%d"), - policy_major, - policy_minor, + fmt_version(policy_major, policy_minor), policy_published_actual.strftime("%Y-%m-%d"), status, ) +def fmt_version(major: int, minor: int, patch: int = None) -> str: + if patch is None: + return f"{major}.{minor}" + else: + return f"{major}.{minor}.{patch}" + + def main() -> None: fname = sys.argv[1] with ThreadPoolExecutor(8) as ex: futures = [ - ex.submit(process_pkg, pkg, major, minor) - for pkg, major, minor in parse_requirements(fname) + ex.submit(process_pkg, pkg, major, minor, patch) + for pkg, major, minor, patch in parse_requirements(fname) ] rows = [f.result() for f in futures] - print("Package Required Policy Status") - print("------------- ----------------- ----------------- ------") - fmt = "{:13} {:>1d}.{:<2d} ({:10}) {:>1d}.{:<2d} ({:10}) {}" + print("Package Required Policy Status") + print("------------- -------------------- -------------------- ------") + fmt = "{:13} {:7} ({:10}) {:7} ({:10}) {}" for row in rows: print(fmt.format(*row)) diff --git a/ci/requirements/py36-min-all-deps.yml b/ci/requirements/py36-min-all-deps.yml index 3f10a158f91..c99ae39e5d9 100644 --- a/ci/requirements/py36-min-all-deps.yml +++ b/ci/requirements/py36-min-all-deps.yml @@ -13,7 +13,7 @@ dependencies: - cartopy=0.17 - cdms2=3.1 - cfgrib=0.9 - - cftime=1.0 + - cftime=1.0.3 # FIXME need 1.0.5 (not released yet); 1.0.4 is broken - coveralls - dask=1.2 - distributed=1.27 diff --git a/ci/requirements/py36.yml b/ci/requirements/py36.yml index 4d6d778f884..6e27cea2ffe 100644 --- a/ci/requirements/py36.yml +++ b/ci/requirements/py36.yml @@ -9,7 +9,7 @@ dependencies: - cartopy - cdms2 - cfgrib - - cftime + - cftime<1.0.4 # FIXME need 1.0.5 (not released yet); 1.0.4 is broken - coveralls - dask - distributed @@ -25,7 +25,7 @@ dependencies: - nc-time-axis - netcdf4 - numba - - numpy + - numpy<1.18 # FIXME https://github.com/pydata/xarray/issues/3409 - pandas - pint - pip diff --git a/ci/requirements/py37-windows.yml b/ci/requirements/py37-windows.yml index 78784b48f3c..7027fc11ab7 100644 --- a/ci/requirements/py37-windows.yml +++ b/ci/requirements/py37-windows.yml @@ -8,8 +8,8 @@ dependencies: - bottleneck - cartopy # - cdms2 # Not available on Windows - # - cfgrib>=0.9.2 # Causes Python interpreter crash on Windows - - cftime + # - cfgrib # Causes Python interpreter crash on Windows + - cftime<1.0.4 # FIXME need 1.0.5 (not released yet); 1.0.4 is broken - coveralls - dask - distributed @@ -25,7 +25,7 @@ dependencies: - nc-time-axis - netcdf4 - numba - - numpy + - numpy<1.18 # FIXME https://github.com/pydata/xarray/issues/3409 - pandas - pint - pip diff --git a/ci/requirements/py37.yml b/ci/requirements/py37.yml index 38e5db641b7..a4c974c0176 100644 --- a/ci/requirements/py37.yml +++ b/ci/requirements/py37.yml @@ -9,7 +9,7 @@ dependencies: - cartopy - cdms2 - cfgrib - - cftime + - cftime<1.0.4 # FIXME need 1.0.5 (not released yet); 1.0.4 is broken - coveralls - dask - distributed @@ -25,7 +25,7 @@ dependencies: - nc-time-axis - netcdf4 - numba - - numpy + - numpy<1.18 # FIXME https://github.com/pydata/xarray/issues/3409 - pandas - pint - pip diff --git a/doc/whats-new.rst b/doc/whats-new.rst index aaac52360a0..86d9d3b3ca3 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -18,6 +18,11 @@ What's New v0.14.1 (unreleased) -------------------- +Breaking changes +~~~~~~~~~~~~~~~~ + +- Minimum cftime version is now 1.0.3. By `Deepak Cherian `_. + New Features ~~~~~~~~~~~~ - Added integration tests against `pint `_. @@ -38,6 +43,10 @@ Bug fixes - Fix grouping over variables with NaNs. (:issue:`2383`, :pull:`3406`). By `Deepak Cherian `_. +- Sync with cftime by removing `dayofwk=-1` for cftime>=1.0.4. + By `Anderson Banihirwe `_. + + Documentation ~~~~~~~~~~~~~ diff --git a/xarray/backends/rasterio_.py b/xarray/backends/rasterio_.py index 7430633c5c0..1f9b9943573 100644 --- a/xarray/backends/rasterio_.py +++ b/xarray/backends/rasterio_.py @@ -257,8 +257,8 @@ def open_rasterio(filename, parse_coordinates=None, chunks=None, cache=None, loc if parse: nx, ny = riods.width, riods.height # xarray coordinates are pixel centered - x, _ = (np.arange(nx) + 0.5, np.zeros(nx) + 0.5) * riods.transform - _, y = (np.zeros(ny) + 0.5, np.arange(ny) + 0.5) * riods.transform + x, _ = riods.transform * (np.arange(nx) + 0.5, np.zeros(nx) + 0.5) + _, y = riods.transform * (np.zeros(ny) + 0.5, np.arange(ny) + 0.5) coords["y"] = y coords["x"] = x else: diff --git a/xarray/coding/cftime_offsets.py b/xarray/coding/cftime_offsets.py index af46510f7c4..8471ed1a558 100644 --- a/xarray/coding/cftime_offsets.py +++ b/xarray/coding/cftime_offsets.py @@ -50,6 +50,7 @@ from ..core.pdcompat import count_not_none from .cftimeindex import CFTimeIndex, _parse_iso8601_with_reso from .times import format_cftime_datetime +from distutils.version import LooseVersion def get_date_type(calendar): @@ -222,6 +223,8 @@ def _adjust_n_years(other, n, month, reference_day): def _shift_month(date, months, day_option="start"): """Shift the date to a month start or end a given number of months away. """ + import cftime + delta_year = (date.month + months) // 12 month = (date.month + months) % 12 @@ -237,11 +240,14 @@ def _shift_month(date, months, day_option="start"): day = _days_in_month(reference) else: raise ValueError(day_option) - # dayofwk=-1 is required to update the dayofwk and dayofyr attributes of - # the returned date object in versions of cftime between 1.0.2 and - # 1.0.3.4. It can be removed for versions of cftime greater than - # 1.0.3.4. - return date.replace(year=year, month=month, day=day, dayofwk=-1) + if LooseVersion(cftime.__version__) < LooseVersion("1.0.4"): + # dayofwk=-1 is required to update the dayofwk and dayofyr attributes of + # the returned date object in versions of cftime between 1.0.2 and + # 1.0.3.4. It can be removed for versions of cftime greater than + # 1.0.3.4. + return date.replace(year=year, month=month, day=day, dayofwk=-1) + else: + return date.replace(year=year, month=month, day=day) def roll_qtrday(other, n, month, day_option, modby=3): diff --git a/xarray/coding/cftimeindex.py b/xarray/coding/cftimeindex.py index 434d55d6569..559c5e16287 100644 --- a/xarray/coding/cftimeindex.py +++ b/xarray/coding/cftimeindex.py @@ -96,6 +96,8 @@ def parse_iso8601(datetime_string): def _parse_iso8601_with_reso(date_type, timestr): + import cftime + default = date_type(1, 1, 1) result = parse_iso8601(timestr) replace = {} @@ -107,12 +109,12 @@ def _parse_iso8601_with_reso(date_type, timestr): # TODO: Consider adding support for sub-second resolution? replace[attr] = int(value) resolution = attr - - # dayofwk=-1 is required to update the dayofwk and dayofyr attributes of - # the returned date object in versions of cftime between 1.0.2 and - # 1.0.3.4. It can be removed for versions of cftime greater than - # 1.0.3.4. - replace["dayofwk"] = -1 + if LooseVersion(cftime.__version__) < LooseVersion("1.0.4"): + # dayofwk=-1 is required to update the dayofwk and dayofyr attributes of + # the returned date object in versions of cftime between 1.0.2 and + # 1.0.3.4. It can be removed for versions of cftime greater than + # 1.0.3.4. + replace["dayofwk"] = -1 return default.replace(**replace), resolution diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py index dce417f27f9..006d6881b5a 100644 --- a/xarray/tests/test_dataset.py +++ b/xarray/tests/test_dataset.py @@ -3593,6 +3593,19 @@ def test_resample_old_api(self): with raises_regex(TypeError, r"resample\(\) no longer supports"): ds.resample("1D", dim="time") + def test_resample_ds_da_are_the_same(self): + time = pd.date_range("2000-01-01", freq="6H", periods=365 * 4) + ds = xr.Dataset( + { + "foo": (("time", "x"), np.random.randn(365 * 4, 5)), + "time": time, + "x": np.arange(5), + } + ) + assert_identical( + ds.resample(time="M").mean()["foo"], ds.foo.resample(time="M").mean() + ) + def test_ds_resample_apply_func_args(self): def func(arg1, arg2, arg3=0.0): return arg1.mean("time") + arg2 + arg3