chromium / external / code.launchpad.net / dateutil / 41d96ae8239a1643d0b1214238b8398deb823f18 / . / dateutil / relativedelta.py

""" | |

Copyright (c) 2003-2010 Gustavo Niemeyer <gustavo@niemeyer.net> | |

This module offers extensions to the standard Python | |

datetime module. | |

""" | |

__license__ = "Simplified BSD" | |

import datetime | |

import calendar | |

from six import integer_types | |

__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"] | |

class weekday(object): | |

__slots__ = ["weekday", "n"] | |

def __init__(self, weekday, n=None): | |

self.weekday = weekday | |

self.n = n | |

def __call__(self, n): | |

if n == self.n: | |

return self | |

else: | |

return self.__class__(self.weekday, n) | |

def __eq__(self, other): | |

try: | |

if self.weekday != other.weekday or self.n != other.n: | |

return False | |

except AttributeError: | |

return False | |

return True | |

def __repr__(self): | |

s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday] | |

if not self.n: | |

return s | |

else: | |

return "%s(%+d)" % (s, self.n) | |

MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)]) | |

class relativedelta(object): | |

""" | |

The relativedelta type is based on the specification of the excelent | |

work done by M.-A. Lemburg in his mx.DateTime extension. However, | |

notice that this type does *NOT* implement the same algorithm as | |

his work. Do *NOT* expect it to behave like mx.DateTime's counterpart. | |

There's two different ways to build a relativedelta instance. The | |

first one is passing it two date/datetime classes: | |

relativedelta(datetime1, datetime2) | |

And the other way is to use the following keyword arguments: | |

year, month, day, hour, minute, second, microsecond: | |

Absolute information. | |

years, months, weeks, days, hours, minutes, seconds, microseconds: | |

Relative information, may be negative. | |

weekday: | |

One of the weekday instances (MO, TU, etc). These instances may | |

receive a parameter N, specifying the Nth weekday, which could | |

be positive or negative (like MO(+1) or MO(-2). Not specifying | |

it is the same as specifying +1. You can also use an integer, | |

where 0=MO. | |

leapdays: | |

Will add given days to the date found, if year is a leap | |

year, and the date found is post 28 of february. | |

yearday, nlyearday: | |

Set the yearday or the non-leap year day (jump leap days). | |

These are converted to day/month/leapdays information. | |

Here is the behavior of operations with relativedelta: | |

1) Calculate the absolute year, using the 'year' argument, or the | |

original datetime year, if the argument is not present. | |

2) Add the relative 'years' argument to the absolute year. | |

3) Do steps 1 and 2 for month/months. | |

4) Calculate the absolute day, using the 'day' argument, or the | |

original datetime day, if the argument is not present. Then, | |

subtract from the day until it fits in the year and month | |

found after their operations. | |

5) Add the relative 'days' argument to the absolute day. Notice | |

that the 'weeks' argument is multiplied by 7 and added to | |

'days'. | |

6) Do steps 1 and 2 for hour/hours, minute/minutes, second/seconds, | |

microsecond/microseconds. | |

7) If the 'weekday' argument is present, calculate the weekday, | |

with the given (wday, nth) tuple. wday is the index of the | |

weekday (0-6, 0=Mon), and nth is the number of weeks to add | |

forward or backward, depending on its signal. Notice that if | |

the calculated date is already Monday, for example, using | |

(0, 1) or (0, -1) won't change the day. | |

""" | |

def __init__(self, dt1=None, dt2=None, | |

years=0, months=0, days=0, leapdays=0, weeks=0, | |

hours=0, minutes=0, seconds=0, microseconds=0, | |

year=None, month=None, day=None, weekday=None, | |

yearday=None, nlyearday=None, | |

hour=None, minute=None, second=None, microsecond=None): | |

if dt1 and dt2: | |

if (not isinstance(dt1, datetime.date)) or (not isinstance(dt2, datetime.date)): | |

raise TypeError("relativedelta only diffs datetime/date") | |

if not type(dt1) == type(dt2): #isinstance(dt1, type(dt2)): | |

if not isinstance(dt1, datetime.datetime): | |

dt1 = datetime.datetime.fromordinal(dt1.toordinal()) | |

elif not isinstance(dt2, datetime.datetime): | |

dt2 = datetime.datetime.fromordinal(dt2.toordinal()) | |

self.years = 0 | |

self.months = 0 | |

self.days = 0 | |

self.leapdays = 0 | |

self.hours = 0 | |

self.minutes = 0 | |

self.seconds = 0 | |

self.microseconds = 0 | |

self.year = None | |

self.month = None | |

self.day = None | |

self.weekday = None | |

self.hour = None | |

self.minute = None | |

self.second = None | |

self.microsecond = None | |

self._has_time = 0 | |

months = (dt1.year*12+dt1.month)-(dt2.year*12+dt2.month) | |

self._set_months(months) | |

dtm = self.__radd__(dt2) | |

if dt1 < dt2: | |

while dt1 > dtm: | |

months += 1 | |

self._set_months(months) | |

dtm = self.__radd__(dt2) | |

else: | |

while dt1 < dtm: | |

months -= 1 | |

self._set_months(months) | |

dtm = self.__radd__(dt2) | |

delta = dt1 - dtm | |

self.seconds = delta.seconds+delta.days*86400 | |

self.microseconds = delta.microseconds | |

else: | |

self.years = years | |

self.months = months | |

self.days = days+weeks*7 | |

self.leapdays = leapdays | |

self.hours = hours | |

self.minutes = minutes | |

self.seconds = seconds | |

self.microseconds = microseconds | |

self.year = year | |

self.month = month | |

self.day = day | |

self.hour = hour | |

self.minute = minute | |

self.second = second | |

self.microsecond = microsecond | |

if isinstance(weekday, integer_types): | |

self.weekday = weekdays[weekday] | |

else: | |

self.weekday = weekday | |

yday = 0 | |

if nlyearday: | |

yday = nlyearday | |

elif yearday: | |

yday = yearday | |

if yearday > 59: | |

self.leapdays = -1 | |

if yday: | |

ydayidx = [31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 366] | |

for idx, ydays in enumerate(ydayidx): | |

if yday <= ydays: | |

self.month = idx+1 | |

if idx == 0: | |

self.day = yday | |

else: | |

self.day = yday-ydayidx[idx-1] | |

break | |

else: | |

raise ValueError("invalid year day (%d)" % yday) | |

self._fix() | |

def _fix(self): | |

if abs(self.microseconds) > 999999: | |

s = self.microseconds//abs(self.microseconds) | |

div, mod = divmod(self.microseconds*s, 1000000) | |

self.microseconds = mod*s | |

self.seconds += div*s | |

if abs(self.seconds) > 59: | |

s = self.seconds//abs(self.seconds) | |

div, mod = divmod(self.seconds*s, 60) | |

self.seconds = mod*s | |

self.minutes += div*s | |

if abs(self.minutes) > 59: | |

s = self.minutes//abs(self.minutes) | |

div, mod = divmod(self.minutes*s, 60) | |

self.minutes = mod*s | |

self.hours += div*s | |

if abs(self.hours) > 23: | |

s = self.hours//abs(self.hours) | |

div, mod = divmod(self.hours*s, 24) | |

self.hours = mod*s | |

self.days += div*s | |

if abs(self.months) > 11: | |

s = self.months//abs(self.months) | |

div, mod = divmod(self.months*s, 12) | |

self.months = mod*s | |

self.years += div*s | |

if (self.hours or self.minutes or self.seconds or self.microseconds or | |

self.hour is not None or self.minute is not None or | |

self.second is not None or self.microsecond is not None): | |

self._has_time = 1 | |

else: | |

self._has_time = 0 | |

def _set_months(self, months): | |

self.months = months | |

if abs(self.months) > 11: | |

s = self.months//abs(self.months) | |

div, mod = divmod(self.months*s, 12) | |

self.months = mod*s | |

self.years = div*s | |

else: | |

self.years = 0 | |

def __add__(self, other): | |

if isinstance(other, relativedelta): | |

return relativedelta(years=other.years+self.years, | |

months=other.months+self.months, | |

days=other.days+self.days, | |

hours=other.hours+self.hours, | |

minutes=other.minutes+self.minutes, | |

seconds=other.seconds+self.seconds, | |

microseconds=other.microseconds+self.microseconds, | |

leapdays=other.leapdays or self.leapdays, | |

year=other.year or self.year, | |

month=other.month or self.month, | |

day=other.day or self.day, | |

weekday=other.weekday or self.weekday, | |

hour=other.hour or self.hour, | |

minute=other.minute or self.minute, | |

second=other.second or self.second, | |

microsecond=other.microsecond or self.microsecond) | |

if not isinstance(other, datetime.date): | |

raise TypeError("unsupported type for add operation") | |

elif self._has_time and not isinstance(other, datetime.datetime): | |

other = datetime.datetime.fromordinal(other.toordinal()) | |

year = (self.year or other.year)+self.years | |

month = self.month or other.month | |

if self.months: | |

assert 1 <= abs(self.months) <= 12 | |

month += self.months | |

if month > 12: | |

year += 1 | |

month -= 12 | |

elif month < 1: | |

year -= 1 | |

month += 12 | |

day = min(calendar.monthrange(year, month)[1], | |

self.day or other.day) | |

repl = {"year": year, "month": month, "day": day} | |

for attr in ["hour", "minute", "second", "microsecond"]: | |

value = getattr(self, attr) | |

if value is not None: | |

repl[attr] = value | |

days = self.days | |

if self.leapdays and month > 2 and calendar.isleap(year): | |

days += self.leapdays | |

ret = (other.replace(**repl) | |

+ datetime.timedelta(days=days, | |

hours=self.hours, | |

minutes=self.minutes, | |

seconds=self.seconds, | |

microseconds=self.microseconds)) | |

if self.weekday: | |

weekday, nth = self.weekday.weekday, self.weekday.n or 1 | |

jumpdays = (abs(nth)-1)*7 | |

if nth > 0: | |

jumpdays += (7-ret.weekday()+weekday)%7 | |

else: | |

jumpdays += (ret.weekday()-weekday)%7 | |

jumpdays *= -1 | |

ret += datetime.timedelta(days=jumpdays) | |

return ret | |

def __radd__(self, other): | |

return self.__add__(other) | |

def __rsub__(self, other): | |

return self.__neg__().__radd__(other) | |

def __sub__(self, other): | |

if not isinstance(other, relativedelta): | |

raise TypeError("unsupported type for sub operation") | |

return relativedelta(years=self.years-other.years, | |

months=self.months-other.months, | |

days=self.days-other.days, | |

hours=self.hours-other.hours, | |

minutes=self.minutes-other.minutes, | |

seconds=self.seconds-other.seconds, | |

microseconds=self.microseconds-other.microseconds, | |

leapdays=self.leapdays or other.leapdays, | |

year=self.year or other.year, | |

month=self.month or other.month, | |

day=self.day or other.day, | |

weekday=self.weekday or other.weekday, | |

hour=self.hour or other.hour, | |

minute=self.minute or other.minute, | |

second=self.second or other.second, | |

microsecond=self.microsecond or other.microsecond) | |

def __neg__(self): | |

return relativedelta(years=-self.years, | |

months=-self.months, | |

days=-self.days, | |

hours=-self.hours, | |

minutes=-self.minutes, | |

seconds=-self.seconds, | |

microseconds=-self.microseconds, | |

leapdays=self.leapdays, | |

year=self.year, | |

month=self.month, | |

day=self.day, | |

weekday=self.weekday, | |

hour=self.hour, | |

minute=self.minute, | |

second=self.second, | |

microsecond=self.microsecond) | |

def __bool__(self): | |

return not (not self.years and | |

not self.months and | |

not self.days and | |

not self.hours and | |

not self.minutes and | |

not self.seconds and | |

not self.microseconds and | |

not self.leapdays and | |

self.year is None and | |

self.month is None and | |

self.day is None and | |

self.weekday is None and | |

self.hour is None and | |

self.minute is None and | |

self.second is None and | |

self.microsecond is None) | |

def __mul__(self, other): | |

f = float(other) | |

return relativedelta(years=int(self.years*f), | |

months=int(self.months*f), | |

days=int(self.days*f), | |

hours=int(self.hours*f), | |

minutes=int(self.minutes*f), | |

seconds=int(self.seconds*f), | |

microseconds=int(self.microseconds*f), | |

leapdays=self.leapdays, | |

year=self.year, | |

month=self.month, | |

day=self.day, | |

weekday=self.weekday, | |

hour=self.hour, | |

minute=self.minute, | |

second=self.second, | |

microsecond=self.microsecond) | |

__rmul__ = __mul__ | |

def __eq__(self, other): | |

if not isinstance(other, relativedelta): | |

return False | |

if self.weekday or other.weekday: | |

if not self.weekday or not other.weekday: | |

return False | |

if self.weekday.weekday != other.weekday.weekday: | |

return False | |

n1, n2 = self.weekday.n, other.weekday.n | |

if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)): | |

return False | |

return (self.years == other.years and | |

self.months == other.months and | |

self.days == other.days and | |

self.hours == other.hours and | |

self.minutes == other.minutes and | |

self.seconds == other.seconds and | |

self.leapdays == other.leapdays and | |

self.year == other.year and | |

self.month == other.month and | |

self.day == other.day and | |

self.hour == other.hour and | |

self.minute == other.minute and | |

self.second == other.second and | |

self.microsecond == other.microsecond) | |

def __ne__(self, other): | |

return not self.__eq__(other) | |

def __div__(self, other): | |

return self.__mul__(1/float(other)) | |

__truediv__ = __div__ | |

def __repr__(self): | |

l = [] | |

for attr in ["years", "months", "days", "leapdays", | |

"hours", "minutes", "seconds", "microseconds"]: | |

value = getattr(self, attr) | |

if value: | |

l.append("%s=%+d" % (attr, value)) | |

for attr in ["year", "month", "day", "weekday", | |

"hour", "minute", "second", "microsecond"]: | |

value = getattr(self, attr) | |

if value is not None: | |

l.append("%s=%s" % (attr, repr(value))) | |

return "%s(%s)" % (self.__class__.__name__, ", ".join(l)) | |

# vim:ts=4:sw=4:et |