diff --git a/da/tools/general.py b/da/tools/general.py index 2d5363ea3cea4d7bdf7fb15a7c32d2f360e205bb..1167cab04af45474af9a50eeddbf879c322c8295 100755 --- a/da/tools/general.py +++ b/da/tools/general.py @@ -17,6 +17,22 @@ import shutil import datetime import re +from dateutil.rrule import rrule, MO, TU, WE, TH, FR, SA, SU, YEARLY, \ + MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDLY + +HOURS_PER_DAY = 24. +MINUTES_PER_DAY = 60.*HOURS_PER_DAY +SECONDS_PER_DAY = 60.*MINUTES_PER_DAY +MUSECONDS_PER_DAY = 1e6*SECONDS_PER_DAY +SEC_PER_MIN = 60 +SEC_PER_HOUR = 3600 +SEC_PER_DAY = SEC_PER_HOUR * 24 +SEC_PER_WEEK = SEC_PER_DAY * 7 +MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY = ( + MO, TU, WE, TH, FR, SA, SU) +WEEKDAYS = (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) + + def validate_rc(rcfile, needed_items): """ validate the contents of an rc-file given a dictionary of required keys """ @@ -165,5 +181,86 @@ def name_convert(name=None, to=None): return "" return "%s_%s-%s_%d" % (code, platform[0], strategy[0], lab_num) +def _to_ordinalf(dt): + """ + Convert :mod:`datetime` to the Gregorian date as UTC float days, + preserving hours, minutes, seconds and microseconds. Return value + is a :func:`float`. + """ + + if hasattr(dt, 'tzinfo') and dt.tzinfo is not None: + delta = dt.tzinfo.utcoffset(dt) + if delta is not None: + dt -= delta + + base = float(dt.toordinal()) + if hasattr(dt, 'hour'): + base += (dt.hour/HOURS_PER_DAY + dt.minute/MINUTES_PER_DAY + + dt.second/SECONDS_PER_DAY + dt.microsecond/MUSECONDS_PER_DAY + ) + return base + +def _from_ordinalf(x, tz=None): + """ + Convert Gregorian float of the date, preserving hours, minutes, + seconds and microseconds. Return value is a :class:`datetime`. + """ + if tz is None: tz = _get_rc_timezone() + ix = int(x) + dt = datetime.datetime.fromordinal(ix) + remainder = float(x) - ix + hour, remainder = divmod(24*remainder, 1) + minute, remainder = divmod(60*remainder, 1) + second, remainder = divmod(60*remainder, 1) + microsecond = int(1e6*remainder) + if microsecond<10: microsecond=0 # compensate for rounding errors + dt = datetime.datetime( + dt.year, dt.month, dt.day, int(hour), int(minute), int(second), + microsecond, tzinfo=UTC).astimezone(tz) + + if microsecond>999990: # compensate for rounding errors + dt += datetime.timedelta(microseconds=1e6-microsecond) + + return dt + +def date2num(d): + """ + *d* is either a :class:`datetime` instance or a sequence of datetimes. + + Return value is a floating point number (or sequence of floats) + which gives the number of days (fraction part represents hours, + minutes, seconds) since 0001-01-01 00:00:00 UTC, *plus* *one*. + The addition of one here is a historical artifact. Also, note + that the Gregorian calendar is assumed; this is not universal + practice. For details, see the module docstring. + """ + try: + return np.asarray([_to_ordinalf(val) for val in d]) + except: + return _to_ordinalf(d) + + +def num2date(x, tz=None): + """ + *x* is a float value which gives the number of days + (fraction part represents hours, minutes, seconds) since + 0001-01-01 00:00:00 UTC *plus* *one*. + The addition of one here is a historical artifact. Also, note + that the Gregorian calendar is assumed; this is not universal + practice. For details, see the module docstring. + + Return value is a :class:`datetime` instance in timezone *tz* (default to + rcparams TZ value). + + If *x* is a sequence, a sequence of :class:`datetime` objects will + be returned. + """ + if tz is None: tz = _get_rc_timezone() + try: + return [_from_ordinalf(val, tz) for val in x] + except: + return _from_ordinalf(x, tz) + + if __name__ == "__main__": pass