| | |
| | import datetime |
| | import calendar |
| |
|
| | import operator |
| | from math import copysign |
| |
|
| | from six import integer_types |
| | from warnings import warn |
| |
|
| | from ._common import weekday |
| |
|
| | MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7)) |
| |
|
| | __all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"] |
| |
|
| |
|
| | class relativedelta(object): |
| | """ |
| | The relativedelta type is designed to be applied to an existing datetime and |
| | can replace specific components of that datetime, or represents an interval |
| | of time. |
| | |
| | It is based on the specification of the excellent work done by M.-A. Lemburg |
| | in his |
| | `mx.DateTime <https://www.egenix.com/products/python/mxBase/mxDateTime/>`_ 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 are two different ways to build a relativedelta instance. The |
| | first one is passing it two date/datetime classes:: |
| | |
| | relativedelta(datetime1, datetime2) |
| | |
| | The second one is passing it any number of the following keyword arguments:: |
| | |
| | relativedelta(arg1=x,arg2=y,arg3=z...) |
| | |
| | year, month, day, hour, minute, second, microsecond: |
| | Absolute information (argument is singular); adding or subtracting a |
| | relativedelta with absolute information does not perform an arithmetic |
| | operation, but rather REPLACES the corresponding value in the |
| | original datetime with the value(s) in relativedelta. |
| | |
| | years, months, weeks, days, hours, minutes, seconds, microseconds: |
| | Relative information, may be negative (argument is plural); adding |
| | or subtracting a relativedelta with relative information performs |
| | the corresponding arithmetic operation on the original datetime value |
| | with the information in the relativedelta. |
| | |
| | weekday: |
| | One of the weekday instances (MO, TU, etc) available in the |
| | relativedelta module. 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. This argument is always |
| | relative e.g. if the calculated date is already Monday, using MO(1) |
| | or MO(-1) won't change the day. To effectively make it absolute, use |
| | it in combination with the day argument (e.g. day=1, MO(1) for first |
| | Monday of the month). |
| | |
| | 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. |
| | |
| | There are relative and absolute forms of the keyword |
| | arguments. The plural is relative, and the singular is |
| | absolute. For each argument in the order below, the absolute form |
| | is applied first (by setting each attribute to that value) and |
| | then the relative form (by adding the value to the attribute). |
| | |
| | The order of attributes considered when this relativedelta is |
| | added to a datetime is: |
| | |
| | 1. Year |
| | 2. Month |
| | 3. Day |
| | 4. Hours |
| | 5. Minutes |
| | 6. Seconds |
| | 7. Microseconds |
| | |
| | Finally, weekday is applied, using the rule described above. |
| | |
| | For example |
| | |
| | >>> from datetime import datetime |
| | >>> from dateutil.relativedelta import relativedelta, MO |
| | >>> dt = datetime(2018, 4, 9, 13, 37, 0) |
| | >>> delta = relativedelta(hours=25, day=1, weekday=MO(1)) |
| | >>> dt + delta |
| | datetime.datetime(2018, 4, 2, 14, 37) |
| | |
| | First, the day is set to 1 (the first of the month), then 25 hours |
| | are added, to get to the 2nd day and 14th hour, finally the |
| | weekday is applied, but since the 2nd is already a Monday there is |
| | no effect. |
| | |
| | """ |
| |
|
| | 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) and |
| | isinstance(dt2, datetime.date)): |
| | raise TypeError("relativedelta only diffs datetime/date") |
| |
|
| | |
| | |
| | if (isinstance(dt1, datetime.datetime) != |
| | isinstance(dt2, datetime.datetime)): |
| | 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 - dt2.year) * 12 + (dt1.month - dt2.month) |
| | self._set_months(months) |
| |
|
| | |
| | |
| | dtm = self.__radd__(dt2) |
| |
|
| | |
| | if dt1 < dt2: |
| | compare = operator.gt |
| | increment = 1 |
| | else: |
| | compare = operator.lt |
| | increment = -1 |
| |
|
| | while compare(dt1, dtm): |
| | months += increment |
| | self._set_months(months) |
| | dtm = self.__radd__(dt2) |
| |
|
| | |
| | delta = dt1 - dtm |
| | self.seconds = delta.seconds + delta.days * 86400 |
| | self.microseconds = delta.microseconds |
| | else: |
| | |
| | if any(x is not None and x != int(x) for x in (years, months)): |
| | raise ValueError("Non-integer years and months are " |
| | "ambiguous and not currently supported.") |
| |
|
| | |
| | self.years = int(years) |
| | self.months = int(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 any(x is not None and int(x) != x |
| | for x in (year, month, day, hour, |
| | minute, second, microsecond)): |
| | |
| | warn("Non-integer value passed as absolute information. " + |
| | "This is not a well-defined condition and will raise " + |
| | "errors in future versions.", DeprecationWarning) |
| |
|
| | 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 = _sign(self.microseconds) |
| | div, mod = divmod(self.microseconds * s, 1000000) |
| | self.microseconds = mod * s |
| | self.seconds += div * s |
| | if abs(self.seconds) > 59: |
| | s = _sign(self.seconds) |
| | div, mod = divmod(self.seconds * s, 60) |
| | self.seconds = mod * s |
| | self.minutes += div * s |
| | if abs(self.minutes) > 59: |
| | s = _sign(self.minutes) |
| | div, mod = divmod(self.minutes * s, 60) |
| | self.minutes = mod * s |
| | self.hours += div * s |
| | if abs(self.hours) > 23: |
| | s = _sign(self.hours) |
| | div, mod = divmod(self.hours * s, 24) |
| | self.hours = mod * s |
| | self.days += div * s |
| | if abs(self.months) > 11: |
| | s = _sign(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 |
| |
|
| | @property |
| | def weeks(self): |
| | return int(self.days / 7.0) |
| |
|
| | @weeks.setter |
| | def weeks(self, value): |
| | self.days = self.days - (self.weeks * 7) + value * 7 |
| |
|
| | def _set_months(self, months): |
| | self.months = months |
| | if abs(self.months) > 11: |
| | s = _sign(self.months) |
| | div, mod = divmod(self.months * s, 12) |
| | self.months = mod * s |
| | self.years = div * s |
| | else: |
| | self.years = 0 |
| |
|
| | def normalized(self): |
| | """ |
| | Return a version of this object represented entirely using integer |
| | values for the relative attributes. |
| | |
| | >>> relativedelta(days=1.5, hours=2).normalized() |
| | relativedelta(days=+1, hours=+14) |
| | |
| | :return: |
| | Returns a :class:`dateutil.relativedelta.relativedelta` object. |
| | """ |
| | |
| | days = int(self.days) |
| |
|
| | hours_f = round(self.hours + 24 * (self.days - days), 11) |
| | hours = int(hours_f) |
| |
|
| | minutes_f = round(self.minutes + 60 * (hours_f - hours), 10) |
| | minutes = int(minutes_f) |
| |
|
| | seconds_f = round(self.seconds + 60 * (minutes_f - minutes), 8) |
| | seconds = int(seconds_f) |
| |
|
| | microseconds = round(self.microseconds + 1e6 * (seconds_f - seconds)) |
| |
|
| | |
| | return self.__class__(years=self.years, months=self.months, |
| | days=days, hours=hours, minutes=minutes, |
| | seconds=seconds, microseconds=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 __add__(self, other): |
| | if isinstance(other, relativedelta): |
| | return self.__class__(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 if other.year is not None |
| | else self.year), |
| | month=(other.month if other.month is not None |
| | else self.month), |
| | day=(other.day if other.day is not None |
| | else self.day), |
| | weekday=(other.weekday if other.weekday is not None |
| | else self.weekday), |
| | hour=(other.hour if other.hour is not None |
| | else self.hour), |
| | minute=(other.minute if other.minute is not None |
| | else self.minute), |
| | second=(other.second if other.second is not None |
| | else self.second), |
| | microsecond=(other.microsecond if other.microsecond |
| | is not None else |
| | self.microsecond)) |
| | if isinstance(other, datetime.timedelta): |
| | return self.__class__(years=self.years, |
| | months=self.months, |
| | days=self.days + other.days, |
| | hours=self.hours, |
| | minutes=self.minutes, |
| | seconds=self.seconds + other.seconds, |
| | microseconds=self.microseconds + other.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) |
| | if not isinstance(other, datetime.date): |
| | return NotImplemented |
| | 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): |
| | return NotImplemented |
| | return self.__class__(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 if self.year is not None |
| | else other.year), |
| | month=(self.month if self.month is not None else |
| | other.month), |
| | day=(self.day if self.day is not None else |
| | other.day), |
| | weekday=(self.weekday if self.weekday is not None else |
| | other.weekday), |
| | hour=(self.hour if self.hour is not None else |
| | other.hour), |
| | minute=(self.minute if self.minute is not None else |
| | other.minute), |
| | second=(self.second if self.second is not None else |
| | other.second), |
| | microsecond=(self.microsecond if self.microsecond |
| | is not None else |
| | other.microsecond)) |
| |
|
| | def __abs__(self): |
| | return self.__class__(years=abs(self.years), |
| | months=abs(self.months), |
| | days=abs(self.days), |
| | hours=abs(self.hours), |
| | minutes=abs(self.minutes), |
| | seconds=abs(self.seconds), |
| | microseconds=abs(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 __neg__(self): |
| | return self.__class__(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) |
| | |
| | __nonzero__ = __bool__ |
| |
|
| | def __mul__(self, other): |
| | try: |
| | f = float(other) |
| | except TypeError: |
| | return NotImplemented |
| |
|
| | return self.__class__(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 NotImplemented |
| | 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.microseconds == other.microseconds 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 __hash__(self): |
| | return hash(( |
| | self.weekday, |
| | self.years, |
| | self.months, |
| | self.days, |
| | self.hours, |
| | self.minutes, |
| | self.seconds, |
| | self.microseconds, |
| | self.leapdays, |
| | self.year, |
| | self.month, |
| | self.day, |
| | self.hour, |
| | self.minute, |
| | self.second, |
| | self.microsecond, |
| | )) |
| |
|
| | def __ne__(self, other): |
| | return not self.__eq__(other) |
| |
|
| | def __div__(self, other): |
| | try: |
| | reciprocal = 1 / float(other) |
| | except TypeError: |
| | return NotImplemented |
| |
|
| | return self.__mul__(reciprocal) |
| |
|
| | __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("{attr}={value:+g}".format(attr=attr, value=value)) |
| | for attr in ["year", "month", "day", "weekday", |
| | "hour", "minute", "second", "microsecond"]: |
| | value = getattr(self, attr) |
| | if value is not None: |
| | l.append("{attr}={value}".format(attr=attr, value=repr(value))) |
| | return "{classname}({attrs})".format(classname=self.__class__.__name__, |
| | attrs=", ".join(l)) |
| |
|
| |
|
| | def _sign(x): |
| | return int(copysign(1, x)) |
| |
|
| | |
| |
|