2
Copyright (c) 2003 Gustavo Niemeyer <niemeyer@conectiva.com>
4
This module offers extensions to the standard python 2.3+
7
__author__ = "Gustavo Niemeyer <niemeyer@conectiva.com>"
8
__license__ = "PSF License"
13
__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
15
class weekday(object):
16
__slots__ = ["weekday", "n"]
18
def __init__(self, weekday, n=0):
19
self.weekday = weekday
22
def __call__(self, n):
26
return self.__class__(self.weekday, n)
28
def __eq__(self, other):
30
if self.weekday != other.weekday or self.n != other.n:
32
except AttributeError:
37
s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday]
41
return "%s(%+d)" % (s, self.n)
43
MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)])
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.
52
There's two different ways to build a relativedelta instance. The
53
first one is passing it two date/datetime classes:
55
relativedelta(datetime1, datetime2)
57
And the other way is to use the following keyword arguments:
59
year, month, day, hour, minute, seconds, microseconds:
62
years, months, weeks, days, hours, minutes, seconds, microseconds:
63
Relative information, may be negative.
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,
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.
77
Set the yearday or the non-leap year day (jump leap days).
78
These are converted to day/month/leapdays information.
80
Here is the behavior of operations with relativedelta:
82
1) Calculate the absolute year, using the 'year' argument, or the
83
original datetime year, if the argument is not present.
85
2) Add the relative 'years' argument to the absolute year.
87
3) Do steps 1 and 2 for month/months.
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.
94
5) Add the relative 'days' argument to the absolute day. Notice
95
that the 'weeks' argument is multiplied by 7 and added to
98
6) Do steps 1 and 2 for hour/hours, minute/minutes, second/seconds,
99
microsecond/microseconds.
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.
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):
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())
131
self.microseconds = 0
139
self.microsecond = None
142
months = (dt1.year*12+dt1.month)-(dt2.year*12+dt2.month)
143
self._set_months(months)
144
dtm = self.__radd__(dt2)
148
self._set_months(months)
149
dtm = self.__radd__(dt2)
153
self._set_months(months)
154
dtm = self.__radd__(dt2)
156
self.seconds = delta.seconds+delta.days*86400
157
self.microseconds = delta.microseconds
161
self.days = days+weeks*7
162
self.leapdays = leapdays
164
self.minutes = minutes
165
self.seconds = seconds
166
self.microseconds = microseconds
173
self.microsecond = microsecond
175
if type(weekday) is int:
176
self.weekday = weekdays[weekday]
178
self.weekday = weekday
188
ydayidx = [31,59,90,120,151,181,212,243,273,304,334,366]
189
for idx, ydays in enumerate(ydayidx):
195
self.day = yday-ydayidx[idx-1]
198
raise ValueError, "invalid year day (%d)" % yday
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)
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)
218
if abs(self.hours) > 23:
219
s = self.hours/abs(self.hours)
220
div, mod = divmod(self.hours*s, 24)
223
if abs(self.months) > 11:
224
s = self.months/abs(self.months)
225
div, mod = divmod(self.months*s, 12)
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):
235
def _set_months(self, months):
237
if abs(self.months) > 11:
238
s = self.months/abs(self.months)
239
div, mod = divmod(self.months*s, 12)
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
253
assert 1 <= abs(self.months) <= 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:
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,
274
minutes=self.minutes,
275
seconds=self.seconds,
276
microseconds=self.microseconds))
278
weekday, nth = self.weekday.weekday, self.weekday.n or 1
279
jumpdays = (abs(nth)-1)*7
281
jumpdays += (7-ret.weekday()+weekday)%7
283
jumpdays += (ret.weekday()-weekday)%7
285
ret += datetime.timedelta(days=jumpdays)
288
def __rsub__(self, other):
289
return self.__neg__().__radd__(other)
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)
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)
332
return relativedelta(years=-self.years,
336
minutes=-self.minutes,
337
seconds=-self.seconds,
338
microseconds=-self.microseconds,
339
leapdays=self.leapdays,
343
weekday=self.weekday,
347
microsecond=self.microsecond)
349
def __nonzero__(self):
350
return not (not self.years and
356
not self.microseconds and
357
not self.leapdays and
358
self.year is None and
359
self.month 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)
367
def __mul__(self, other):
369
return relativedelta(years=self.years*f,
370
months=self.months*f,
373
minutes=self.minutes*f,
374
seconds=self.seconds*f,
375
microseconds=self.microseconds*f,
376
leapdays=self.leapdays,
380
weekday=self.weekday,
384
microsecond=self.microsecond)
386
def __eq__(self, other):
387
if not isinstance(other, relativedelta):
389
if self.weekday or other.weekday:
390
if not self.weekday or not other.weekday:
392
if self.weekday.weekday != other.weekday.weekday:
394
n1, n2 = self.weekday.n, other.weekday.n
395
if n1 != n2 and not (n1 in (0, 1) and n2 in (0, 1)):
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)
412
def __ne__(self, other):
413
return not self.__eq__(other)
415
def __div__(self, other):
416
return self.__mul__(1/float(other))
420
for attr in ["years", "months", "days", "leapdays",
421
"hours", "minutes", "seconds", "microseconds"]:
422
value = getattr(self, attr)
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))