From 78e9adaf0c56778104e0cd8761b0acb1c1db6d8f Mon Sep 17 00:00:00 2001 From: kevindougherty-noaa Date: Thu, 16 Oct 2025 15:16:20 +0000 Subject: [PATCH 1/2] make validTimes np.datetime64 for easier matplotlib plotting backend --- src/eva/data/ioda_stats.py | 42 +++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/eva/data/ioda_stats.py b/src/eva/data/ioda_stats.py index e655045b..4234c8fa 100644 --- a/src/eva/data/ioda_stats.py +++ b/src/eva/data/ioda_stats.py @@ -17,6 +17,7 @@ import netCDF4 as nc import numpy as np +import pandas as pd # -------------------------------------------------------------------------------------------------- @@ -69,6 +70,34 @@ def subset_channels(ds, channels): return ds +# -------------------------------------------------------------------------------------------------- +# Valid time helper + +def _validtime_iso_to_datetime64ns(values, units=None): + """ + Convert ISO-8601 strings (or byte-strings) such as "YYYY-MM-DDTHH:MM:SSZ" + to numpy datetime64[ns] in UTC and tz-naive form (friendly for Matplotlib). + + Parameters + ---------- + values : array-like + String/bytes/object array of ISO-8601 timestamps. + + Returns + ------- + numpy.ndarray + Array of dtype datetime64[ns] with original shape. + """ + a = np.asarray(values) + # bytes -> unicode first + if a.dtype.kind == "S": + a = a.astype("U") + # only convert strings/objects; otherwise return as-is + if a.dtype.kind in {"U", "O"}: + ts = pd.to_datetime(a.ravel(), utc=True, errors="coerce").tz_convert(None) + return ts.to_numpy(dtype="datetime64[ns]").reshape(a.shape) + return values + # -------------------------------------------------------------------------------------------------- @@ -186,6 +215,15 @@ def execute(self, dataset_config, data_collections, timing): domain = ds_header['statisticDomain'] add_domain = True + # Normalize validTime from ISO8601 strings to datetime64[ns] + if add_validTime: + vt_vals = _validtime_iso_to_datetime64ns(validTime.values) + validTime = DataArray( + vt_vals, + dims=validTime.dims, + attrs={**getattr(validTime, "attrs", {}), "axis_type": "time"} + ) + ds_header.close() # Set the channels based on user selection and add channels variable @@ -251,7 +289,9 @@ def execute(self, dataset_config, data_collections, timing): ds['MetaData::channelNumber'] = sensor_channels if add_validTime: - ds['analysisCycle'] = validTime + # Expose normalized time as a coordinate for concat, + # and as a data variable for plotting/backends. + ds = ds.assign_coords(analysisCycle=validTime) ds['MetaData::validTime'] = validTime # Set channels From 913f7bcea0347b0c2c5085379f9347edec3afbd2 Mon Sep 17 00:00:00 2001 From: kevindougherty-noaa Date: Thu, 16 Oct 2025 15:32:14 +0000 Subject: [PATCH 2/2] pycodestyle --- src/eva/data/ioda_stats.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/eva/data/ioda_stats.py b/src/eva/data/ioda_stats.py index 4234c8fa..09127589 100644 --- a/src/eva/data/ioda_stats.py +++ b/src/eva/data/ioda_stats.py @@ -73,6 +73,7 @@ def subset_channels(ds, channels): # -------------------------------------------------------------------------------------------------- # Valid time helper + def _validtime_iso_to_datetime64ns(values, units=None): """ Convert ISO-8601 strings (or byte-strings) such as "YYYY-MM-DDTHH:MM:SSZ"