Skip to content

Commit ff4ef98

Browse files
authored
Fix/fix 3hr 6hr issue (#498)
* Fix 6hr, 3hr issue * feat: align sub-daily timestamps to daily grid for 3hr and 6hr data
1 parent a68ef2c commit ff4ef98

1 file changed

Lines changed: 91 additions & 0 deletions

File tree

src/access_moppy/base.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,6 +830,93 @@ def _numeric_delta_to_days(delta: float, time_units: Optional[str]) -> float:
830830
return delta / 86400.0
831831
return delta
832832

833+
@staticmethod
834+
def _days_to_numeric_units(days: float, time_units: Optional[str]) -> float:
835+
"""Convert day-length values back to the numeric coordinate units."""
836+
if not time_units:
837+
return days
838+
839+
interval = str(time_units).split("since", 1)[0].strip().lower()
840+
if interval in {"day", "days", "d"}:
841+
return days
842+
if interval in {"hour", "hours", "hr", "hrs", "h"}:
843+
return days * 24.0
844+
if interval in {"minute", "minutes", "min", "mins", "m"}:
845+
return days * 1440.0
846+
if interval in {"second", "seconds", "sec", "secs", "s"}:
847+
return days * 86400.0
848+
return days
849+
850+
def _align_subdaily_point_time_to_square_grid(self) -> None:
851+
"""Align point-sampled sub-daily timestamps to day-boundary slots.
852+
853+
Some ACCESS streams begin point-sampled sub-daily series at +3h/+6h.
854+
WCRP TIME001 expects these frequencies to be square on the canonical
855+
daily grid. Shift the whole time axis (and time bounds if present) by
856+
that leading offset.
857+
"""
858+
if "time" not in self.ds.coords or self.cmor_name not in self.ds:
859+
return
860+
861+
table_id = (self.compound_name or "").split(".", 1)[0]
862+
table_hours = {
863+
"3hr": 3.0,
864+
"3hrPt": 3.0,
865+
"6hrPlevPt": 6.0,
866+
}
867+
target_hours = table_hours.get(table_id)
868+
if target_hours is None:
869+
return
870+
871+
cell_methods = str(self.ds[self.cmor_name].attrs.get("cell_methods", ""))
872+
if "time: point" not in cell_methods:
873+
return
874+
875+
time_da = self.ds["time"]
876+
values = np.asarray(time_da.values)
877+
if values.size == 0 or not np.issubdtype(values.dtype, np.number):
878+
return
879+
880+
time_units = time_da.attrs.get("units")
881+
if not isinstance(time_units, str) or "since" not in time_units:
882+
return
883+
884+
first_value = float(values.flat[0])
885+
first_days = self._numeric_delta_to_days(first_value, time_units)
886+
leading_offset_days = first_days - np.floor(first_days)
887+
if np.isclose(leading_offset_days, 0.0, atol=1e-10):
888+
return
889+
890+
target_days = target_hours / 24.0
891+
if not np.isclose(leading_offset_days, target_days, atol=1e-8):
892+
return
893+
894+
shift_units = self._days_to_numeric_units(leading_offset_days, time_units)
895+
self.ds["time"] = xr.DataArray(
896+
values - shift_units,
897+
dims=time_da.dims,
898+
coords=time_da.coords,
899+
attrs=time_da.attrs,
900+
)
901+
902+
bounds_name = time_da.attrs.get("bounds")
903+
if isinstance(bounds_name, str) and bounds_name in self.ds:
904+
bnds = self.ds[bounds_name]
905+
if np.issubdtype(np.asarray(bnds.values).dtype, np.number):
906+
self.ds[bounds_name] = xr.DataArray(
907+
bnds.values - shift_units,
908+
dims=bnds.dims,
909+
coords=bnds.coords,
910+
attrs=bnds.attrs,
911+
)
912+
913+
logger.debug(
914+
"Shifted '%s' time axis by %.6f %s to align sub-daily point timestamps",
915+
self.cmor_name,
916+
shift_units,
917+
time_units.split("since", 1)[0].strip(),
918+
)
919+
833920
def rechunk_dataset(self):
834921
"""
835922
Apply intelligent rechunking to the dataset.
@@ -1186,6 +1273,10 @@ def write(self):
11861273
self.ds[var].attrs["units"] = normalized
11871274
logger.debug("Normalized '%s' units: %r -> %r", var, units, normalized)
11881275

1276+
# Align point-sampled sub-daily timestamps (e.g. 3hrPt/6hrPlevPt)
1277+
# to WCRP TIME001 square-grid expectations before writing.
1278+
self._align_subdaily_point_time_to_square_grid()
1279+
11891280
# ========== Prepare String Coordinates ==========
11901281
# Detect and prepare all string/character coordinates before writing
11911282
string_coords_info = self._prepare_string_coordinates()

0 commit comments

Comments
 (0)