root/django/trunk/contrib/dateutil/relativedelta.py

Revision 84, 16.7 kB (checked in by steadicat, 15 months ago)

Added loads of useful contrib.

  • Property svn:keywords set to Id
Line 
1"""
2Copyright (c) 2003-2005  Gustavo Niemeyer <gustavo@niemeyer.net>
3
4This module offers extensions to the standard python 2.3+
5datetime module.
6"""
7__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
8__license__ = "PSF License"
9
10import datetime
11import calendar
12
13__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
14
15class weekday(object):
16    __slots__ = ["weekday", "n"]
17
18    def __init__(self, weekday, n=None):
19        self.weekday = weekday
20        self.n = n
21
22    def __call__(self, n):
23        if n == self.n:
24            return self
25        else:
26            return self.__class__(self.weekday, n)
27
28    def __eq__(self, other):
29        try:
30            if self.weekday != other.weekday or self.n != other.n:
31                return False
32        except AttributeError:
33            return False
34        return True
35
36    def __repr__(self):
37        s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday]
38        if not self.n:
39            return s
40        else:
41            return "%s(%+d)" % (s, self.n)
42
43MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)])
44
45class relativedelta:
46    """
47The relativedelta type is based on the specification of the excelent
48work done by M.-A. Lemburg in his mx.DateTime extension. However,
49notice that this type does *NOT* implement the same algorithm as
50his work. Do *NOT* expect it to behave like mx.DateTime's counterpart.
51
52There's two different ways to build a relativedelta instance. The
53first one is passing it two date/datetime classes:
54
55    relativedelta(datetime1, datetime2)
56
57And the other way is to use the following keyword arguments:
58
59    year, month, day, hour, minute, second, microsecond:
60        Absolute information.
61
62    years, months, weeks, days, hours, minutes, seconds, microseconds:
63        Relative information, may be negative.
64
65    weekday:
66        One of the weekday instances (MO, TU, etc). These instances may
67        receive a parameter N, specifying the Nth weekday, which could
68        be positive or negative (like MO(+1) or MO(-2). Not specifying
69        it is the same as specifying +1. You can also use an integer,
70        where 0=MO.
71
72    leapdays:
73        Will add given days to the date found, if year is a leap
74        year, and the date found is post 28 of february.
75
76    yearday, nlyearday:
77        Set the yearday or the non-leap year day (jump leap days).
78        These are converted to day/month/leapdays information.
79
80Here is the behavior of operations with relativedelta:
81
821) Calculate the absolute year, using the 'year' argument, or the
83   original datetime year, if the argument is not present.
84
852) Add the relative 'years' argument to the absolute year.
86
873) Do steps 1 and 2 for month/months.
88
894) Calculate the absolute day, using the 'day' argument, or the
90   original datetime day, if the argument is not present. Then,
91   subtract from the day until it fits in the year and month
92   found after their operations.
93
945) Add the relative 'days' argument to the absolute day. Notice
95   that the 'weeks' argument is multiplied by 7 and added to
96   'days'.
97
986) Do steps 1 and 2 for hour/hours, minute/minutes, second/seconds,
99   microsecond/microseconds.
100
1017) If the 'weekday' argument is present, calculate the weekday,
102   with the given (wday, nth) tuple. wday is the index of the
103   weekday (0-6, 0=Mon), and nth is the number of weeks to add
104   forward or backward, depending on its signal. Notice that if
105   the calculated date is already Monday, for example, using
106   (0, 1) or (0, -1) won't change the day.
107    """
108
109    def __init__(self, dt1=None, dt2=None,
110                 years=0, months=0, days=0, leapdays=0, weeks=0,
111                 hours=0, minutes=0, seconds=0, microseconds=0,
112                 year=None, month=None, day=None, weekday=None,
113                 yearday=None, nlyearday=None,
114                 hour=None, minute=None, second=None, microsecond=None):
115        if dt1 and dt2:
116            if not isinstance(dt1, datetime.date) or \
117               not isinstance(dt2, datetime.date):
118                raise TypeError, "relativedelta only diffs datetime/date"
119            if type(dt1) is not type(dt2):
120                if not isinstance(dt1, datetime.datetime):
121                    dt1 = datetime.datetime.fromordinal(dt1.toordinal())
122                elif not isinstance(dt2, datetime.datetime):
123                    dt2 = datetime.datetime.fromordinal(dt2.toordinal())
124            self.years = 0
125            self.months = 0
126            self.days = 0
127            self.leapdays = 0
128            self.hours = 0
129            self.minutes = 0
130            self.seconds = 0
131            self.microseconds = 0
132            self.year = None
133            self.month = None
134            self.day = None
135            self.weekday = None
136            self.hour = None
137            self.minute = None
138            self.second = None
139            self.microsecond = None
140            self._has_time = 0
141
142            months = (dt1.year*12+dt1.month)-(dt2.year*12+dt2.month)
143            self._set_months(months)
144            dtm = self.__radd__(dt2)
145            if dt1 < dt2:
146                while dt1 > dtm:
147                    months += 1
148                    self._set_months(months)
149                    dtm = self.__radd__(dt2)
150            else:
151                while dt1 < dtm:
152                    months -= 1
153                    self._set_months(months)
154                    dtm = self.__radd__(dt2)
155            delta = dt1 - dtm
156            self.seconds = delta.seconds+delta.days*86400
157            self.microseconds = delta.microseconds
158        else:
159            self.years = years
160            self.months = months
161            self.days = days+weeks*7
162            self.leapdays = leapdays
163            self.hours = hours
164            self.minutes = minutes
165            self.seconds = seconds
166            self.microseconds = microseconds
167            self.year = year
168            self.month = month
169            self.day = day
170            self.hour = hour
171            self.minute = minute
172            self.second = second
173            self.microsecond = microsecond
174
175            if type(weekday) is int:
176                self.weekday = weekdays[weekday]
177            else:
178                self.weekday = weekday
179
180            yday = 0
181            if nlyearday:
182                yday = nlyearday
183            elif yearday:
184                yday = yearday
185                if yearday > 59:
186                    self.leapdays = -1
187            if yday:
188                ydayidx = [31,59,90,120,151,181,212,243,273,304,334,366]
189                for idx, ydays in enumerate(ydayidx):
190                    if yday <= ydays:
191                        self.month = idx+1
192                        if idx == 0:
193                            self.day = ydays
194                        else:
195                            self.day = yday-ydayidx[idx-1]
196                        break
197                else:
198                    raise ValueError, "invalid year day (%d)" % yday
199
200        self._fix()
201
202    def _fix(self):
203        if abs(self.microseconds) > 999999:
204            s = self.microseconds/abs(self.microseconds)
205            div, mod = divmod(self.microseconds*s, 1000000)
206            self.microseconds = mod*s
207            self.seconds += div*s
208        if abs(self.seconds) > 59:
209            s = self.seconds/abs(self.seconds)
210            div, mod = divmod(self.seconds*s, 60)
211            self.seconds = mod*s
212            self.minutes += div*s
213        if abs(self.minutes) > 59:
214            s = self.minutes/abs(self.minutes)
215            div, mod = divmod(self.minutes*s, 60)
216            self.minutes = mod*s
217            self.hours += div*s
218        if abs(self.hours) > 23:
219            s = self.hours/abs(self.hours)
220            div, mod = divmod(self.hours*s, 24)
221            self.hours = mod*s
222            self.days += div*s
223        if abs(self.months) > 11:
224            s = self.months/abs(self.months)
225            div, mod = divmod(self.months*s, 12)
226            self.months = mod*s
227            self.years += div*s
228        if (self.hours or self.minutes or self.seconds or self.microseconds or
229            self.hour is not None or self.minute is not None or
230            self.second is not None or self.microsecond is not None):
231            self._has_time = 1
232        else:
233            self._has_time = 0
234
235    def _set_months(self, months):
236        self.months = months
237        if abs(self.months) > 11:
238            s = self.months/abs(self.months)
239            div, mod = divmod(self.months*s, 12)
240            self.months = mod*s
241            self.years = div*s
242        else:
243            self.years = 0
244
245    def __radd__(self, other):
246        if not isinstance(other, datetime.date):
247            raise TypeError, "unsupported type for add operation"
248        elif self._has_time and not isinstance(other, datetime.datetime):
249            other = datetime.datetime.fromordinal(other.toordinal())
250        year = (self.year or other.year)+self.years
251        month = self.month or other.month
252        if self.months:
253            assert 1 <= abs(self.months) <= 12
254            month += self.months
255            if month > 12:
256                year += 1
257                month -= 12
258            elif month < 1:
259                year -= 1
260                month += 12
261        day = min(calendar.monthrange(year, month)[1],
262                  self.day or other.day)
263        repl = {"year": year, "month": month, "day": day}
264        for attr in ["hour", "minute", "second", "microsecond"]:
265            value = getattr(self, attr)
266            if value is not None:
267                repl[attr] = value
268        days = self.days
269        if self.leapdays and month > 2 and calendar.isleap(year):
270            days += self.leapdays
271        ret = (other.replace(**repl)
272               + datetime.timedelta(days=days,
273                                    hours=self.hours,
274                                    minutes=self.minutes,
275                                    seconds=self.seconds,
276                                    microseconds=self.microseconds))
277        if self.weekday:
278            weekday, nth = self.weekday.weekday, self.weekday.n or 1
279            jumpdays = (abs(nth)-1)*7
280            if nth > 0:
281                jumpdays += (7-ret.weekday()+weekday)%7
282            else:
283                jumpdays += (ret.weekday()-weekday)%7
284                jumpdays *= -1
285            ret += datetime.timedelta(days=jumpdays)
286        return ret
287
288    def __rsub__(self, other):
289        return self.__neg__().__radd__(other)
290
291    def __add__(self, other):
292        if not isinstance(other, relativedelta):
293            raise TypeError, "unsupported type for add operation"
294        return relativedelta(years=other.years+self.years,
295                             months=other.months+self.months,
296                             days=other.days+self.days,
297                             hours=other.hours+self.hours,
298                             minutes=other.minutes+self.minutes,
299                             seconds=other.seconds+self.seconds,
300                             microseconds=other.microseconds+self.microseconds,
301                             leapdays=other.leapdays or self.leapdays,
302                             year=other.year or self.year,
303                             month=other.month or self.month,
304                             day=other.day or self.day,
305                             weekday=other.weekday or self.weekday,
306                             hour=other.hour or self.hour,
307                             minute=other.minute or self.minute,
308                             second=other.second or self.second,
309                             microsecond=other.second or self.microsecond)
310
311    def __sub__(self, other):
312        if not isinstance(other, relativedelta):
313            raise TypeError, "unsupported type for sub operation"
314        return relativedelta(years=other.years-self.years,
315                             months=other.months-self.months,
316                             days=other.days-self.days,
317                             hours=other.hours-self.hours,
318                             minutes=other.minutes-self.minutes,
319                             seconds=other.seconds-self.seconds,
320                             microseconds=other.microseconds-self.microseconds,
321                             leapdays=other.leapdays or self.leapdays,
322                             year=other.year or self.year,
323                             month=other.month or self.month,
324                             day=other.day or self.day,
325                             weekday=other.weekday or self.weekday,
326                             hour=other.hour or self.hour,
327                             minute=other.minute or self.minute,
328                             second=other.second or self.second,
329                             microsecond=other.second or self.microsecond)
330
331    def __neg__(self):
332        return relativedelta(years=-self.years,
333                             months=-self.months,
334                             days=-self.days,
335                             hours=-self.hours,
336                             minutes=-self.minutes,
337                             seconds=-self.seconds,
338                             microseconds=-self.microseconds,
339                             leapdays=self.leapdays,
340                             year=self.year,
341                             month=self.month,
342                             day=self.day,
343                             weekday=self.weekday,
344                             hour=self.hour,
345                             minute=self.minute,
346                             second=self.second,
347                             microsecond=self.microsecond)
348
349    def __nonzero__(self):
350        return not (not self.years and
351                    not self.months and
352                    not self.days and
353                    not self.hours and
354                    not self.minutes and
355                    not self.seconds and
356                    not self.microseconds and
357                    not self.leapdays and
358                    self.year is None and
359                    self.month is None and
360                    self.day is None and
361                    self.weekday is None and
362                    self.hour is None and
363                    self.minute is None and
364                    self.second is None and
365                    self.microsecond is None)
366
367    def __mul__(self, other):
368        f = float(other)
369        return relativedelta(years=self.years*f,
370                             months=self.months*f,
371                             days=self.days*f,
372                             hours=self.hours*f,
373                             minutes=self.minutes*f,
374                             seconds=self.seconds*f,
375                             microseconds=self.microseconds*f,
376                             leapdays=self.leapdays,
377                             year=self.year,
378                             month=self.month,
379                             day=self.day,
380                             weekday=self.weekday,
381                             hour=self.hour,
382                             minute=self.minute,
383                             second=self.second,
384                             microsecond=self.microsecond)
385
386    def __eq__(self, other):
387        if not isinstance(other, relativedelta):
388            return False
389        if self.weekday or other.weekday:
390            if not self.weekday or not other.weekday:
391                return False
392            if self.weekday.weekday != other.weekday.weekday:
393                return False
394            n1, n2 = self.weekday.n, other.weekday.n
395            if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)):
396                return False
397        return (self.years == other.years and
398                self.months == other.months and
399                self.days == other.days and
400                self.hours == other.hours and
401                self.minutes == other.minutes and
402                self.seconds == other.seconds and
403                self.leapdays == other.leapdays and
404                self.year == other.year and
405                self.month == other.month and
406                self.day == other.day and
407                self.hour == other.hour and
408                self.minute == other.minute and
409                self.second == other.second and
410                self.microsecond == other.microsecond)
411
412    def __ne__(self, other):
413        return not self.__eq__(other)
414
415    def __div__(self, other):
416        return self.__mul__(1/float(other))
417
418    def __repr__(self):
419        l = []
420        for attr in ["years", "months", "days", "leapdays",
421                     "hours", "minutes", "seconds", "microseconds"]:
422            value = getattr(self, attr)
423            if value:
424                l.append("%s=%+d" % (attr, value))
425        for attr in ["year", "month", "day", "weekday",
426                     "hour", "minute", "second", "microsecond"]:
427            value = getattr(self, attr)
428            if value is not None:
429                l.append("%s=%s" % (attr, `value`))
430        return "%s(%s)" % (self.__class__.__name__, ", ".join(l))
431
432# vim:ts=4:sw=4:et
Note: See TracBrowser for help on using the browser.