1
# -*- coding: utf-8 -*-
2 84
import datetime
3 84
import calendar
4

5 84
import operator
6 84
from math import copysign
7

8 84
from six import integer_types
9 84
from warnings import warn
10

11 84
from ._common import weekday
12

13 84
MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7))
14

15 84
__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
16

17

18 84
class 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.
23

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.
29

30
    There are two different ways to build a relativedelta instance. The
31
    first one is passing it two date/datetime classes::
32

33
        relativedelta(datetime1, datetime2)
34

35
    The second one is passing it any number of the following keyword arguments::
36

37
        relativedelta(arg1=x,arg2=y,arg3=z...)
38

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.
44

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.
50

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).
61

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.
65

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.
69

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).
75

76
    The order of attributes considered when this relativedelta is
77
    added to a datetime is:
78

79
    1. Year
80
    2. Month
81
    3. Day
82
    4. Hours
83
    5. Minutes
84
    6. Seconds
85
    7. Microseconds
86

87
    Finally, weekday is applied, using the rule described above.
88

89
    For example
90

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)
97

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.
102

103
    """
104

105 84
    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):
111

112 84
        if dt1 and dt2:
113
            # datetime is a subclass of date. So both must be date
114 84
            if not (isinstance(dt1, datetime.date) and
115
                    isinstance(dt2, datetime.date)):
116 84
                raise TypeError("relativedelta only diffs datetime/date")
117

118
            # We allow two dates, or two datetimes, so we coerce them to be
119
            # of the same type
120 84
            if (isinstance(dt1, datetime.datetime) !=
121
                    isinstance(dt2, datetime.datetime)):
122 84
                if not isinstance(dt1, datetime.datetime):
123 84
                    dt1 = datetime.datetime.fromordinal(dt1.toordinal())
124 84
                elif not isinstance(dt2, datetime.datetime):
125 84
                    dt2 = datetime.datetime.fromordinal(dt2.toordinal())
126

127 84
            self.years = 0
128 84
            self.months = 0
129 84
            self.days = 0
130 84
            self.leapdays = 0
131 84
            self.hours = 0
132 84
            self.minutes = 0
133 84
            self.seconds = 0
134 84
            self.microseconds = 0
135 84
            self.year = None
136 84
            self.month = None
137 84
            self.day = None
138 84
            self.weekday = None
139 84
            self.hour = None
140 84
            self.minute = None
141 84
            self.second = None
142 84
            self.microsecond = None
143 84
            self._has_time = 0
144

145
            # Get year / month delta between the two
146 84
            months = (dt1.year - dt2.year) * 12 + (dt1.month - dt2.month)
147 84
            self._set_months(months)
148

149
            # Remove the year/month delta so the timedelta is just well-defined
150
            # time units (seconds, days and microseconds)
151 84
            dtm = self.__radd__(dt2)
152

153
            # If we've overshot our target, make an adjustment
154 84
            if dt1 < dt2:
155 84
                compare = operator.gt
156 84
                increment = 1
157
            else:
158 84
                compare = operator.lt
159 84
                increment = -1
160

161 84
            while compare(dt1, dtm):
162 84
                months += increment
163 84
                self._set_months(months)
164 84
                dtm = self.__radd__(dt2)
165

166
            # Get the timedelta between the "months-adjusted" date and dt1
167 84
            delta = dt1 - dtm
168 84
            self.seconds = delta.seconds + delta.days * 86400
169 84
            self.microseconds = delta.microseconds
170
        else:
171
            # Check for non-integer values in integer-only quantities
172 84
            if any(x is not None and x != int(x) for x in (years, months)):
173 84
                raise ValueError("Non-integer years and months are "
174
                                 "ambiguous and not currently supported.")
175

176
            # Relative information
177 84
            self.years = int(years)
178 84
            self.months = int(months)
179 84
            self.days = days + weeks * 7
180 84
            self.leapdays = leapdays
181 84
            self.hours = hours
182 84
            self.minutes = minutes
183 84
            self.seconds = seconds
184 84
            self.microseconds = microseconds
185

186
            # Absolute information
187 84
            self.year = year
188 84
            self.month = month
189 84
            self.day = day
190 84
            self.hour = hour
191 84
            self.minute = minute
192 84
            self.second = second
193 84
            self.microsecond = microsecond
194

195 84
            if any(x is not None and int(x) != x
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 84
                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)
202

203 84
            if isinstance(weekday, integer_types):
204 84
                self.weekday = weekdays[weekday]
205
            else:
206 84
                self.weekday = weekday
207

208 84
            yday = 0
209 84
            if nlyearday:
210 84
                yday = nlyearday
211 84
            elif yearday:
212 84
                yday = yearday
213 84
                if yearday > 59:
214 84
                    self.leapdays = -1
215 84
            if yday:
216 84
                ydayidx = [31, 59, 90, 120, 151, 181, 212,
217
                           243, 273, 304, 334, 366]
218 84
                for idx, ydays in enumerate(ydayidx):
219 84
                    if yday <= ydays:
220 84
                        self.month = idx+1
221 84
                        if idx == 0:
222 84
                            self.day = yday
223
                        else:
224 84
                            self.day = yday-ydayidx[idx-1]
225 84
                        break
226
                else:
227 84
                    raise ValueError("invalid year day (%d)" % yday)
228

229 84
        self._fix()
230

231 84
    def _fix(self):
232 84
        if abs(self.microseconds) > 999999:
233 84
            s = _sign(self.microseconds)
234 84
            div, mod = divmod(self.microseconds * s, 1000000)
235 84
            self.microseconds = mod * s
236 84
            self.seconds += div * s
237 84
        if abs(self.seconds) > 59:
238 84
            s = _sign(self.seconds)
239 84
            div, mod = divmod(self.seconds * s, 60)
240 84
            self.seconds = mod * s
241 84
            self.minutes += div * s
242 84
        if abs(self.minutes) > 59:
243 84
            s = _sign(self.minutes)
244 84
            div, mod = divmod(self.minutes * s, 60)
245 84
            self.minutes = mod * s
246 84
            self.hours += div * s
247 84
        if abs(self.hours) > 23:
248 84
            s = _sign(self.hours)
249 84
            div, mod = divmod(self.hours * s, 24)
250 84
            self.hours = mod * s
251 84
            self.days += div * s
252 84
        if abs(self.months) > 11:
253 84
            s = _sign(self.months)
254 84
            div, mod = divmod(self.months * s, 12)
255 84
            self.months = mod * s
256 84
            self.years += div * s
257 84
        if (self.hours or self.minutes or self.seconds or self.microseconds
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 84
            self._has_time = 1
261
        else:
262 84
            self._has_time = 0
263

264 84
    @property
265 27
    def weeks(self):
266 84
        return int(self.days / 7.0)
267

268 84
    @weeks.setter
269 27
    def weeks(self, value):
270 84
        self.days = self.days - (self.weeks * 7) + value * 7
271

272 84
    def _set_months(self, months):
273 84
        self.months = months
274 84
        if abs(self.months) > 11:
275 84
            s = _sign(self.months)
276 84
            div, mod = divmod(self.months * s, 12)
277 84
            self.months = mod * s
278 84
            self.years = div * s
279
        else:
280 84
            self.years = 0
281

282 84
    def normalized(self):
283
        """
284
        Return a version of this object represented entirely using integer
285
        values for the relative attributes.
286

287
        >>> relativedelta(days=1.5, hours=2).normalized()
288
        relativedelta(days=+1, hours=+14)
289

290
        :return:
291
            Returns a :class:`dateutil.relativedelta.relativedelta` object.
292
        """
293
        # Cascade remainders down (rounding each to roughly nearest microsecond)
294 84
        days = int(self.days)
295

296 84
        hours_f = round(self.hours + 24 * (self.days - days), 11)
297 84
        hours = int(hours_f)
298

299 84
        minutes_f = round(self.minutes + 60 * (hours_f - hours), 10)
300 84
        minutes = int(minutes_f)
301

302 84
        seconds_f = round(self.seconds + 60 * (minutes_f - minutes), 8)
303 84
        seconds = int(seconds_f)
304

305 84
        microseconds = round(self.microseconds + 1e6 * (seconds_f - seconds))
306

307
        # Constructor carries overflow back up with call to _fix()
308 84
        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)
316

317 84
    def __add__(self, other):
318 84
        if isinstance(other, relativedelta):
319 84
            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 84
        if isinstance(other, datetime.timedelta):
346 84
            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 84
        if not isinstance(other, datetime.date):
363 84
            return NotImplemented
364 84
        elif self._has_time and not isinstance(other, datetime.datetime):
365 84
            other = datetime.datetime.fromordinal(other.toordinal())
366 84
        year = (self.year or other.year)+self.years
367 84
        month = self.month or other.month
368 84
        if self.months:
369 84
            assert 1 <= abs(self.months) <= 12
370 84
            month += self.months
371 84
            if month > 12:
372 84
                year += 1
373 84
                month -= 12
374 84
            elif month < 1:
375 84
                year -= 1
376 84
                month += 12
377 84
        day = min(calendar.monthrange(year, month)[1],
378
                  self.day or other.day)
379 84
        repl = {"year": year, "month": month, "day": day}
380 84
        for attr in ["hour", "minute", "second", "microsecond"]:
381 84
            value = getattr(self, attr)
382 84
            if value is not None:
383 84
                repl[attr] = value
384 84
        days = self.days
385 84
        if self.leapdays and month > 2 and calendar.isleap(year):
386 84
            days += self.leapdays
387 84
        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 84
        if self.weekday:
394 84
            weekday, nth = self.weekday.weekday, self.weekday.n or 1
395 84
            jumpdays = (abs(nth) - 1) * 7
396 84
            if nth > 0:
397 84
                jumpdays += (7 - ret.weekday() + weekday) % 7
398
            else:
399 84
                jumpdays += (ret.weekday() - weekday) % 7
400 84
                jumpdays *= -1
401 84
            ret += datetime.timedelta(days=jumpdays)
402 84
        return ret
403

404 84
    def __radd__(self, other):
405 84
        return self.__add__(other)
406

407 84
    def __rsub__(self, other):
408 84
        return self.__neg__().__radd__(other)
409

410 84
    def __sub__(self, other):
411 84
        if not isinstance(other, relativedelta):
412 84
            return NotImplemented   # In case the other object defines __rsub__
413 84
        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))
438

439 84
    def __abs__(self):
440 84
        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)
456

457 84
    def __neg__(self):
458 84
        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)
474

475 84
    def __bool__(self):
476 84
        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 84
    __nonzero__ = __bool__
494

495 84
    def __mul__(self, other):
496 84
        try:
497 84
            f = float(other)
498 84
        except TypeError:
499 84
            return NotImplemented
500

501 84
        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)
517

518 84
    __rmul__ = __mul__
519

520 84
    def __eq__(self, other):
521 84
        if not isinstance(other, relativedelta):
522 84
            return NotImplemented
523 84
        if self.weekday or other.weekday:
524 84
            if not self.weekday or not other.weekday:
525 84
                return False
526 84
            if self.weekday.weekday != other.weekday.weekday:
527 84
                return False
528 84
            n1, n2 = self.weekday.n, other.weekday.n
529 84
            if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)):
530 84
                return False
531 84
        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)
546

547 84
    def __hash__(self):
548 84
        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
        ))
566

567 84
    def __ne__(self, other):
568 84
        return not self.__eq__(other)
569

570 84
    def __div__(self, other):
571 84
        try:
572 84
            reciprocal = 1 / float(other)
573 84
        except TypeError:
574 84
            return NotImplemented
575

576 84
        return self.__mul__(reciprocal)
577

578 84
    __truediv__ = __div__
579

580 84
    def __repr__(self):
581 84
        l = []
582 84
        for attr in ["years", "months", "days", "leapdays",
583
                     "hours", "minutes", "seconds", "microseconds"]:
584 84
            value = getattr(self, attr)
585 84
            if value:
586 84
                l.append("{attr}={value:+g}".format(attr=attr, value=value))
587 84
        for attr in ["year", "month", "day", "weekday",
588
                     "hour", "minute", "second", "microsecond"]:
589 84
            value = getattr(self, attr)
590 84
            if value is not None:
591 84
                l.append("{attr}={value}".format(attr=attr, value=repr(value)))
592 84
        return "{classname}({attrs})".format(classname=self.__class__.__name__,
593
                                             attrs=", ".join(l))
594

595

596 84
def _sign(x):
597 84
    return int(copysign(1, x))
598

599
# vim:ts=4:sw=4:et

Read our documentation on viewing source code .

Loading