jpayne@69
|
1 # -*- coding: utf-8 -*-
|
jpayne@69
|
2 import datetime
|
jpayne@69
|
3 import calendar
|
jpayne@69
|
4
|
jpayne@69
|
5 import operator
|
jpayne@69
|
6 from math import copysign
|
jpayne@69
|
7
|
jpayne@69
|
8 from six import integer_types
|
jpayne@69
|
9 from warnings import warn
|
jpayne@69
|
10
|
jpayne@69
|
11 from ._common import weekday
|
jpayne@69
|
12
|
jpayne@69
|
13 MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7))
|
jpayne@69
|
14
|
jpayne@69
|
15 __all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
|
jpayne@69
|
16
|
jpayne@69
|
17
|
jpayne@69
|
18 class relativedelta(object):
|
jpayne@69
|
19 """
|
jpayne@69
|
20 The relativedelta type is designed to be applied to an existing datetime and
|
jpayne@69
|
21 can replace specific components of that datetime, or represents an interval
|
jpayne@69
|
22 of time.
|
jpayne@69
|
23
|
jpayne@69
|
24 It is based on the specification of the excellent work done by M.-A. Lemburg
|
jpayne@69
|
25 in his
|
jpayne@69
|
26 `mx.DateTime <https://www.egenix.com/products/python/mxBase/mxDateTime/>`_ extension.
|
jpayne@69
|
27 However, notice that this type does *NOT* implement the same algorithm as
|
jpayne@69
|
28 his work. Do *NOT* expect it to behave like mx.DateTime's counterpart.
|
jpayne@69
|
29
|
jpayne@69
|
30 There are two different ways to build a relativedelta instance. The
|
jpayne@69
|
31 first one is passing it two date/datetime classes::
|
jpayne@69
|
32
|
jpayne@69
|
33 relativedelta(datetime1, datetime2)
|
jpayne@69
|
34
|
jpayne@69
|
35 The second one is passing it any number of the following keyword arguments::
|
jpayne@69
|
36
|
jpayne@69
|
37 relativedelta(arg1=x,arg2=y,arg3=z...)
|
jpayne@69
|
38
|
jpayne@69
|
39 year, month, day, hour, minute, second, microsecond:
|
jpayne@69
|
40 Absolute information (argument is singular); adding or subtracting a
|
jpayne@69
|
41 relativedelta with absolute information does not perform an arithmetic
|
jpayne@69
|
42 operation, but rather REPLACES the corresponding value in the
|
jpayne@69
|
43 original datetime with the value(s) in relativedelta.
|
jpayne@69
|
44
|
jpayne@69
|
45 years, months, weeks, days, hours, minutes, seconds, microseconds:
|
jpayne@69
|
46 Relative information, may be negative (argument is plural); adding
|
jpayne@69
|
47 or subtracting a relativedelta with relative information performs
|
jpayne@69
|
48 the corresponding arithmetic operation on the original datetime value
|
jpayne@69
|
49 with the information in the relativedelta.
|
jpayne@69
|
50
|
jpayne@69
|
51 weekday:
|
jpayne@69
|
52 One of the weekday instances (MO, TU, etc) available in the
|
jpayne@69
|
53 relativedelta module. These instances may receive a parameter N,
|
jpayne@69
|
54 specifying the Nth weekday, which could be positive or negative
|
jpayne@69
|
55 (like MO(+1) or MO(-2)). Not specifying it is the same as specifying
|
jpayne@69
|
56 +1. You can also use an integer, where 0=MO. This argument is always
|
jpayne@69
|
57 relative e.g. if the calculated date is already Monday, using MO(1)
|
jpayne@69
|
58 or MO(-1) won't change the day. To effectively make it absolute, use
|
jpayne@69
|
59 it in combination with the day argument (e.g. day=1, MO(1) for first
|
jpayne@69
|
60 Monday of the month).
|
jpayne@69
|
61
|
jpayne@69
|
62 leapdays:
|
jpayne@69
|
63 Will add given days to the date found, if year is a leap
|
jpayne@69
|
64 year, and the date found is post 28 of february.
|
jpayne@69
|
65
|
jpayne@69
|
66 yearday, nlyearday:
|
jpayne@69
|
67 Set the yearday or the non-leap year day (jump leap days).
|
jpayne@69
|
68 These are converted to day/month/leapdays information.
|
jpayne@69
|
69
|
jpayne@69
|
70 There are relative and absolute forms of the keyword
|
jpayne@69
|
71 arguments. The plural is relative, and the singular is
|
jpayne@69
|
72 absolute. For each argument in the order below, the absolute form
|
jpayne@69
|
73 is applied first (by setting each attribute to that value) and
|
jpayne@69
|
74 then the relative form (by adding the value to the attribute).
|
jpayne@69
|
75
|
jpayne@69
|
76 The order of attributes considered when this relativedelta is
|
jpayne@69
|
77 added to a datetime is:
|
jpayne@69
|
78
|
jpayne@69
|
79 1. Year
|
jpayne@69
|
80 2. Month
|
jpayne@69
|
81 3. Day
|
jpayne@69
|
82 4. Hours
|
jpayne@69
|
83 5. Minutes
|
jpayne@69
|
84 6. Seconds
|
jpayne@69
|
85 7. Microseconds
|
jpayne@69
|
86
|
jpayne@69
|
87 Finally, weekday is applied, using the rule described above.
|
jpayne@69
|
88
|
jpayne@69
|
89 For example
|
jpayne@69
|
90
|
jpayne@69
|
91 >>> from datetime import datetime
|
jpayne@69
|
92 >>> from dateutil.relativedelta import relativedelta, MO
|
jpayne@69
|
93 >>> dt = datetime(2018, 4, 9, 13, 37, 0)
|
jpayne@69
|
94 >>> delta = relativedelta(hours=25, day=1, weekday=MO(1))
|
jpayne@69
|
95 >>> dt + delta
|
jpayne@69
|
96 datetime.datetime(2018, 4, 2, 14, 37)
|
jpayne@69
|
97
|
jpayne@69
|
98 First, the day is set to 1 (the first of the month), then 25 hours
|
jpayne@69
|
99 are added, to get to the 2nd day and 14th hour, finally the
|
jpayne@69
|
100 weekday is applied, but since the 2nd is already a Monday there is
|
jpayne@69
|
101 no effect.
|
jpayne@69
|
102
|
jpayne@69
|
103 """
|
jpayne@69
|
104
|
jpayne@69
|
105 def __init__(self, dt1=None, dt2=None,
|
jpayne@69
|
106 years=0, months=0, days=0, leapdays=0, weeks=0,
|
jpayne@69
|
107 hours=0, minutes=0, seconds=0, microseconds=0,
|
jpayne@69
|
108 year=None, month=None, day=None, weekday=None,
|
jpayne@69
|
109 yearday=None, nlyearday=None,
|
jpayne@69
|
110 hour=None, minute=None, second=None, microsecond=None):
|
jpayne@69
|
111
|
jpayne@69
|
112 if dt1 and dt2:
|
jpayne@69
|
113 # datetime is a subclass of date. So both must be date
|
jpayne@69
|
114 if not (isinstance(dt1, datetime.date) and
|
jpayne@69
|
115 isinstance(dt2, datetime.date)):
|
jpayne@69
|
116 raise TypeError("relativedelta only diffs datetime/date")
|
jpayne@69
|
117
|
jpayne@69
|
118 # We allow two dates, or two datetimes, so we coerce them to be
|
jpayne@69
|
119 # of the same type
|
jpayne@69
|
120 if (isinstance(dt1, datetime.datetime) !=
|
jpayne@69
|
121 isinstance(dt2, datetime.datetime)):
|
jpayne@69
|
122 if not isinstance(dt1, datetime.datetime):
|
jpayne@69
|
123 dt1 = datetime.datetime.fromordinal(dt1.toordinal())
|
jpayne@69
|
124 elif not isinstance(dt2, datetime.datetime):
|
jpayne@69
|
125 dt2 = datetime.datetime.fromordinal(dt2.toordinal())
|
jpayne@69
|
126
|
jpayne@69
|
127 self.years = 0
|
jpayne@69
|
128 self.months = 0
|
jpayne@69
|
129 self.days = 0
|
jpayne@69
|
130 self.leapdays = 0
|
jpayne@69
|
131 self.hours = 0
|
jpayne@69
|
132 self.minutes = 0
|
jpayne@69
|
133 self.seconds = 0
|
jpayne@69
|
134 self.microseconds = 0
|
jpayne@69
|
135 self.year = None
|
jpayne@69
|
136 self.month = None
|
jpayne@69
|
137 self.day = None
|
jpayne@69
|
138 self.weekday = None
|
jpayne@69
|
139 self.hour = None
|
jpayne@69
|
140 self.minute = None
|
jpayne@69
|
141 self.second = None
|
jpayne@69
|
142 self.microsecond = None
|
jpayne@69
|
143 self._has_time = 0
|
jpayne@69
|
144
|
jpayne@69
|
145 # Get year / month delta between the two
|
jpayne@69
|
146 months = (dt1.year - dt2.year) * 12 + (dt1.month - dt2.month)
|
jpayne@69
|
147 self._set_months(months)
|
jpayne@69
|
148
|
jpayne@69
|
149 # Remove the year/month delta so the timedelta is just well-defined
|
jpayne@69
|
150 # time units (seconds, days and microseconds)
|
jpayne@69
|
151 dtm = self.__radd__(dt2)
|
jpayne@69
|
152
|
jpayne@69
|
153 # If we've overshot our target, make an adjustment
|
jpayne@69
|
154 if dt1 < dt2:
|
jpayne@69
|
155 compare = operator.gt
|
jpayne@69
|
156 increment = 1
|
jpayne@69
|
157 else:
|
jpayne@69
|
158 compare = operator.lt
|
jpayne@69
|
159 increment = -1
|
jpayne@69
|
160
|
jpayne@69
|
161 while compare(dt1, dtm):
|
jpayne@69
|
162 months += increment
|
jpayne@69
|
163 self._set_months(months)
|
jpayne@69
|
164 dtm = self.__radd__(dt2)
|
jpayne@69
|
165
|
jpayne@69
|
166 # Get the timedelta between the "months-adjusted" date and dt1
|
jpayne@69
|
167 delta = dt1 - dtm
|
jpayne@69
|
168 self.seconds = delta.seconds + delta.days * 86400
|
jpayne@69
|
169 self.microseconds = delta.microseconds
|
jpayne@69
|
170 else:
|
jpayne@69
|
171 # Check for non-integer values in integer-only quantities
|
jpayne@69
|
172 if any(x is not None and x != int(x) for x in (years, months)):
|
jpayne@69
|
173 raise ValueError("Non-integer years and months are "
|
jpayne@69
|
174 "ambiguous and not currently supported.")
|
jpayne@69
|
175
|
jpayne@69
|
176 # Relative information
|
jpayne@69
|
177 self.years = int(years)
|
jpayne@69
|
178 self.months = int(months)
|
jpayne@69
|
179 self.days = days + weeks * 7
|
jpayne@69
|
180 self.leapdays = leapdays
|
jpayne@69
|
181 self.hours = hours
|
jpayne@69
|
182 self.minutes = minutes
|
jpayne@69
|
183 self.seconds = seconds
|
jpayne@69
|
184 self.microseconds = microseconds
|
jpayne@69
|
185
|
jpayne@69
|
186 # Absolute information
|
jpayne@69
|
187 self.year = year
|
jpayne@69
|
188 self.month = month
|
jpayne@69
|
189 self.day = day
|
jpayne@69
|
190 self.hour = hour
|
jpayne@69
|
191 self.minute = minute
|
jpayne@69
|
192 self.second = second
|
jpayne@69
|
193 self.microsecond = microsecond
|
jpayne@69
|
194
|
jpayne@69
|
195 if any(x is not None and int(x) != x
|
jpayne@69
|
196 for x in (year, month, day, hour,
|
jpayne@69
|
197 minute, second, microsecond)):
|
jpayne@69
|
198 # For now we'll deprecate floats - later it'll be an error.
|
jpayne@69
|
199 warn("Non-integer value passed as absolute information. " +
|
jpayne@69
|
200 "This is not a well-defined condition and will raise " +
|
jpayne@69
|
201 "errors in future versions.", DeprecationWarning)
|
jpayne@69
|
202
|
jpayne@69
|
203 if isinstance(weekday, integer_types):
|
jpayne@69
|
204 self.weekday = weekdays[weekday]
|
jpayne@69
|
205 else:
|
jpayne@69
|
206 self.weekday = weekday
|
jpayne@69
|
207
|
jpayne@69
|
208 yday = 0
|
jpayne@69
|
209 if nlyearday:
|
jpayne@69
|
210 yday = nlyearday
|
jpayne@69
|
211 elif yearday:
|
jpayne@69
|
212 yday = yearday
|
jpayne@69
|
213 if yearday > 59:
|
jpayne@69
|
214 self.leapdays = -1
|
jpayne@69
|
215 if yday:
|
jpayne@69
|
216 ydayidx = [31, 59, 90, 120, 151, 181, 212,
|
jpayne@69
|
217 243, 273, 304, 334, 366]
|
jpayne@69
|
218 for idx, ydays in enumerate(ydayidx):
|
jpayne@69
|
219 if yday <= ydays:
|
jpayne@69
|
220 self.month = idx+1
|
jpayne@69
|
221 if idx == 0:
|
jpayne@69
|
222 self.day = yday
|
jpayne@69
|
223 else:
|
jpayne@69
|
224 self.day = yday-ydayidx[idx-1]
|
jpayne@69
|
225 break
|
jpayne@69
|
226 else:
|
jpayne@69
|
227 raise ValueError("invalid year day (%d)" % yday)
|
jpayne@69
|
228
|
jpayne@69
|
229 self._fix()
|
jpayne@69
|
230
|
jpayne@69
|
231 def _fix(self):
|
jpayne@69
|
232 if abs(self.microseconds) > 999999:
|
jpayne@69
|
233 s = _sign(self.microseconds)
|
jpayne@69
|
234 div, mod = divmod(self.microseconds * s, 1000000)
|
jpayne@69
|
235 self.microseconds = mod * s
|
jpayne@69
|
236 self.seconds += div * s
|
jpayne@69
|
237 if abs(self.seconds) > 59:
|
jpayne@69
|
238 s = _sign(self.seconds)
|
jpayne@69
|
239 div, mod = divmod(self.seconds * s, 60)
|
jpayne@69
|
240 self.seconds = mod * s
|
jpayne@69
|
241 self.minutes += div * s
|
jpayne@69
|
242 if abs(self.minutes) > 59:
|
jpayne@69
|
243 s = _sign(self.minutes)
|
jpayne@69
|
244 div, mod = divmod(self.minutes * s, 60)
|
jpayne@69
|
245 self.minutes = mod * s
|
jpayne@69
|
246 self.hours += div * s
|
jpayne@69
|
247 if abs(self.hours) > 23:
|
jpayne@69
|
248 s = _sign(self.hours)
|
jpayne@69
|
249 div, mod = divmod(self.hours * s, 24)
|
jpayne@69
|
250 self.hours = mod * s
|
jpayne@69
|
251 self.days += div * s
|
jpayne@69
|
252 if abs(self.months) > 11:
|
jpayne@69
|
253 s = _sign(self.months)
|
jpayne@69
|
254 div, mod = divmod(self.months * s, 12)
|
jpayne@69
|
255 self.months = mod * s
|
jpayne@69
|
256 self.years += div * s
|
jpayne@69
|
257 if (self.hours or self.minutes or self.seconds or self.microseconds
|
jpayne@69
|
258 or self.hour is not None or self.minute is not None or
|
jpayne@69
|
259 self.second is not None or self.microsecond is not None):
|
jpayne@69
|
260 self._has_time = 1
|
jpayne@69
|
261 else:
|
jpayne@69
|
262 self._has_time = 0
|
jpayne@69
|
263
|
jpayne@69
|
264 @property
|
jpayne@69
|
265 def weeks(self):
|
jpayne@69
|
266 return int(self.days / 7.0)
|
jpayne@69
|
267
|
jpayne@69
|
268 @weeks.setter
|
jpayne@69
|
269 def weeks(self, value):
|
jpayne@69
|
270 self.days = self.days - (self.weeks * 7) + value * 7
|
jpayne@69
|
271
|
jpayne@69
|
272 def _set_months(self, months):
|
jpayne@69
|
273 self.months = months
|
jpayne@69
|
274 if abs(self.months) > 11:
|
jpayne@69
|
275 s = _sign(self.months)
|
jpayne@69
|
276 div, mod = divmod(self.months * s, 12)
|
jpayne@69
|
277 self.months = mod * s
|
jpayne@69
|
278 self.years = div * s
|
jpayne@69
|
279 else:
|
jpayne@69
|
280 self.years = 0
|
jpayne@69
|
281
|
jpayne@69
|
282 def normalized(self):
|
jpayne@69
|
283 """
|
jpayne@69
|
284 Return a version of this object represented entirely using integer
|
jpayne@69
|
285 values for the relative attributes.
|
jpayne@69
|
286
|
jpayne@69
|
287 >>> relativedelta(days=1.5, hours=2).normalized()
|
jpayne@69
|
288 relativedelta(days=+1, hours=+14)
|
jpayne@69
|
289
|
jpayne@69
|
290 :return:
|
jpayne@69
|
291 Returns a :class:`dateutil.relativedelta.relativedelta` object.
|
jpayne@69
|
292 """
|
jpayne@69
|
293 # Cascade remainders down (rounding each to roughly nearest microsecond)
|
jpayne@69
|
294 days = int(self.days)
|
jpayne@69
|
295
|
jpayne@69
|
296 hours_f = round(self.hours + 24 * (self.days - days), 11)
|
jpayne@69
|
297 hours = int(hours_f)
|
jpayne@69
|
298
|
jpayne@69
|
299 minutes_f = round(self.minutes + 60 * (hours_f - hours), 10)
|
jpayne@69
|
300 minutes = int(minutes_f)
|
jpayne@69
|
301
|
jpayne@69
|
302 seconds_f = round(self.seconds + 60 * (minutes_f - minutes), 8)
|
jpayne@69
|
303 seconds = int(seconds_f)
|
jpayne@69
|
304
|
jpayne@69
|
305 microseconds = round(self.microseconds + 1e6 * (seconds_f - seconds))
|
jpayne@69
|
306
|
jpayne@69
|
307 # Constructor carries overflow back up with call to _fix()
|
jpayne@69
|
308 return self.__class__(years=self.years, months=self.months,
|
jpayne@69
|
309 days=days, hours=hours, minutes=minutes,
|
jpayne@69
|
310 seconds=seconds, microseconds=microseconds,
|
jpayne@69
|
311 leapdays=self.leapdays, year=self.year,
|
jpayne@69
|
312 month=self.month, day=self.day,
|
jpayne@69
|
313 weekday=self.weekday, hour=self.hour,
|
jpayne@69
|
314 minute=self.minute, second=self.second,
|
jpayne@69
|
315 microsecond=self.microsecond)
|
jpayne@69
|
316
|
jpayne@69
|
317 def __add__(self, other):
|
jpayne@69
|
318 if isinstance(other, relativedelta):
|
jpayne@69
|
319 return self.__class__(years=other.years + self.years,
|
jpayne@69
|
320 months=other.months + self.months,
|
jpayne@69
|
321 days=other.days + self.days,
|
jpayne@69
|
322 hours=other.hours + self.hours,
|
jpayne@69
|
323 minutes=other.minutes + self.minutes,
|
jpayne@69
|
324 seconds=other.seconds + self.seconds,
|
jpayne@69
|
325 microseconds=(other.microseconds +
|
jpayne@69
|
326 self.microseconds),
|
jpayne@69
|
327 leapdays=other.leapdays or self.leapdays,
|
jpayne@69
|
328 year=(other.year if other.year is not None
|
jpayne@69
|
329 else self.year),
|
jpayne@69
|
330 month=(other.month if other.month is not None
|
jpayne@69
|
331 else self.month),
|
jpayne@69
|
332 day=(other.day if other.day is not None
|
jpayne@69
|
333 else self.day),
|
jpayne@69
|
334 weekday=(other.weekday if other.weekday is not None
|
jpayne@69
|
335 else self.weekday),
|
jpayne@69
|
336 hour=(other.hour if other.hour is not None
|
jpayne@69
|
337 else self.hour),
|
jpayne@69
|
338 minute=(other.minute if other.minute is not None
|
jpayne@69
|
339 else self.minute),
|
jpayne@69
|
340 second=(other.second if other.second is not None
|
jpayne@69
|
341 else self.second),
|
jpayne@69
|
342 microsecond=(other.microsecond if other.microsecond
|
jpayne@69
|
343 is not None else
|
jpayne@69
|
344 self.microsecond))
|
jpayne@69
|
345 if isinstance(other, datetime.timedelta):
|
jpayne@69
|
346 return self.__class__(years=self.years,
|
jpayne@69
|
347 months=self.months,
|
jpayne@69
|
348 days=self.days + other.days,
|
jpayne@69
|
349 hours=self.hours,
|
jpayne@69
|
350 minutes=self.minutes,
|
jpayne@69
|
351 seconds=self.seconds + other.seconds,
|
jpayne@69
|
352 microseconds=self.microseconds + other.microseconds,
|
jpayne@69
|
353 leapdays=self.leapdays,
|
jpayne@69
|
354 year=self.year,
|
jpayne@69
|
355 month=self.month,
|
jpayne@69
|
356 day=self.day,
|
jpayne@69
|
357 weekday=self.weekday,
|
jpayne@69
|
358 hour=self.hour,
|
jpayne@69
|
359 minute=self.minute,
|
jpayne@69
|
360 second=self.second,
|
jpayne@69
|
361 microsecond=self.microsecond)
|
jpayne@69
|
362 if not isinstance(other, datetime.date):
|
jpayne@69
|
363 return NotImplemented
|
jpayne@69
|
364 elif self._has_time and not isinstance(other, datetime.datetime):
|
jpayne@69
|
365 other = datetime.datetime.fromordinal(other.toordinal())
|
jpayne@69
|
366 year = (self.year or other.year)+self.years
|
jpayne@69
|
367 month = self.month or other.month
|
jpayne@69
|
368 if self.months:
|
jpayne@69
|
369 assert 1 <= abs(self.months) <= 12
|
jpayne@69
|
370 month += self.months
|
jpayne@69
|
371 if month > 12:
|
jpayne@69
|
372 year += 1
|
jpayne@69
|
373 month -= 12
|
jpayne@69
|
374 elif month < 1:
|
jpayne@69
|
375 year -= 1
|
jpayne@69
|
376 month += 12
|
jpayne@69
|
377 day = min(calendar.monthrange(year, month)[1],
|
jpayne@69
|
378 self.day or other.day)
|
jpayne@69
|
379 repl = {"year": year, "month": month, "day": day}
|
jpayne@69
|
380 for attr in ["hour", "minute", "second", "microsecond"]:
|
jpayne@69
|
381 value = getattr(self, attr)
|
jpayne@69
|
382 if value is not None:
|
jpayne@69
|
383 repl[attr] = value
|
jpayne@69
|
384 days = self.days
|
jpayne@69
|
385 if self.leapdays and month > 2 and calendar.isleap(year):
|
jpayne@69
|
386 days += self.leapdays
|
jpayne@69
|
387 ret = (other.replace(**repl)
|
jpayne@69
|
388 + datetime.timedelta(days=days,
|
jpayne@69
|
389 hours=self.hours,
|
jpayne@69
|
390 minutes=self.minutes,
|
jpayne@69
|
391 seconds=self.seconds,
|
jpayne@69
|
392 microseconds=self.microseconds))
|
jpayne@69
|
393 if self.weekday:
|
jpayne@69
|
394 weekday, nth = self.weekday.weekday, self.weekday.n or 1
|
jpayne@69
|
395 jumpdays = (abs(nth) - 1) * 7
|
jpayne@69
|
396 if nth > 0:
|
jpayne@69
|
397 jumpdays += (7 - ret.weekday() + weekday) % 7
|
jpayne@69
|
398 else:
|
jpayne@69
|
399 jumpdays += (ret.weekday() - weekday) % 7
|
jpayne@69
|
400 jumpdays *= -1
|
jpayne@69
|
401 ret += datetime.timedelta(days=jumpdays)
|
jpayne@69
|
402 return ret
|
jpayne@69
|
403
|
jpayne@69
|
404 def __radd__(self, other):
|
jpayne@69
|
405 return self.__add__(other)
|
jpayne@69
|
406
|
jpayne@69
|
407 def __rsub__(self, other):
|
jpayne@69
|
408 return self.__neg__().__radd__(other)
|
jpayne@69
|
409
|
jpayne@69
|
410 def __sub__(self, other):
|
jpayne@69
|
411 if not isinstance(other, relativedelta):
|
jpayne@69
|
412 return NotImplemented # In case the other object defines __rsub__
|
jpayne@69
|
413 return self.__class__(years=self.years - other.years,
|
jpayne@69
|
414 months=self.months - other.months,
|
jpayne@69
|
415 days=self.days - other.days,
|
jpayne@69
|
416 hours=self.hours - other.hours,
|
jpayne@69
|
417 minutes=self.minutes - other.minutes,
|
jpayne@69
|
418 seconds=self.seconds - other.seconds,
|
jpayne@69
|
419 microseconds=self.microseconds - other.microseconds,
|
jpayne@69
|
420 leapdays=self.leapdays or other.leapdays,
|
jpayne@69
|
421 year=(self.year if self.year is not None
|
jpayne@69
|
422 else other.year),
|
jpayne@69
|
423 month=(self.month if self.month is not None else
|
jpayne@69
|
424 other.month),
|
jpayne@69
|
425 day=(self.day if self.day is not None else
|
jpayne@69
|
426 other.day),
|
jpayne@69
|
427 weekday=(self.weekday if self.weekday is not None else
|
jpayne@69
|
428 other.weekday),
|
jpayne@69
|
429 hour=(self.hour if self.hour is not None else
|
jpayne@69
|
430 other.hour),
|
jpayne@69
|
431 minute=(self.minute if self.minute is not None else
|
jpayne@69
|
432 other.minute),
|
jpayne@69
|
433 second=(self.second if self.second is not None else
|
jpayne@69
|
434 other.second),
|
jpayne@69
|
435 microsecond=(self.microsecond if self.microsecond
|
jpayne@69
|
436 is not None else
|
jpayne@69
|
437 other.microsecond))
|
jpayne@69
|
438
|
jpayne@69
|
439 def __abs__(self):
|
jpayne@69
|
440 return self.__class__(years=abs(self.years),
|
jpayne@69
|
441 months=abs(self.months),
|
jpayne@69
|
442 days=abs(self.days),
|
jpayne@69
|
443 hours=abs(self.hours),
|
jpayne@69
|
444 minutes=abs(self.minutes),
|
jpayne@69
|
445 seconds=abs(self.seconds),
|
jpayne@69
|
446 microseconds=abs(self.microseconds),
|
jpayne@69
|
447 leapdays=self.leapdays,
|
jpayne@69
|
448 year=self.year,
|
jpayne@69
|
449 month=self.month,
|
jpayne@69
|
450 day=self.day,
|
jpayne@69
|
451 weekday=self.weekday,
|
jpayne@69
|
452 hour=self.hour,
|
jpayne@69
|
453 minute=self.minute,
|
jpayne@69
|
454 second=self.second,
|
jpayne@69
|
455 microsecond=self.microsecond)
|
jpayne@69
|
456
|
jpayne@69
|
457 def __neg__(self):
|
jpayne@69
|
458 return self.__class__(years=-self.years,
|
jpayne@69
|
459 months=-self.months,
|
jpayne@69
|
460 days=-self.days,
|
jpayne@69
|
461 hours=-self.hours,
|
jpayne@69
|
462 minutes=-self.minutes,
|
jpayne@69
|
463 seconds=-self.seconds,
|
jpayne@69
|
464 microseconds=-self.microseconds,
|
jpayne@69
|
465 leapdays=self.leapdays,
|
jpayne@69
|
466 year=self.year,
|
jpayne@69
|
467 month=self.month,
|
jpayne@69
|
468 day=self.day,
|
jpayne@69
|
469 weekday=self.weekday,
|
jpayne@69
|
470 hour=self.hour,
|
jpayne@69
|
471 minute=self.minute,
|
jpayne@69
|
472 second=self.second,
|
jpayne@69
|
473 microsecond=self.microsecond)
|
jpayne@69
|
474
|
jpayne@69
|
475 def __bool__(self):
|
jpayne@69
|
476 return not (not self.years and
|
jpayne@69
|
477 not self.months and
|
jpayne@69
|
478 not self.days and
|
jpayne@69
|
479 not self.hours and
|
jpayne@69
|
480 not self.minutes and
|
jpayne@69
|
481 not self.seconds and
|
jpayne@69
|
482 not self.microseconds and
|
jpayne@69
|
483 not self.leapdays and
|
jpayne@69
|
484 self.year is None and
|
jpayne@69
|
485 self.month is None and
|
jpayne@69
|
486 self.day is None and
|
jpayne@69
|
487 self.weekday is None and
|
jpayne@69
|
488 self.hour is None and
|
jpayne@69
|
489 self.minute is None and
|
jpayne@69
|
490 self.second is None and
|
jpayne@69
|
491 self.microsecond is None)
|
jpayne@69
|
492 # Compatibility with Python 2.x
|
jpayne@69
|
493 __nonzero__ = __bool__
|
jpayne@69
|
494
|
jpayne@69
|
495 def __mul__(self, other):
|
jpayne@69
|
496 try:
|
jpayne@69
|
497 f = float(other)
|
jpayne@69
|
498 except TypeError:
|
jpayne@69
|
499 return NotImplemented
|
jpayne@69
|
500
|
jpayne@69
|
501 return self.__class__(years=int(self.years * f),
|
jpayne@69
|
502 months=int(self.months * f),
|
jpayne@69
|
503 days=int(self.days * f),
|
jpayne@69
|
504 hours=int(self.hours * f),
|
jpayne@69
|
505 minutes=int(self.minutes * f),
|
jpayne@69
|
506 seconds=int(self.seconds * f),
|
jpayne@69
|
507 microseconds=int(self.microseconds * f),
|
jpayne@69
|
508 leapdays=self.leapdays,
|
jpayne@69
|
509 year=self.year,
|
jpayne@69
|
510 month=self.month,
|
jpayne@69
|
511 day=self.day,
|
jpayne@69
|
512 weekday=self.weekday,
|
jpayne@69
|
513 hour=self.hour,
|
jpayne@69
|
514 minute=self.minute,
|
jpayne@69
|
515 second=self.second,
|
jpayne@69
|
516 microsecond=self.microsecond)
|
jpayne@69
|
517
|
jpayne@69
|
518 __rmul__ = __mul__
|
jpayne@69
|
519
|
jpayne@69
|
520 def __eq__(self, other):
|
jpayne@69
|
521 if not isinstance(other, relativedelta):
|
jpayne@69
|
522 return NotImplemented
|
jpayne@69
|
523 if self.weekday or other.weekday:
|
jpayne@69
|
524 if not self.weekday or not other.weekday:
|
jpayne@69
|
525 return False
|
jpayne@69
|
526 if self.weekday.weekday != other.weekday.weekday:
|
jpayne@69
|
527 return False
|
jpayne@69
|
528 n1, n2 = self.weekday.n, other.weekday.n
|
jpayne@69
|
529 if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)):
|
jpayne@69
|
530 return False
|
jpayne@69
|
531 return (self.years == other.years and
|
jpayne@69
|
532 self.months == other.months and
|
jpayne@69
|
533 self.days == other.days and
|
jpayne@69
|
534 self.hours == other.hours and
|
jpayne@69
|
535 self.minutes == other.minutes and
|
jpayne@69
|
536 self.seconds == other.seconds and
|
jpayne@69
|
537 self.microseconds == other.microseconds and
|
jpayne@69
|
538 self.leapdays == other.leapdays and
|
jpayne@69
|
539 self.year == other.year and
|
jpayne@69
|
540 self.month == other.month and
|
jpayne@69
|
541 self.day == other.day and
|
jpayne@69
|
542 self.hour == other.hour and
|
jpayne@69
|
543 self.minute == other.minute and
|
jpayne@69
|
544 self.second == other.second and
|
jpayne@69
|
545 self.microsecond == other.microsecond)
|
jpayne@69
|
546
|
jpayne@69
|
547 def __hash__(self):
|
jpayne@69
|
548 return hash((
|
jpayne@69
|
549 self.weekday,
|
jpayne@69
|
550 self.years,
|
jpayne@69
|
551 self.months,
|
jpayne@69
|
552 self.days,
|
jpayne@69
|
553 self.hours,
|
jpayne@69
|
554 self.minutes,
|
jpayne@69
|
555 self.seconds,
|
jpayne@69
|
556 self.microseconds,
|
jpayne@69
|
557 self.leapdays,
|
jpayne@69
|
558 self.year,
|
jpayne@69
|
559 self.month,
|
jpayne@69
|
560 self.day,
|
jpayne@69
|
561 self.hour,
|
jpayne@69
|
562 self.minute,
|
jpayne@69
|
563 self.second,
|
jpayne@69
|
564 self.microsecond,
|
jpayne@69
|
565 ))
|
jpayne@69
|
566
|
jpayne@69
|
567 def __ne__(self, other):
|
jpayne@69
|
568 return not self.__eq__(other)
|
jpayne@69
|
569
|
jpayne@69
|
570 def __div__(self, other):
|
jpayne@69
|
571 try:
|
jpayne@69
|
572 reciprocal = 1 / float(other)
|
jpayne@69
|
573 except TypeError:
|
jpayne@69
|
574 return NotImplemented
|
jpayne@69
|
575
|
jpayne@69
|
576 return self.__mul__(reciprocal)
|
jpayne@69
|
577
|
jpayne@69
|
578 __truediv__ = __div__
|
jpayne@69
|
579
|
jpayne@69
|
580 def __repr__(self):
|
jpayne@69
|
581 l = []
|
jpayne@69
|
582 for attr in ["years", "months", "days", "leapdays",
|
jpayne@69
|
583 "hours", "minutes", "seconds", "microseconds"]:
|
jpayne@69
|
584 value = getattr(self, attr)
|
jpayne@69
|
585 if value:
|
jpayne@69
|
586 l.append("{attr}={value:+g}".format(attr=attr, value=value))
|
jpayne@69
|
587 for attr in ["year", "month", "day", "weekday",
|
jpayne@69
|
588 "hour", "minute", "second", "microsecond"]:
|
jpayne@69
|
589 value = getattr(self, attr)
|
jpayne@69
|
590 if value is not None:
|
jpayne@69
|
591 l.append("{attr}={value}".format(attr=attr, value=repr(value)))
|
jpayne@69
|
592 return "{classname}({attrs})".format(classname=self.__class__.__name__,
|
jpayne@69
|
593 attrs=", ".join(l))
|
jpayne@69
|
594
|
jpayne@69
|
595
|
jpayne@69
|
596 def _sign(x):
|
jpayne@69
|
597 return int(copysign(1, x))
|
jpayne@69
|
598
|
jpayne@69
|
599 # vim:ts=4:sw=4:et
|