[docs]def_combine_dt_ndarray(date_arr:npt.NDArray[np.str_],time_arr:npt.NDArray[np.str_]|None=None,time_pad=False,)->np.ndarray:# TODO: When min pyver is 3.10, maybe consider pattern matching heredef_parse_date(date_val:str)->np.datetime64:ifdate_val=="":returnnp.datetime64("nat")returnnp.datetime64(datetime.strptime(date_val,"%Y%m%d"))def_parse_datetime(date_val:str)->np.datetime64:ifdate_val=="T":returnnp.datetime64("nat")ifdate_val.endswith("2400"):date,_=date_val.split("T")returnnp.datetime64(datetime.strptime(date,"%Y%m%d")+timedelta(days=1))returnnp.datetime64(datetime.strptime(date_val,"%Y%m%dT%H%M"))# vectorize here doesn't speed things, it just nice for the interfaceparse_date=np.vectorize(_parse_date,["datetime64"])parse_datetime=np.vectorize(_parse_datetime,["datetime64"])iftime_arrisNone:returnparse_date(date_arr).astype("datetime64[D]")ifnp.all(time_arr=="0"):returnparse_date(date_arr).astype("datetime64[D]")time_arr=time_arr.astype("U4")iftime_pad:ifnp.any(np.char.str_len(time_arr[time_arr!=""])<4):warn("Time values are being padded with zeros",stacklevel=2)ifnotnp.all(time_arr==""):time_arr[time_arr!=""]=np.char.zfill(time_arr[time_arr!=""],4)arr=np.char.add(np.char.add(date_arr,"T"),time_arr)returnparse_datetime(arr).astype("datetime64[m]")
[docs]defcombine_dt(dataset:xr.Dataset,is_coord:bool=True,date_name:WHPName=DATE,time_name:WHPName=TIME,time_pad=False,)->xr.Dataset:"""Combine the exchange style string variables of date and optinally time into a single variable containing real datetime objects This will remove the time variable if present, and replace then rename the date variable. Date is replaced/renamed to maintain variable order in the xr.DataSet """# date and time want specific attrs whos values have been# selected by significant debatedate=dataset[date_name.full_nc_name]time:xr.DataArray|None=dataset.get(time_name.full_nc_name)# not be present, this is allowedwhp_name:WHPNameAttr=[date_name.full_whp_name,time_name.full_whp_name]try:iftimeisNone:dt_arr=_combine_dt_ndarray(date.values)else:dt_arr=_combine_dt_ndarray(date.values,time.values,time_pad=time_pad)exceptValueErroraserr:raiseExchangeError(f"Could not parse date/time cols {date_name.whp_name}{time_name.whp_name}")fromerrprecision=1/24/60# minute as day fractionifdt_arr.dtype.name=="datetime64[D]":precision=1whp_name=date_name.whp_nametime_var=xr.DataArray(dt_arr.astype("datetime64[ns]"),dims=date.dims,attrs={"standard_name":"time","whp_name":whp_name,"resolution":precision,},)ifis_coordisTrue:time_var.attrs["axis"]="T"# if the thing being combined is a coordinate, it may not contain vill valuestime_var.encoding["_FillValue"]=Noneifis_coordelsenp.nantime_var.encoding["units"]="days since 1950-01-01T00:00Z"time_var.encoding["calendar"]="gregorian"time_var.encoding["dtype"]="double"try:deldataset[time_name.nc_name]exceptKeyError:pass# this is being done in a funny way to retain the variable ordering# we will always keep the "time" variable namedataset[date_name.nc_name]=time_varreturndataset.rename({date_name.nc_name:time_name.nc_name})