Source code for pyremo.cal

"""this module handles absolute calendars that occur in REMO datasets."""

import datetime as dt
import math


# roundTime from here:
# https://stackoverflow.com/questions/3463930/how-to-round-the-minute-of-a-datetime-object-python
def roundTime(datetime=None, roundTo=60):
    """Round a datetime object to any time lapse in seconds
    dt : datetime.datetime object, default now.
    roundTo : Closest number of seconds to round to, default 1 minute.
    Author: Thierry Husson 2012 - Use it as you want but don't blame me.
    """
    if datetime is None:
        datetime = dt.datetime.now()
    seconds = (datetime.replace(tzinfo=None) - datetime.min).seconds
    rounding = (seconds + roundTo / 2) // roundTo * roundTo
    return datetime + dt.timedelta(0, rounding - seconds, -datetime.microsecond)


class AbsoluteCalendar:
    """Absolute calendar to handle absolute dates.

    Forcing Files will probably contain absolute dates. This is not cf-standard so
    we handle this manually (not using the netCDF4 num2date/date2num, those work only
    for relative calendars.).
    """

    def __init__(self, units=None, calendar=None):
        if units is None:
            self.units = "day as %Y%m%d.%f"
        if calendar is None:
            self.calendar = "proleptic_gregorian"
        self.fmt = "%Y%m%d"  # self.units.split()[2]

    def ncattrs_dict(self):
        return {
            "standard_name": "time",
            "units": self.units,
            "calendar": self.calendar,
            "axis": "T",
        }

    def date2num(self, datetime):
        """convert a datetime object to an absolute numeric date value."""
        delta = dt.timedelta(
            hours=datetime.hour, minutes=datetime.minute, seconds=datetime.second
        )
        frac = delta.total_seconds() / dt.timedelta(days=1).total_seconds()
        return float(datetime.strftime(self.fmt)) + frac

    def num2date(self, num, use_cftime=False, roundTo=60, calendar="standard"):
        """convert a numeric absolute date value to a datetime object."""
        frac, whole = math.modf(num)
        date_str = str(int(whole))
        if date_str[6:8] == "00":
            date_str = date_str[0:6] + "15"
        # date = pd.to_datetime(date_str, format=self.fmt) #dt.datetime.strptime(date_str, self.fmt)
        date = dt.datetime.strptime(date_str[0:8], self.fmt)
        datetime = roundTime(
            date + dt.timedelta(seconds=dt.timedelta(days=1).total_seconds() * frac),
            roundTo=roundTo,
        )
        if use_cftime:
            import cftime

            datetime = cftime.datetime(
                datetime.year,
                datetime.month,
                datetime.day,
                datetime.hour,
                datetime.minute,
                calendar=calendar,
            )
        return datetime


[docs] def parse_dates(ds, use_cftime=False, calendar="standard"): """Update the time axis of a REMO dataset. Updates the time axis of a REMO dataset containing an absolute time axis. Parameters ---------- ds : xr.Dataset Dataset with absolute time axis. use_cftime: bool Use cftime objects instead of datetime objects. calendar: str CF calendar if use_cftime is True. """ ds = ds.copy() ds["time"] = parse_absolute_time(ds.time, use_cftime=use_cftime, calendar=calendar) return ds
[docs] def parse_absolute_time(time, use_cftime=False, calendar="standard"): """Update a time axis containg fractional absolute dates. Updates fractional absolute dates to relative dates. Parameters ---------- time : array like or xr.DataArray Time axis containing absolute dates as float or int. use_cftime: bool Use cftime objects instead of datetime objects. calendar: str CF calendar if use_cftime is True. """ parser = AbsoluteCalendar() return [parser.num2date(date, use_cftime, calendar=calendar) for date in time]