Source code for varwg.times

# Name:        times
# Purpose: several time conversions and stuff, formerly found in
#          time_array_conversion, timestamp or my_globals
#
# Author:      Thomas Pfaff, Magdalena Eder, Dirk Schlabing
#
# Created:     17.11.2011
# Copyright:   (c) guttenberg 2011
# Licence:     <who cares?>
#!/usr/bin/env python
"""
Time helpers and conversions (:mod:`times`)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Helper functions to convert arrays containing time information.
All the functions with a ``2`` in the name provide the advertised conversions
for scalar as well as for *array* input.

.. currentmodule:: varwg.times

.. autosummary::
   :nosignatures:
   :toctree: generated/

    cwr2datetime
    cwr2str
    cwr2unix
    datetime2doy
    datetime2ordinal
    datetime2str
    datetime2unix
    doy2datetime
    iso2datetime
    iso2unix
    ordinal2datetime
    str2datetime
    str2ordinal
    str2unix
    unix2cwr
    unix2datetime
    unix2ordinal
    unix2str

    time_part
    time_part_sort
    timestamp2index
    index2timestamp
"""
from datetime import datetime, timedelta
import itertools
import time
import calendar
import numpy as np
import pandas as pd


[docs] class TimeParseError(ValueError): pass
[docs] def cwr2datetime(cwr_string): """Converts a CWR-timesting into a datetime object. >>> cwr2datetime("2040001.00") datetime.datetime(2040, 1, 1, 0, 0) >>> cwr2datetime('1980001') datetime.datetime(1980, 1, 1, 0, 0) """ def cwr2datetime_single(cwr_string_single): year = int(cwr_string_single[:4]) doy = int(cwr_string_single[4:7]) - 1 try: decimal_day = float(cwr_string_single[7:]) except ValueError: decimal_day = 0 seconds = decimal_day * 24 * 60**2 return datetime(year, 1, 1) + timedelta(days=doy, seconds=seconds) try: return cwr2datetime_single(str(cwr_string)) except ValueError: cwr_string = np.array(cwr_string).astype(str) return np.array([cwr2datetime_single(cwr) for cwr in cwr_string])
[docs] def cwr2str(cwr_string, d_format="%d.%m.%Y %H:%M:%S"): """Converts a CWR-timestring into a human-readable timestring. >>> cwr2str("2040001.00") '01.01.2040 00:00:00' """ dt = cwr2datetime(cwr_string) return datetime2str(dt, d_format)
[docs] def cwr2unix(cwr_string): """Converts a CWR-timestring to a unix timestamp. >>> cwr2unix("2040001.00") 2208988800.0 """ def cwr2unix_single(cwr_string_single): cwr_string_single = "%013.5f" % float(cwr_string_single) yearday = int(float(cwr_string_single[4:])) # round down to whole days # this saves us from calculating the month and day-of-month timestamp = str2unix(cwr_string_single[:7], "%Y%j") seconds = (float(cwr_string_single[4:]) - yearday) * 86400 return timestamp + seconds try: return cwr2unix_single(cwr_string) except (ValueError, TypeError): return np.array([cwr2unix_single(cwr) for cwr in cwr_string])
[docs] def dy2datetime(dy): """Converts a DYRESM float time (a.k.a. julian date) into a datetime object. >>> dy2datetime(2451544.5) datetime.datetime(2000, 1, 1, 0, 0) >>> dy2datetime(2456462.4375) datetime.datetime(2013, 6, 18, 22, 30) """ return ordinal2datetime(dy - 1721424.5)
[docs] def datetime2ordinal(dt): """Converts a datetime object into an ordinal. >>> import datetime >>> dt = datetime.datetime(2040, 1, 1, 0, 0, 0, 500000) >>> datetime2ordinal(dt) 744730 >>> datetime2ordinal(datetime.datetime(1, 1, 1, 0, 0)) 1 """ try: return np.array([sub_dt.toordinal() for sub_dt in dt]) except TypeError: return dt.toordinal()
[docs] def datetime2cwr(dt): """Converts a datetime object into a CWR/Julian timestamp. >>> import datetime >>> datetime2cwr(datetime.datetime(2040, 1, 1, 12, 30, 30, 0)) 2040001.5211805555 """ def datetime2cwr_single(dt): timetuple = dt.timetuple() year = timetuple.tm_year yearday = timetuple.tm_yday seconds_as_days = ( float( timetuple.tm_hour * 3600 + timetuple.tm_min * 60 + timetuple.tm_sec ) / 86400 ) return float( str(year) + ("%03d" % yearday) + "." + str(seconds_as_days)[2:] ) try: return np.array([datetime2cwr_single(sub_dt) for sub_dt in dt]) except TypeError: return datetime2cwr_single(dt)
[docs] def date2jdn(dates): """Converts date object to julian day number (without hours!)""" def date2jdn_single(date): a = (14 - date.month) // 12 y = date.year + 4800 - a m = date.month + 12 * a - 3 return ( date.day + (153 * m + 2) // 5 + 365 * y + y // 4 - y // 100 + y // 400 - 32045 ) try: return np.array([date2jdn_single(date) for date in dates]) except TypeError: return date2jdn_single(dates)
[docs] def datetime2cwr_old(dt): return unix2cwr(datetime2unix(dt))
[docs] def datetime2str(dt, d_format="%d.%m.%Y %H:%M:%S"): """Converts a datetime object into a human-readable string. >>> import datetime >>> datetime2str(datetime.datetime(2040, 1, 1, 0, 0, 0, 500000)) '01.01.2040 00:00:00' """ try: return np.array( [sub_datetime.strftime(d_format) for sub_datetime in dt] ) except TypeError: return dt.strftime(d_format)
[docs] def datetime2unix(dt): """Converts a dateime object into a unix-timestamp. >>> import datetime >>> dt = datetime.datetime(2040, 1, 1, 0, 0, 0, 500000) >>> datetime2unix(dt) 2208988800.5 >>> datetime2unix(datetime.datetime(1970, 1, 1, 0, 0)) 0.0 """ try: tzinfo = dt.tzinfo if hasattr(dt, "tzinfo") else dt[0].tzinfo except IndexError: tzinfo = None diff = dt - datetime(1970, 1, 1, 0, 0, tzinfo=tzinfo) try: return np.array( [ sub_diff.days * 86400 + sub_diff.seconds + sub_diff.microseconds / 1e6 for sub_diff in diff ] ) except TypeError: return diff.days * 86400 + diff.seconds + diff.microseconds / 1e6
[docs] def datetime2doy(dt): """Extracts the day of year as a float from the given datetimes. >>> import datetime >>> dt = datetime.datetime(2040, 1, 1, 12, 30, 30, 500000) >>> datetime2doy(dt) 1.5211863425925927 """ def datetime2doy_single(dt): return ( dt.timetuple().tm_yday + dt.hour / 24.0 + dt.minute / (24.0 * 60) + dt.second / (24.0 * 60**2) + dt.microsecond / (24.0 * 60**2 * 1e6) ) try: return np.array([datetime2doy_single(sub_dt) for sub_dt in dt]) except TypeError: return datetime2doy_single(dt)
[docs] def datetime2hour(dt): """Extracts the hour of a day as a float from the given datetimes. >>> import datetime >>> dt = datetime.datetime(2040, 1, 1, 12, 30) >>> datetime2hour(dt) 12.5 """ def datetime2hour_single(dt): return ( dt.hour + dt.minute / 60.0 + dt.second / (60.0**2) + dt.microsecond / (60.0**2 * 1e6) ) try: return np.array([datetime2hour_single(sub_dt) for sub_dt in dt]) except TypeError: return datetime2hour_single(dt)
[docs] def doy2datetime(doy, year=2000): """Constructs a datetime object from given day of year. Parameters ---------- year : int, optional The year of the day of the year. Examples -------- >>> import datetime, numpy >>> doy2datetime(1.5211863425925927, 2040) datetime.datetime(2040, 1, 1, 12, 30, 30, 500000) >>> doy2datetime(numpy.arange(1,3)) array([datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2000, 1, 2, 0, 0)], dtype=object) """ def doy2datetime_single(doy, year): return datetime(int(year), month=1, day=1) + timedelta( days=float(doy) - 1 ) # TODO: interpret 365, 0 as a new year try: if type(year) is int: years = itertools.cycle([year]) else: years = year return np.array( [ doy2datetime_single(sub_doy, sub_year) for sub_doy, sub_year in zip(doy, years) ] ) except TypeError: return doy2datetime_single(doy, year)
# these two are virtually the same
[docs] def iso2datetime(iso): """Converts an ISO formated time string to a datetime object.""" try: return str2datetime(iso, "%Y-%m-%dT%H:%M:%S.%f") except (ValueError, TypeError): return str2datetime(iso, "%Y-%m-%dT%H:%M:%S")
[docs] def datetimefromisoformat(ts, fmt="%Y-%m-%dT%H:%M:%S"): return datetime.strptime(ts, fmt)
# (same same but different)
[docs] def iso2unix(iso): """Converts an ISO formated time string to a unix time stamp.""" try: return str2unix(iso, "%Y-%m-%dT%H:%M:%S.%f") except (ValueError, TypeError): # sometimes the microsecond is missing return str2unix(iso, "%Y-%m-%dT%H:%M:%S")
[docs] def ordinal2datetime(ord_): """Converts an ordinal to a datetime object. >>> ordinal2datetime(744730) datetime.datetime(2040, 1, 1, 0, 0) """ try: return np.array( [ ( datetime.fromordinal(int(sub_ord)) + timedelta(sub_ord - int(sub_ord)) ) for sub_ord in ord_ ] ) except TypeError: return datetime.fromordinal(int(ord_)) + timedelta(ord_ - int(ord_))
[docs] def str2datetime(str_, d_format="%d.%m.%Y %H:%M:%S"): """Converts a human readable time string tinto a datetime object. >>> str2datetime("01.01.2040 00:00:00") datetime.datetime(2040, 1, 1, 0, 0) """ try: if not isinstance(str_[0], str): raise TypeError("Sequence is of type %s, not str" % type(str_[0])) pd_series = pd.to_datetime( str_, format=d_format, # infer_datetime_format=True ) return pd_series.to_pydatetime() except (ValueError, AttributeError): return datetime.strptime(str_, d_format)
[docs] def str2ordinal(str_, d_format="%d.%m.%Y %H:%M:%S"): """Converts a human readable time string to an ordinal. >>> str2ordinal("01.01.2040 00:00:00") 744730 >>> str2ordinal("01.01.0001 00:00:00") 1 """ return datetime2ordinal(str2datetime(str_, d_format))
[docs] def str2unix(str_, d_format="%d.%m.%Y %H:%M:%S"): """Converts a time-string of the given d_format (default: "dd.mm.yyyy HH:MM:SS") into a unix-timestamp. >>> str2unix("01.01.2040 00:00:00") 2208988800.0 >>> str2unix("01.01.1970 00:00:00") 0.0 """ return datetime2unix(str2datetime(str_, d_format))
[docs] def unix2cwr(timestamp): """Converts a unix-timestamp to a CWR-timestring. >>> unix2cwr(2208988800.0) 2040001.0 """ def unix2cwr_single(timestamp_single): src_tuple = time.gmtime(timestamp_single) # we are looking for "{year}{yearday}.{seconds expressed in days} year = str(src_tuple[0]) yearday = time.strftime("%j", src_tuple) year_yearday_tuple = time.strptime(year + yearday, "%Y%j") diff_seconds = timestamp_single - calendar.timegm(year_yearday_tuple) seconds_as_days = float(diff_seconds) / 86400 return "%s%03d%s" % ( year, int(yearday), str("%f" % seconds_as_days)[1:], ) try: return float(unix2cwr_single(timestamp)) except TypeError: return np.array([float(unix2cwr_single(ts)) for ts in timestamp])
[docs] def unix2datetime(timestamp): """Convert a unix time stamp to a datetime object. >>> import datetime >>> unix2datetime(2208988800.5) datetime.datetime(2040, 1, 1, 0, 0, 0, 500000) >>> unix2datetime(0) datetime.datetime(1970, 1, 1, 0, 0) """ try: return np.array( [ datetime(1970, 1, 1) + timedelta(seconds=float(stamp)) for stamp in timestamp ] ) except TypeError: return datetime(1970, 1, 1) + timedelta(seconds=float(timestamp))
[docs] def unix2ordinal(timestamp): """Converts a unix time stamp to an ordinal. >>> unix2ordinal(2208988800.0) 744730 """ try: np.array( [datetime2ordinal(unix2datetime(stamp)) for stamp in timestamp] ) except TypeError: return datetime2ordinal(unix2datetime(timestamp))
[docs] def unix2str(timestamp, d_format="%d.%m.%Y %H:%M:%S"): """Converts a unix-timestamp into a time-string of the given d_format (default: "dd.mm.yyyy HH:MM:SS"). >>> unix2str(2208988800.5) '01.01.2040 00:00:00' >>> unix2str(0) '01.01.1970 00:00:00' """ return datetime2str(unix2datetime(timestamp), d_format)
[docs] def xls2datetime(xldate, datemode=0): """Here's the bare-knuckle no-seat-belts use-at-own-risk version posted by John Machin in http://stackoverflow.com datemode: 0 for 1900-based, 1 for 1904-based """ def xls2datetime_single(xldate, datemode): return ( # 30.Dez, weil 1900 in Excel ein Schaltjahr ist --> ab dem 1. Maerz # stimmts datetime(1899, 12, 30) + timedelta(days=xldate + 1462 * datemode) ) try: return xls2datetime_single(xldate, datemode) except (ValueError, TypeError): return np.array([xls2datetime_single(xl, datemode) for xl in xldate])
[docs] def timedelta2seconds(dt): return dt.days * 86400 + dt.seconds + dt.microseconds * 1e-6
[docs] def timedelta2slice(delta, dt, offset=0, step=None): start = offset stop = ( None if delta is None else offset + int(timedelta2seconds(delta) / timedelta2seconds(dt)) ) return slice(start, stop, step)
[docs] def timestamps2slice(startts=None, endts=None, dt=None, refts=None, step=None): """Ask Thomas. >>> timestamps2slice('2010-03-01T23:54:00', '2010-03-07T23:52:00', \ timedelta(days=1)) slice(0, 5, None) >>> timestamps2slice('2010-03-01T23:54:00', '2010-03-07T23:52:00', \ timedelta(days=1), '2010-03-04T00:00:00') slice(-2, 3, None) >>> timestamps2slice('2010-03-01T23:54:00', '2010-03-07T23:52:00', \ timedelta(days=1), '2010-01-04T00:00:00') slice(56, 61, None) """ # Dirk: Ist das Kunst oder kann das weg? # if dt is None: # tdelta = timedelta(seconds=1) if startts is None: tstart = None if type(startts) == str: tstart = datetimefromisoformat(startts) else: tstart = startts if endts is None: tend = None if type(endts) == str: tend = datetimefromisoformat(endts) else: tend = endts if refts is None: tref = tstart elif type(refts) == str: tref = datetimefromisoformat(refts) else: tref = refts if (tstart is None) or (tref is None): slicestart = None else: slicestart = int( timedelta2seconds(tstart - tref) / timedelta2seconds(dt) ) return timedelta2slice( None if ((tend is None) or (tstart is None)) else tend - tstart, dt, slicestart, step, )
[docs] def timestamp2index(ts, dt, refts, **kwargs): """Calculates the array index for a certain time in an equidistant time-series given the reference time (where the index would be 0) and the time discretization. If any of the input parameters contains timezone information, all others also need to contain timezone information. Parameters ---------- ts : str or datetime-object The timestamp to determine the index for If it is a string, it will be converted to datetime using the function _datetimefromisoformat Formatting keywords may be passed to this function dt : str or timedelta object The discretization of the time series (the amount of time that elapsed between indices) If used as a string, it needs to be given in the format "keyword1=value1,keyword2=value2". Keywords must be understood by the timedelta constructor (like days, hours, minutes, seconds) and the values may only be integers. refts : str or datetime-object The timestamp to determine the index for If it is a string, it will be converted to datetime using the function _datetimefromisoformat Formatting keywords may be passed to this function Returns ------- index : integer The index of a discrete time series array of the given parameters Examples -------- >>> timestr1, timestr2 = '2008-06-01T00:00:00', '2007-01-01T00:00:00' >>> timestamp2index(timestr1, 'minutes=5', timestr2) 148896 >>> timestamp2index(timestr1, 'hours=1,minutes=5',timestr2) 11453 >>> timestamp2index(timestr1, timedelta(hours=1, minutes=5), timestr2) 11453 """ if not isinstance(ts, datetime): _ts = datetimefromisoformat(ts, **kwargs) else: _ts = ts if not isinstance(refts, datetime): _refts = datetimefromisoformat(refts, **kwargs) else: _refts = refts if not isinstance(dt, timedelta): kwargs = dict( [ (sp[0], int(sp[1])) for sp in [item.split("=") for item in dt.split(",")] ] ) _dt = timedelta(**kwargs) else: _dt = dt return int(timedelta2seconds(_ts - _refts) / timedelta2seconds(_dt))
[docs] def isoformat2unix(ts): dt = timedelta(seconds=1) tstart = datetimefromisoformat("1970-01-01T00:00:00") return timestamp2index(datetimefromisoformat(ts), dt, tstart)
[docs] def index2timestamp(idx, dt, refts, **kwargs): """Calculates the ISOstring timestamp for a certain index in an equidistant time-series given the reference time (where the index would be 0) and the time discretization. If any of the input parameters contains timezone information, all others also need to contain timezone information. Parameters ---------- idx : str or datetime-object The timestamp to determine the index for If it is a string, it will be converted to datetime using the function _datetimefromisoformat Formatting keywords may be passed to this function dt : str or timedelta object The discretization of the time series (the amount of time that elapsed between indices) If used as a string, it needs to be given in the format "keyword1=value1,keyword2=value2". Keywords must be understood by the timedelta constructor (like days, hours, minutes, seconds) and the values may only be integers. refts : str or datetime-object The timestamp to determine the index for If it is a string, it will be converted to datetime using the function _datetimefromisoformat Formatting keywords may be passed to this function Returns ------- ISO-timestamp : string The ISO-timestamp of a discrete time series array of the given parameters in this format: '%Y-%m-%dT%H:%M:%S' Examples -------- >>> index2timestamp(25637, 'seconds=10', '2010-09-25T00:00:10') '2010-09-27T23:13:00' >>> index2timestamp(365, 'days=1', '2010-09-25T00:00:10') '2011-09-25T00:00:10' """ if not isinstance(refts, datetime): _refts = datetimefromisoformat(refts, **kwargs) else: _refts = refts if not isinstance(dt, timedelta): kwargs = dict( [ (sp[0], int(sp[1])) for sp in [item.split("=") for item in dt.split(",")] ] ) _dt = timedelta(**kwargs) else: _dt = dt return str(datetime.isoformat(_refts + idx * _dt))
# Time functions from Dirk's my_globals:
[docs] def build_diff_timestring(diff): """Split time difference given in seconds into a string representation of days, hours, minutes and seconds. """ days = hours = minutes = 0 seconds = abs(diff) while seconds >= 60: minutes += 1 seconds -= 60 while minutes >= 60: hours += 1 minutes -= 60 while hours >= 24: days += 1 hours -= 24 diff_str = "" if days > 0: diff_str += "%d d " % days if hours > 0: diff_str += "%d h " % hours if minutes > 0: diff_str += "%d m " % minutes diff_str += "%f s" % seconds return diff_str
[docs] def time_part(timestamps_or_datetimes, sub_format_str): """Returns the "time_part" of a timestamp or datetimes. The time_part has to be convertible into an integer. This is useful to group values according to months, weeks and so on. """ def single_time_part_unix(stamp, d_format): return int(unix2str(stamp, d_format)) if np.isfinite(stamp) else None def single_time_part_date(time_, d_format): return int(time_.strftime(d_format)) def single_time_part_np(time_, d_format): # this feels over-complicated return int( iso2datetime(np.datetime_as_string(time_)).strftime(d_format) ) times_ = np.asarray(timestamps_or_datetimes) if times_.dtype == object: single_time_part = single_time_part_date elif np.issubdtype(times_.dtype, np.datetime64): single_time_part = single_time_part_np else: single_time_part = single_time_part_unix try: return np.array( [ single_time_part(timestamp, sub_format_str) for timestamp in times_ ] ) except TypeError: return single_time_part(np.asscalar(times_), sub_format_str)
[docs] def time_part_(datetimes, date_part): try: return np.array([getattr(date, date_part) for date in datetimes]) except TypeError: return getattr(datetimes, date_part)
[docs] def time_part_sort_(datetimes, values, date_part): assert len(datetimes) == len(values) time_of_values = np.array([getattr(date, date_part) for date in datetimes]) grouped_values = [] times = sorted(set(time_of_values)) for time_key in times: grouped_values += [values[np.where(time_of_values == time_key)[0]]] return times, grouped_values
[docs] def time_part_sort(timestamps, values, sub_format_str): """Groups "values" as a nested list according to the "sub_format_str" of the "timestamps". >>> dtimes = datetime(2000, 1, 1) + np.arange(4) * timedelta(days=16) >>> values = np.arange(4) >>> time_part_sort(dtimes, values, "%m") ([1, 2], [array([0, 1]), array([2, 3])]) """ assert len(timestamps) == len(values) time_of_values = time_part(timestamps, sub_format_str) grouped_values = [] times = sorted(set(time_of_values)) for time_key in times: grouped_values += [values[np.where(time_of_values == time_key)[0]]] return times, grouped_values
[docs] def regularize(values, dtimes, nan=False, main_diff=None): """Regularize an irregular time series by linear interpolation. The time interval is guessed from the most frequent interval in `times`. >>> dtimes = datetime(2000, 1, 1) + np.arange(4) * timedelta(hours=1) >>> dtimes[1] += timedelta(minutes=30) >>> values = np.arange(4.) >>> values[1] = 1.5 >>> values array([ 0. , 1.5, 2. , 3. ]) >>> dtimes array([datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2000, 1, 1, 1, 30), datetime.datetime(2000, 1, 1, 2, 0), datetime.datetime(2000, 1, 1, 3, 0)], dtype=object) >>> regularize(values, dtimes) (array([ 0., 1., 2., 3.]), array([datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2000, 1, 1, 1, 0), datetime.datetime(2000, 1, 1, 2, 0), datetime.datetime(2000, 1, 1, 3, 0)], dtype=object)) """ in_unix = datetime2unix(dtimes) t_diffs = np.diff(in_unix) unique_t_diffs = np.unique(t_diffs) if len(unique_t_diffs) == 1: # our work is not needed return values, dtimes if main_diff is None: hist, edges = np.histogram(t_diffs, unique_t_diffs) main_diff_seconds = edges[np.argmax(hist)] main_diff = timedelta(seconds=main_diff_seconds) elif type(main_diff) is timedelta: main_diff_seconds = main_diff.total_seconds() else: main_diff_seconds = main_diff main_diff = timedelta(seconds=main_diff_seconds) n_diff = int(np.ceil((in_unix[-1] - in_unix[0]) / main_diff_seconds)) + 1 out_dtimes = dtimes[0] + main_diff * np.arange(n_diff) out_unix = datetime2unix(out_dtimes) out_values = np.interp(out_unix, in_unix, values) if nan: # put in nans where there are missing timesteps missing = np.where(t_diffs > main_diff_seconds)[0] if len(missing) > 1: mask = np.zeros_like(out_values, dtype=bool) for ii in missing: start_dt, end_dt = dtimes[ii : ii + 2] mask |= (out_dtimes > start_dt) & (out_dtimes < end_dt) out_values[mask] = np.nan return out_values, out_dtimes
[docs] def expand_timeseries(timestamps, repeats=4, values=None): """Interpolates timestamps and repeats according values. Assumes constant length of time-step. Examples -------- >>> import numpy as np >>> timestamps = str2unix(["01.01.1980 00:00:00", "02.01.1980 00:00:00"]) >>> values = np.array([1, 2]) >>> timestamps, values = expand_timeseries(timestamps, 2, values) >>> print (unix2str(timestamps), values) ['01.01.1980 00:00:00' '01.01.1980 12:00:00' '02.01.1980 00:00:00' '02.01.1980 12:00:00'] [1 1 2 2] """ dt = (timestamps[1] - timestamps[0]) / repeats dts = np.arange(0, repeats * dt, dt) old_len = len(timestamps) timestamps = timestamps.repeat(repeats).reshape((old_len, repeats)) timestamps += dts timestamps = timestamps.ravel() if values is not None: return timestamps, values.repeat(repeats) return timestamps
[docs] def periodic_distance(x1, x2, period): """ Examples -------- >>> periodic_distance(0, 23, 24) array(1.0) """ period = float(period) dist = (x1 - x2) % period return np.where(dist > period / 2, period - dist, dist)
[docs] def doy_distance(doy1, doy2): """ Examples -------- >>> import numpy as np >>> doy_distance(0, np.array([364, 0, 1])) array([ 1., 0., 1.]) """ return periodic_distance(doy1, doy2, 365)
[docs] def hour_distance(hour1, hour2): return periodic_distance(hour1, hour2, 24)
[docs] def daily_ranges(dtimes, data): """Daily max-min. Parameters ---------- dtimes : sequence of datetime objects For the time being it is assumed that time steps are equally spaced! data : 1d array Returns ------- ranges : 1d array length is number of days in dtimes. """ step_length = (dtimes[1] - dtimes[0]).total_seconds() steps_per_day = 60**2 * 24 / step_length data_2d = data.reshape(-1, steps_per_day) maxs = np.nanmax(data_2d, axis=1) mins = np.nanmin(data_2d, axis=1) return maxs - mins
[docs] def feb29_mask(dtimes): """Returns a mask indicating the location of the additional day in the leap years. Should help you to get rid off those buggers. Parameters ---------- dtimes : ndarray Returns ------- mask : boolean ndarray """ months = time_part(dtimes, "%m") days = time_part(dtimes, "%d") return (months == 2) & (days == 29)
if __name__ == "__main__": import doctest doctest.testmod()