Source code for Goulib.datetime2

"""
additions to :mod:`datetime` standard library
"""
__author__ = "Philippe Guglielmetti"
__copyright__ = "Copyright 2012, Philippe Guglielmetti"
__credits__ = []
__license__ = "LGPL"

import re

from Goulib import math2, interval, decorators
from datetime import datetime, date, time, timedelta
import datetime as dt

# classes extending builtin


[docs]class datetime2(dt.datetime):
[docs] def __init__(self, *args, **kwargs): super(datetime2, self).__init__(*args, **kwargs)
[docs] def __sub__(self, other): d = super(datetime2, self).__sub__(self, other) return timedelta(d)
[docs]class date2(dt.date):
[docs] def init__(self, *args, **kwargs): super(date2, self).__init__(*args, **kwargs)
[docs]class time2(dt.time):
[docs] def __init__(self, *args, **kwargs): super(time2, self).__init__(*args, **kwargs)
[docs]class timedelta2(dt.timedelta):
[docs] def __init__(self, *args, **kwargs): super(timedelta2, self).__init__(*args, **kwargs) self.test = 'ok'
[docs] def isoformat(self): # allow seamless json serialization return str(self)
# useful constants timedelta0 = timedelta(0) onesecond = timedelta(seconds=1) oneminute = timedelta(minutes=1) onehour = timedelta(hours=1) oneday = timedelta(days=1) oneweek = timedelta(weeks=1) datemin = date(year=dt.MINYEAR, month=1, day=1) midnight = time() # regexes # https://stackoverflow.com/a/40309602/1395973 # https://regex101.com/r/NUZS1Z/2 rdatesep = '[^\w\d\r\n:]' ryear = '(\d{4}|\d{2})' rmonth = '(0?[1-9]|1[0-2])' rday = '(0?[1-9]|[12]\d|30|31)' rdateany = '(\b'+rday+rdatesep+rmonth+rdatesep+ryear + \ '\b)|(\b'+rmonth+rdatesep+rday+rdatesep+ryear+'\b)'
[docs]def datetimef(d, t=None, fmt='%Y-%m-%d'): """"converts something to a datetime :param d: can be: - datetime : result is a copy of d with time optionally replaced - date : result is date at time t, (00:00AM by default) - int or float : if fmt is None, d is considered as Excel date numeric format (see http://answers.oreilly.com/topic/1694-how-excel-stores-date-and-time-values/ ) - string or specified format: result is datetime parsed using specified format string :param fmt: format string. See http://docs.python.org/2/library/datetime.html#strftime-strptime-behavior :param t: optional time. replaces the time of the datetime obtained from d. Allows datetimef(date,time) :return: datetime """ if isinstance(d, dt.datetime): d = d elif isinstance(d, dt.date): d = datetime(year=d.year, month=d.month, day=d.day) elif isinstance(d, (int, float)): d = datetime(year=1900, month=1, day=1)+timedelta(days=d-2) # WHY -2 ? else: d = datetime.strptime(str(d), fmt) if t: d = d.replace(hour=t.hour, minute=t.minute, second=t.second) return d
[docs]def datef(d, fmt='%Y-%m-%d'): '''converts something to a date. See datetimef''' if isinstance(d, dt.datetime): return d.date() if isinstance(d, dt.date): return d if isinstance(d, (str, int, float)): return datetimef(d, fmt=fmt).date() return date(d) # last chance...
[docs]def timef(t, fmt='%H:%M:%S'): '''converts something to a time. See datetimef''' if isinstance(t, dt.datetime): return t.time() if isinstance(t, dt.time): return t if isinstance(t, (int, float)): if '%d' not in fmt: # t is in hours s = math2.rint(t*3600) h, m = divmod(s, 3600) m, s = divmod(m, 60) return time(hour=h, minute=m, second=s) else: # t is in days (Excel pass return datetimef(t, fmt=fmt).time()
[docs]@decorators.memoize def fmt2regex(fmt): """converts a date/time format string to the regex that comiles it :param fmt: string :return: compiled regex """ expr = fmt.replace('%D', '(?P<days>-?[0-9]\d*)') expr = expr.replace('%H', '(?P<hours>-?[0-9]\d*)') expr = expr.replace('%M', '(?P<minutes>\d+)') expr = expr.replace('%S', '(?P<seconds>\d+(\.\d+)?)') return re.compile(expr)
[docs]def timedeltaf(t, fmt=None): """converts something to a timedelta. :param t: can be: * already a timedelta, or a time, or a int/float giving a number of days (Excel) * or a string in HH:MM:SS format by default * or a string in timedelta str() output format """ if isinstance(t, timedelta): return t if isinstance(t, time): return time_sub(t, midnight) elif isinstance(t, (int, float)): return timedelta(days=t) # https://stackoverflow.com/a/21074460/1395973 if fmt is None: fmt = '(%D day[s]?[,]? )?%H:%M:%S*' m = re.match(fmt2regex(fmt), t) if m is None: raise ValueError('"%s" does not match fmt=%s' % (t, fmt)) m = m.groupdict() d = {key: float(value) for key, value in m.items() if value is not None} td = timedelta(**d) return td
[docs]def strftimedelta(t, fmt='%H:%M:%S'): """ :param t: float seconds or timedelta """ if not math2.is_number(t): t = t.total_seconds() hours, remainder = divmod(t, 3600) minutes, seconds = divmod(remainder, 60) res = fmt.replace('%H', '%d' % hours) res = res.replace('%h', '%d' % hours if hours else '') res = res.replace('%M', '%02d' % minutes) res = res.replace('%m', '%d' % minutes if minutes else '') res = res.replace('%S', '%02d' % seconds) return res
[docs]def tdround(td, s=1): """ return timedelta rounded to s seconds """ return timedelta(seconds=s*round(td.total_seconds()/s))
[docs]def minutes(td): """ :param td: timedelta :return: float timedelta in minutes """ return td.total_seconds()/60.
[docs]def hours(td): """ :param td: timedelta :return: float timedelta in hours """ return td.total_seconds()/3600.
[docs]def daysgen(start, length, step=oneday): '''returns a range of dates or datetimes''' for i in range(length): yield start try: start = start+step except: start = time_add(start, step)
[docs]def days(start, length, step=oneday): return [x for x in daysgen(start, length, step)]
[docs]def timedelta_sum(timedeltas): return sum((d for d in timedeltas if d), timedelta0)
[docs]def timedelta_div(t1, t2): '''divides a timedelta by a timedelta or a number. should be a method of timedelta...''' if isinstance(t2, timedelta): return t1.total_seconds() / t2.total_seconds() else: return timedelta(seconds=t1.total_seconds() / t2)
[docs]def timedelta_mul(t1, t2): '''multiplies a timedelta. should be a method of timedelta...''' try: # timedelta is t1 return timedelta(seconds=t1.total_seconds() * t2) except: # timedelta is t2 return timedelta(seconds=t2.total_seconds() * t1)
[docs]def time_sub(t1, t2): '''substracts 2 time. should be a method of time...''' return datetimef(datemin, t1)-datetimef(datemin, t2)
[docs]def time_add(t, d): '''adds delta to time. should be a method of time...''' return (datetimef(datemin, t)+d).time()
[docs]def add_months(date, months): day = date.day month = date.month + months - 1 # zero based year = date.year + month // 12 month = month % 12 + 1 # back to 1 based if month == 2: if day >= 29 and not year % 4 and (year % 100 or not year % 400): day = 29 elif day > 28: day = 28 elif month in (4, 6, 9, 11) and day > 30: day = 30 return date.replace(year, month, day)
[docs]def date_add(date, years=0, months=0, weeks=0, days=0): return add_months(date, years*12+months)+(weeks*7+days)*oneday
[docs]def equal(a, b, epsilon=timedelta(seconds=0.5)): """approximately equal. Use this instead of a==b :return: True if a and b are less than seconds apart """ try: d = abs(a-b) except: d = abs(time_sub(a, b)) return d < epsilon
[docs]def datetime_intersect(t1, t2): '''returns timedelta overlap between 2 intervals (tuples) of datetime''' a, b = interval.intersection(t1, t2) if not a: return timedelta0 return b-a
[docs]def time_intersect(t1, t2): '''returns timedelta overlap between 2 intervals (tuples) of time''' a, b = interval.intersection(t1, t2) if not a: return timedelta0 return time_sub(b, a)