Source code for grid_process.time_utils
# Copyright (c) 2026 MeteoSwiss, contributors listed in AUTHORS
#
# Distributed under the terms of the BSD 3-Clause License.
#
# SPDX-License-Identifier: BSD-3-Clause
"""
Module to wrap some time utility functions
"""
import logging
from functools import partial
from numpy import ndarray
from pandas import to_datetime, to_timedelta, DatetimeIndex
import xarray as xr
from grid_process.helpers import make_accessor
from grid_process.data_model import get_validtime
logger = logging.getLogger(__name__)
# Register xarray accessor so module functions can also be called as methods
# on xr.Dataset objects
[docs]
@xr.register_dataset_accessor("time_utils")
class TimeUtils:
"""
Xarray accessors class for time utility methods
"""
def __init__(self, ds: xr.Dataset):
self._data = ds
dataset_accessor = partial(make_accessor, accessor_class=TimeUtils)
[docs]
def to_datetime_tzaware(timestamps: ndarray) -> DatetimeIndex:
"""
Converts an array of numpy.datetime64 objects to a timezone-aware
pandas.DatetimeIndex representing time in UTC.
Parameters
----------
timestamps : array of naive numpy.datetime64
assumed to be in UTC time
Returns
--------
out : pandas.DatetimeIndex
UTC timezone aware
"""
return to_datetime(timestamps).tz_localize("UTC")
[docs]
@dataset_accessor
def set_daily(ds: xr.Dataset, lt_duplicates: bool = True, lt_hr: int = 0) -> xr.Dataset:
"""
Sets timestamps of daily aggregations at the start of the day (i.e. 00:00).
Daily parameters are stored in the DWH following this convention. If
``lt_duplicates=True``, selects correct validtimes between summer and
winter local time duplicates first, otherwise assume daily aggregation are
for UTC periods.
Parameters
----------
ds : xarray.Dataset
with a valid time dimension (numpy.datetime64). When ``lt_duplicates=True``,
duplicated timestamps corresponding to summer (UTC+2) and winter
(UTC+1) localtime periods are expected as for a dataset returned by
:func:`~grid_process.aggreg.interval_stat` with arg ``lt_duplicates``
set to True
lt_duplicates : bool
whether the dataset contains duplicated timestamps corresponding
to summer (UTC+2) and winter (UTC+1) localtime periods. Default is True
lt_hr : int
hour of the day representing the end of the daily period in localtime.
Only used when ``lt_duplicates=True``. Default is 0, meaning the end
of the period is 00:00 J+1 LT
Returns
-------
ds_out : xarray.Dataset
dataset where daily timestamps are set to 00:00 of the respective
day. Optionally, selection of correct timestamps between summer/winter
duplicates based on day of year is done
See also
--------
:func:`~grid_process.aggreg.interval_stat`
"""
validtime = get_validtime(ds, raise_notfound=True)
if lt_duplicates:
lt_dates = to_datetime_tzaware(ds[validtime].values).tz_convert("Europe/Zurich")
ds_out = ds.isel({validtime: (lt_dates.hour == lt_hr)})
return ds_out.assign_coords({validtime: ds_out[validtime].dt.floor("D")})
return ds.assign_coords(
{
validtime: xr.where(
ds[validtime].dt.hour == 0,
ds[validtime] - to_timedelta("24h"),
ds[validtime].dt.floor("D"),
)
}
)