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