aboutsummaryrefslogtreecommitdiff
path: root/dateutil/relativedelta.py
diff options
context:
space:
mode:
authorniemeyer <>2003-09-28 01:20:02 +0000
committerniemeyer <>2003-09-28 01:20:02 +0000
commit68ae2757ae15c84bf947d47a82a314b3b975bc9b (patch)
treeb871732d4f8d7f8c5d7acfe649d8827c53b0a5d2 /dateutil/relativedelta.py
downloaddateutil-68ae2757ae15c84bf947d47a82a314b3b975bc9b.tar.gz
Importing development code into trunk.
Diffstat (limited to 'dateutil/relativedelta.py')
-rw-r--r--dateutil/relativedelta.py378
1 files changed, 378 insertions, 0 deletions
diff --git a/dateutil/relativedelta.py b/dateutil/relativedelta.py
new file mode 100644
index 0000000..e42f9d3
--- /dev/null
+++ b/dateutil/relativedelta.py
@@ -0,0 +1,378 @@
+"""
+Copyright (c) 2003 Gustavo Niemeyer <niemeyer@conectiva.com>
+
+This module offers extensions to the standard python 2.3+
+datetime module.
+"""
+__author__ = "Gustavo Niemeyer"
+__license__ = "PSF License"
+
+import datetime
+import calendar
+
+__all__ = ["relativedelta"]
+
+class relativedelta:
+ """
+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, seconds, microseconds:
+ Absolute information.
+
+ years, months, weeks, days, hours, minutes, seconds, microseconds:
+ Relative information, may be negative.
+
+ weekday:
+ Tuple with (wday, nth), specifying the nth relative weekday.
+
+ 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 type(dt1) is not 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.weekday = weekday
+ self.hour = hour
+ self.minute = minute
+ self.second = second
+ self.microsecond = microsecond
+
+ 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 = ydays
+ 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 __radd__(self, other):
+ 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 and self.weekday[1]:
+ weekday, nth = self.weekday
+ jumpdays = (abs(nth)-1)*7
+ ret_weekday = ret.weekday()
+ if nth > 0:
+ if weekday < ret_weekday:
+ jumpdays += (6-ret_weekday)+weekday+1
+ else:
+ jumpdays += weekday-ret_weekday
+ else:
+ if weekday > ret_weekday:
+ jumpdays += ret_weekday+1+(6-weekday)
+ else:
+ jumpdays += ret_weekday-weekday
+ jumpdays *= -1
+ ret += datetime.timedelta(days=jumpdays)
+ return ret
+
+ def __rsub__(self, other):
+ return self.__neg__().__radd__(other)
+
+ def __add__(self, other):
+ if not isinstance(other, relativedelta):
+ raise TypeError, "unsupported type for add operation"
+ 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,
+ 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.second or self.microsecond)
+
+ def __sub__(self, other):
+ if not isinstance(other, relativedelta):
+ raise TypeError, "unsupported type for sub operation"
+ 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,
+ 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.second or self.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,
+ 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 __nonzero__(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
+ 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=self.years*f,
+ months=self.months*f,
+ days=self.days*f,
+ hours=self.hours*f,
+ minutes=self.minutes*f,
+ seconds=self.seconds*f,
+ microseconds=self.microseconds*f,
+ 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 __eq__(self, other):
+ if not isinstance(other, relativedelta):
+ return False
+ for attr in self.__dict__:
+ if getattr(self, attr) != getattr(other, attr):
+ return False
+ return True
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __div__(self, other):
+ return self.__mul__(1/float(other))
+
+ 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, `value`))
+ return "%s(%s)" % (self.__class__.__name__, ", ".join(l))
+
+# vim:ts=4:sw=4:et