-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Units and calendar attributes of time_bnds are dropped by to_netcdf #11275
Description
What happened?
If I define a time_bnds variable and denote it with the bounds attribute to the time variable, sometimes to_netcdf will drop the units and calendar attributes of the time_bnds variable. This seems like a bug to me.
What did you expect to happen?
I expected written variables to have the attributes I defined for them.
Minimal Complete Verifiable Example
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "xarray[complete]@git+https://github.com/pydata/xarray.git@main",
# ]
# ///
#
# This script automatically imports the development branch of xarray to check for issues.
# Please delete this header if you have _not_ tested this script with `uv run`!
import xarray as xr
xr.show_versions()
# your reproducer code ...
import numpy as np
from datetime import datetime
TIME0 = datetime(1980, 1, 1)
TUNITS = f'days since {TIME0.strftime("%Y-%m-%d")}'
TCAL = 'proleptic_gregorian'
year = 2001
tval = (datetime(year, 1, 1) - TIME0).days
yrdays = (datetime(year+1, 1, 1) - datetime(year, 1, 1)).days
tbvals = np.reshape([tval, tval + yrdays], (1, 2))
time_bnds = xr.DataArray(
data=tbvals.astype(np.double), dims=['time','nv'],
attrs={'long_name':'time bounds', 'units':TUNITS, 'calendar':TCAL}
)
nlat = 180
nlon = 360
late = np.linspace( -90, 90, nlat+1)
lone = np.linspace(-180, 180, nlon+1)
lat = 0.5*(late[1:] + late[:-1])
lon = 0.5*(lone[1:] + lone[:-1])
coords = {
'time':(['time'], np.array([tval]).astype(np.double), {
'long_name':'time',
'units':TUNITS,
'calendar':TCAL,
'bounds':'time_bnds',
}),
'lat':(['lat'], lat.astype(np.single), {
'long_name':'latitude',
'units':'degrees_north',
}),
'lon':(['lon'], lon.astype(np.single), {
'long_name':'longitude',
'units':'degrees_east',
}),
}
field = xr.DataArray(
data=np.zeros((1, nlat, nlon)).astype(np.single),
dims=['time','lat','lon'], coords=coords,
)
ds1 = xr.Dataset(data_vars={'time_bnds':time_bnds})
ds1.to_netcdf('test1.nc4')
ds1.close()
ds2 = xr.Dataset(data_vars={'time_bnds':time_bnds, 'field':field})
ds2.to_netcdf('test2.nc4')
ds2.close()
ds3 = xr.Dataset(data_vars={'something':time_bnds, 'field':field})
ds3.to_netcdf('test3.nc4')
ds3.close()Steps to reproduce
No response
MVCE confirmation
- Minimal example — the example is as focused as reasonably possible to demonstrate the underlying issue in xarray.
- Complete example — the example is self-contained, including all data and the text of any traceback.
- Verifiable example — the example copy & pastes into an IPython prompt or Binder notebook, returning the result.
- New issue — a search of GitHub Issues suggests this is not a duplicate.
- Recent environment — the issue occurs with the latest version of xarray and its dependencies.
Relevant log output
(newpy) bweir@discover11:/discover/nobackup/bweir> ncdump -h test1.nc4
netcdf test1 {
dimensions:
time = 1 ;
nv = 2 ;
variables:
double time_bnds(time, nv) ;
time_bnds:_FillValue = NaN ;
time_bnds:long_name = "time bounds" ;
time_bnds:units = "days since 1980-01-01" ;
time_bnds:calendar = "proleptic_gregorian" ;
}
(newpy) bweir@discover11:/discover/nobackup/bweir> ncdump -h test2.nc4
netcdf test2 {
dimensions:
time = 1 ;
nv = 2 ;
lat = 180 ;
lon = 360 ;
variables:
double time(time) ;
time:_FillValue = NaN ;
time:long_name = "time" ;
time:units = "days since 1980-01-01" ;
time:calendar = "proleptic_gregorian" ;
time:bounds = "time_bnds" ;
double time_bnds(time, nv) ;
time_bnds:_FillValue = NaN ;
time_bnds:long_name = "time bounds" ;
float lat(lat) ;
lat:_FillValue = NaNf ;
lat:long_name = "latitude" ;
lat:units = "degrees_north" ;
float lon(lon) ;
lon:_FillValue = NaNf ;
lon:long_name = "longitude" ;
lon:units = "degrees_east" ;
float field(time, lat, lon) ;
field:_FillValue = NaNf ;
}
(newpy) bweir@discover11:/discover/nobackup/bweir> ncdump -h test3.nc4
netcdf test3 {
dimensions:
time = 1 ;
nv = 2 ;
lat = 180 ;
lon = 360 ;
variables:
double time(time) ;
time:_FillValue = NaN ;
time:long_name = "time" ;
time:units = "days since 1980-01-01" ;
time:calendar = "proleptic_gregorian" ;
double something(time, nv) ;
something:_FillValue = NaN ;
something:long_name = "time bounds" ;
something:units = "days since 1980-01-01" ;
something:calendar = "proleptic_gregorian" ;
float lat(lat) ;
lat:_FillValue = NaNf ;
lat:long_name = "latitude" ;
lat:units = "degrees_north" ;
float lon(lon) ;
lon:_FillValue = NaNf ;
lon:long_name = "longitude" ;
lon:units = "degrees_east" ;
float field(time, lat, lon) ;
field:_FillValue = NaNf ;
}
Anything else we need to know?
You'll see that test1.nc4 and test3.nc4 have the correct attributes for time_bnds. If you comment out the bounds attribute for the time variable, then time_bnds will have the correct attributes. Clearly there is something going on in the code that's tracing through time to time:bounds to time_bnds:units and time_bnds:calendar and dropping them. Why you would write code to do this horrifies me.
Environment
Details
INSTALLED VERSIONS
commit: None
python: 3.14.3 | packaged by conda-forge | (main, Feb 9 2026, 21:56:02) [GCC 14.3.0]
python-bits: 64
OS: Linux
OS-release: 5.14.21-150400.24.100-default
machine: x86_64
processor: x86_64
byteorder: little
LC_ALL: None
LANG: en_US.UTF-8
LOCALE: ('en_US', 'UTF-8')
libhdf5: 2.1.0
libnetcdf: 4.10.0
xarray: 2026.2.0
pandas: 3.0.2
numpy: 2.4.3
scipy: 1.17.1
netCDF4: 1.7.4
pydap: None
h5netcdf: 1.8.1
h5py: 3.16.0
zarr: None
cftime: 1.6.5
nc_time_axis: None
iris: None
bottleneck: None
dask: None
distributed: None
matplotlib: None
cartopy: None
seaborn: None
numbagg: None
fsspec: None
cupy: None
pint: None
sparse: None
flox: None
numpy_groupies: None
setuptools: None
pip: 26.0.1
conda: None
pytest: None
mypy: None
IPython: None
sphinx: None