Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Enhancement]: Xarray: Deprecated since version 2025.01.1: Please pass a coders.CFDatetimeCoder instance initialized with use_cftime to the decode_times kwarg instead. #719

Closed
tomvothecoder opened this issue Jan 14, 2025 · 2 comments
Assignees
Labels
minor A minor release, includes backwards compatible changes. priority: now Requires immediate attention

Comments

@tomvothecoder
Copy link
Collaborator

tomvothecoder commented Jan 14, 2025

Is your feature request related to a problem?

xarray=2025.01.1 deprecates the use_cftime kwarg, which affects xcdat.open_dataset() and xcdat.open_mfdataset().

Related to: https://github.com/pydata/xarray/releases/tag/v2025.01.1

It also includes an update to the time decoding infrastructure as a step toward pydata/xarray#9618.

Describe the solution you'd like

  1. Add code that checks the Xarray version to determine what to use
	if xarray.__version__ < 2025.01.1: 
		use_cftime = True
    else:
    	decode_times = coders.CFDatimeCoder(use_cftime=True) 

Describe alternatives you've considered

No response

Additional context

@tomvothecoder tomvothecoder added minor A minor release, includes backwards compatible changes. priority: now Requires immediate attention labels Jan 14, 2025
@tomvothecoder tomvothecoder self-assigned this Jan 16, 2025
@tomvothecoder
Copy link
Collaborator Author

tomvothecoder commented Jan 22, 2025

FYI @xCDAT/core-developers the xarray.coders.CFDatetimeCoder has the ability decode and encode the time coordinates variables. I think we've ran into situations where we need the raw float time coordinates, but an encoding feature was not available before.

@tomvothecoder
Copy link
Collaborator Author

Actually, I don't think this deprecation affects xCDAT because we use our own custom functions for decoding time coordinates as cftime objects. No deprecation warnings are raised in the test suite.

  • Custom function called decode_time()
    • xcdat/xcdat/dataset.py

      Lines 248 to 386 in c93b5bf

      def decode_time(dataset: xr.Dataset) -> xr.Dataset:
      """Decodes CF and non-CF time coordinates and time bounds using ``cftime``.
      By default, ``xarray`` only supports decoding time with CF compliant units
      [5]_. This function enables also decoding time with non-CF compliant units.
      It skips decoding time coordinates that have already been decoded as
      ``"datetime64[ns]"`` or ``cftime.datetime``.
      For time coordinates to be decodable, they must have a "calendar" attribute
      set to a CF calendar type supported by ``cftime``. CF calendar types
      include "noleap", "360_day", "365_day", "366_day", "gregorian",
      "proleptic_gregorian", "julian", "all_leap", or "standard". They must also
      have a "units" attribute set to a format supported by xCDAT ("months since
      ..." or "years since ...").
      Parameters
      ----------
      dataset : xr.Dataset
      Dataset with numerically encoded time coordinates and time bounds (if
      they exist). If the time coordinates cannot be decoded then the original
      dataset is returned.
      Returns
      -------
      xr.Dataset
      Dataset with decoded time coordinates and time bounds (if they exist) as
      ``cftime`` objects.
      Raises
      ------
      KeyError
      If time coordinates were not detected in the dataset, either because they
      don't exist at all or their CF attributes (e.g., 'axis' or
      'standard_name') are not set.
      Notes
      -----
      Time coordinates are represented by ``cftime.datetime`` objects because
      it is not restricted by the ``pandas.Timestamp`` range (years 1678 through
      2262). Refer to [6]_ and [7]_ for more information on this limitation.
      References
      -----
      .. [5] https://cfconventions.org/cf-conventions/cf-conventions.html#time-coordinate
      .. [6] https://docs.xarray.dev/en/stable/user-guide/weather-climate.html#non-standard-calendars-and-dates-outside-the-timestamp-valid-range
      .. [7] https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#timestamp-limitations
      Examples
      --------
      Decode the time coordinates in a Dataset:
      >>> from xcdat.dataset import decode_time
      >>>
      >>> ds.time
      <xarray.DataArray 'time' (time: 3)>
      array([0, 1, 2])
      Coordinates:
      * time (time) int64 0 1 2
      Attributes:
      units: years since 2000-01-01
      bounds: time_bnds
      axis: T
      long_name: time
      standard_name: time
      calendar: noleap
      >>>
      >>> ds_decoded = decode_time(ds)
      >>> ds_decoded.time
      <xarray.DataArray 'time' (time: 3)>
      array([cftime.DatetimeNoLeap(1850, 1, 1, 0, 0, 0, 0, has_year_zero=True),
      cftime.DatetimeNoLeap(1850, 1, 1, 0, 0, 0, 0, has_year_zero=True),
      cftime.DatetimeNoLeap(1850, 1, 1, 0, 0, 0, 0, has_year_zero=True)],
      dtype='object')
      Coordinates:
      * time (time) datetime64[ns] 2000-01-01 2001-01-01 2002-01-01
      Attributes:
      units: years since 2000-01-01
      bounds: time_bnds
      axis: T
      long_name: time
      standard_name: time
      calendar: noleap
      View time encoding information:
      >>> ds_decoded.time.encoding
      {'source': None,
      'dtype': dtype('int64'),
      'original_shape': (3,),
      'units': 'years since 2000-01-01',
      'calendar': 'noleap'}
      """
      ds = dataset.copy()
      coord_keys = _get_all_coord_keys(ds, "T")
      if len(coord_keys) == 0:
      raise KeyError(
      "No time coordinates were found in this dataset to decode. If time "
      "coordinates were expected to exist, make sure they are detectable by "
      "setting the CF 'axis' or 'standard_name' attribute (e.g., "
      "ds['time'].attrs['axis'] = 'T' or "
      "ds['time'].attrs['standard_name'] = 'time'). Afterwards, try decoding "
      "again with `xcdat.decode_time`."
      )
      for key in coord_keys:
      coords = ds[key].copy()
      if _is_decodable(coords) and not _is_decoded(coords):
      if coords.attrs.get("calendar") is None:
      coords.attrs["calendar"] = "standard"
      logger.warning(
      f"'{coords.name}' does not have a calendar attribute set. "
      "Defaulting to CF 'standard' calendar."
      )
      decoded_time = _decode_time(coords)
      ds = ds.assign_coords({coords.name: decoded_time})
      try:
      bounds = ds.bounds.get_bounds("T", var_key=coords.name)
      except KeyError:
      bounds = None
      if bounds is not None and not _is_decoded(bounds):
      # Bounds don't typically store the "units" and "calendar"
      # attributes required for decoding, so these attributes need to be
      # copied from the coordinates.
      bounds.attrs.update(
      {
      "units": coords.attrs["units"],
      "calendar": coords.attrs["calendar"],
      }
      )
      decoded_bounds = _decode_time(bounds)
      ds = ds.assign({bounds.name: decoded_bounds})
      return ds
  • use_cftime=True is passed to xarray.coding.decode_cf_datetime but it is not deprecated here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
minor A minor release, includes backwards compatible changes. priority: now Requires immediate attention
Projects
Status: Done
Development

No branches or pull requests

1 participant