~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to fai/python-dateutil-0.4/dateutil/relativedelta.py

  • Committer: Robert Collins
  • Date: 2005-09-13 15:11:39 UTC
  • mto: (147.2.6) (364.1.3 bzrtools)
  • mto: This revision was merged to the branch mainline in revision 324.
  • Revision ID: robertc@robertcollins.net-20050913151139-9ac920fc9d7bda31
TODOification

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""
 
2
Copyright (c) 2003  Gustavo Niemeyer <niemeyer@conectiva.com>
 
3
 
 
4
This module offers extensions to the standard python 2.3+
 
5
datetime module.
 
6
"""
 
7
__author__ = "Gustavo Niemeyer <niemeyer@conectiva.com>"
 
8
__license__ = "PSF License"
 
9
 
 
10
import datetime
 
11
import calendar
 
12
 
 
13
__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
 
14
 
 
15
class weekday(object):
 
16
    __slots__ = ["weekday", "n"]
 
17
 
 
18
    def __init__(self, weekday, n=0):
 
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
 
 
43
MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)])
 
44
 
 
45
class relativedelta:
 
46
    """
 
47
The relativedelta type is based on the specification of the excelent
 
48
work done by M.-A. Lemburg in his mx.DateTime extension. However,
 
49
notice that this type does *NOT* implement the same algorithm as
 
50
his work. Do *NOT* expect it to behave like mx.DateTime's counterpart.
 
51
 
 
52
There's two different ways to build a relativedelta instance. The
 
53
first one is passing it two date/datetime classes:
 
54
 
 
55
    relativedelta(datetime1, datetime2)
 
56
 
 
57
And the other way is to use the following keyword arguments:
 
58
 
 
59
    year, month, day, hour, minute, seconds, microseconds:
 
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
 
 
80
Here is the behavior of operations with relativedelta:
 
81
 
 
82
1) Calculate the absolute year, using the 'year' argument, or the
 
83
   original datetime year, if the argument is not present.
 
84
 
 
85
2) Add the relative 'years' argument to the absolute year.
 
86
 
 
87
3) Do steps 1 and 2 for month/months.
 
88
 
 
89
4) 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
 
 
94
5) 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
 
 
98
6) Do steps 1 and 2 for hour/hours, minute/minutes, second/seconds,
 
99
   microsecond/microseconds.
 
100
 
 
101
7) 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 (n1 in (0, 1) and n2 in (0, 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