1# -*- coding: utf-8 -*-
2from __future__ import unicode_literals
3from ._common import unittest, WarningTestMixin, NotAValue
4
5import calendar
6from datetime import datetime, date, timedelta
7
8from dateutil.relativedelta import *
9
10
11class RelativeDeltaTest(WarningTestMixin, unittest.TestCase):
12    now = datetime(2003, 9, 17, 20, 54, 47, 282310)
13    today = date(2003, 9, 17)
14
15    def testInheritance(self):
16        # Ensure that relativedelta is inheritance-friendly.
17        class rdChildClass(relativedelta):
18            pass
19
20        ccRD = rdChildClass(years=1, months=1, days=1, leapdays=1, weeks=1,
21                            hours=1, minutes=1, seconds=1, microseconds=1)
22
23        rd = relativedelta(years=1, months=1, days=1, leapdays=1, weeks=1,
24                           hours=1, minutes=1, seconds=1, microseconds=1)
25
26        self.assertEqual(type(ccRD + rd), type(ccRD),
27                         msg='Addition does not inherit type.')
28
29        self.assertEqual(type(ccRD - rd), type(ccRD),
30                         msg='Subtraction does not inherit type.')
31
32        self.assertEqual(type(-ccRD), type(ccRD),
33                         msg='Negation does not inherit type.')
34
35        self.assertEqual(type(ccRD * 5.0), type(ccRD),
36                         msg='Multiplication does not inherit type.')
37
38        self.assertEqual(type(ccRD / 5.0), type(ccRD),
39                         msg='Division does not inherit type.')
40
41    def testMonthEndMonthBeginning(self):
42        self.assertEqual(relativedelta(datetime(2003, 1, 31, 23, 59, 59),
43                                       datetime(2003, 3, 1, 0, 0, 0)),
44                         relativedelta(months=-1, seconds=-1))
45
46        self.assertEqual(relativedelta(datetime(2003, 3, 1, 0, 0, 0),
47                                       datetime(2003, 1, 31, 23, 59, 59)),
48                         relativedelta(months=1, seconds=1))
49
50    def testMonthEndMonthBeginningLeapYear(self):
51        self.assertEqual(relativedelta(datetime(2012, 1, 31, 23, 59, 59),
52                                       datetime(2012, 3, 1, 0, 0, 0)),
53                         relativedelta(months=-1, seconds=-1))
54
55        self.assertEqual(relativedelta(datetime(2003, 3, 1, 0, 0, 0),
56                                       datetime(2003, 1, 31, 23, 59, 59)),
57                         relativedelta(months=1, seconds=1))
58
59    def testNextMonth(self):
60        self.assertEqual(self.now+relativedelta(months=+1),
61                         datetime(2003, 10, 17, 20, 54, 47, 282310))
62
63    def testNextMonthPlusOneWeek(self):
64        self.assertEqual(self.now+relativedelta(months=+1, weeks=+1),
65                         datetime(2003, 10, 24, 20, 54, 47, 282310))
66
67    def testNextMonthPlusOneWeek10am(self):
68        self.assertEqual(self.today +
69                         relativedelta(months=+1, weeks=+1, hour=10),
70                         datetime(2003, 10, 24, 10, 0))
71
72    def testNextMonthPlusOneWeek10amDiff(self):
73        self.assertEqual(relativedelta(datetime(2003, 10, 24, 10, 0),
74                                       self.today),
75                         relativedelta(months=+1, days=+7, hours=+10))
76
77    def testOneMonthBeforeOneYear(self):
78        self.assertEqual(self.now+relativedelta(years=+1, months=-1),
79                         datetime(2004, 8, 17, 20, 54, 47, 282310))
80
81    def testMonthsOfDiffNumOfDays(self):
82        self.assertEqual(date(2003, 1, 27)+relativedelta(months=+1),
83                         date(2003, 2, 27))
84        self.assertEqual(date(2003, 1, 31)+relativedelta(months=+1),
85                         date(2003, 2, 28))
86        self.assertEqual(date(2003, 1, 31)+relativedelta(months=+2),
87                         date(2003, 3, 31))
88
89    def testMonthsOfDiffNumOfDaysWithYears(self):
90        self.assertEqual(date(2000, 2, 28)+relativedelta(years=+1),
91                         date(2001, 2, 28))
92        self.assertEqual(date(2000, 2, 29)+relativedelta(years=+1),
93                         date(2001, 2, 28))
94
95        self.assertEqual(date(1999, 2, 28)+relativedelta(years=+1),
96                         date(2000, 2, 28))
97        self.assertEqual(date(1999, 3, 1)+relativedelta(years=+1),
98                         date(2000, 3, 1))
99        self.assertEqual(date(1999, 3, 1)+relativedelta(years=+1),
100                         date(2000, 3, 1))
101
102        self.assertEqual(date(2001, 2, 28)+relativedelta(years=-1),
103                         date(2000, 2, 28))
104        self.assertEqual(date(2001, 3, 1)+relativedelta(years=-1),
105                         date(2000, 3, 1))
106
107    def testNextFriday(self):
108        self.assertEqual(self.today+relativedelta(weekday=FR),
109                         date(2003, 9, 19))
110
111    def testNextFridayInt(self):
112        self.assertEqual(self.today+relativedelta(weekday=calendar.FRIDAY),
113                         date(2003, 9, 19))
114
115    def testLastFridayInThisMonth(self):
116        self.assertEqual(self.today+relativedelta(day=31, weekday=FR(-1)),
117                         date(2003, 9, 26))
118
119    def testNextWednesdayIsToday(self):
120        self.assertEqual(self.today+relativedelta(weekday=WE),
121                         date(2003, 9, 17))
122
123    def testNextWenesdayNotToday(self):
124        self.assertEqual(self.today+relativedelta(days=+1, weekday=WE),
125                         date(2003, 9, 24))
126
127    def test15thISOYearWeek(self):
128        self.assertEqual(date(2003, 1, 1) +
129                         relativedelta(day=4, weeks=+14, weekday=MO(-1)),
130                         date(2003, 4, 7))
131
132    def testMillenniumAge(self):
133        self.assertEqual(relativedelta(self.now, date(2001, 1, 1)),
134                         relativedelta(years=+2, months=+8, days=+16,
135                                       hours=+20, minutes=+54, seconds=+47,
136                                       microseconds=+282310))
137
138    def testJohnAge(self):
139        self.assertEqual(relativedelta(self.now,
140                                       datetime(1978, 4, 5, 12, 0)),
141                         relativedelta(years=+25, months=+5, days=+12,
142                                       hours=+8, minutes=+54, seconds=+47,
143                                       microseconds=+282310))
144
145    def testJohnAgeWithDate(self):
146        self.assertEqual(relativedelta(self.today,
147                                       datetime(1978, 4, 5, 12, 0)),
148                         relativedelta(years=+25, months=+5, days=+11,
149                                       hours=+12))
150
151    def testYearDay(self):
152        self.assertEqual(date(2003, 1, 1)+relativedelta(yearday=260),
153                         date(2003, 9, 17))
154        self.assertEqual(date(2002, 1, 1)+relativedelta(yearday=260),
155                         date(2002, 9, 17))
156        self.assertEqual(date(2000, 1, 1)+relativedelta(yearday=260),
157                         date(2000, 9, 16))
158        self.assertEqual(self.today+relativedelta(yearday=261),
159                         date(2003, 9, 18))
160
161    def testYearDayBug(self):
162        # Tests a problem reported by Adam Ryan.
163        self.assertEqual(date(2010, 1, 1)+relativedelta(yearday=15),
164                         date(2010, 1, 15))
165
166    def testNonLeapYearDay(self):
167        self.assertEqual(date(2003, 1, 1)+relativedelta(nlyearday=260),
168                         date(2003, 9, 17))
169        self.assertEqual(date(2002, 1, 1)+relativedelta(nlyearday=260),
170                         date(2002, 9, 17))
171        self.assertEqual(date(2000, 1, 1)+relativedelta(nlyearday=260),
172                         date(2000, 9, 17))
173        self.assertEqual(self.today+relativedelta(yearday=261),
174                         date(2003, 9, 18))
175
176    def testAddition(self):
177        self.assertEqual(relativedelta(days=10) +
178                         relativedelta(years=1, months=2, days=3, hours=4,
179                                       minutes=5, microseconds=6),
180                         relativedelta(years=1, months=2, days=13, hours=4,
181                                       minutes=5, microseconds=6))
182
183    def testAdditionToDatetime(self):
184        self.assertEqual(datetime(2000, 1, 1) + relativedelta(days=1),
185                         datetime(2000, 1, 2))
186
187    def testRightAdditionToDatetime(self):
188        self.assertEqual(relativedelta(days=1) + datetime(2000, 1, 1),
189                         datetime(2000, 1, 2))
190
191    def testAdditionInvalidType(self):
192        with self.assertRaises(TypeError):
193            relativedelta(days=3) + 9
194
195    def testAdditionUnsupportedType(self):
196        # For unsupported types that define their own comparators, etc.
197        self.assertIs(relativedelta(days=1) + NotAValue, NotAValue)
198
199    def testSubtraction(self):
200        self.assertEqual(relativedelta(days=10) -
201                         relativedelta(years=1, months=2, days=3, hours=4,
202                                       minutes=5, microseconds=6),
203                         relativedelta(years=-1, months=-2, days=7, hours=-4,
204                                       minutes=-5, microseconds=-6))
205
206    def testRightSubtractionFromDatetime(self):
207        self.assertEqual(datetime(2000, 1, 2) - relativedelta(days=1),
208                         datetime(2000, 1, 1))
209
210    def testSubractionWithDatetime(self):
211        self.assertRaises(TypeError, lambda x, y: x - y,
212                          (relativedelta(days=1), datetime(2000, 1, 1)))
213
214    def testSubtractionInvalidType(self):
215        with self.assertRaises(TypeError):
216            relativedelta(hours=12) - 14
217
218    def testSubtractionUnsupportedType(self):
219        self.assertIs(relativedelta(days=1) + NotAValue, NotAValue)
220
221    def testMultiplication(self):
222        self.assertEqual(datetime(2000, 1, 1) + relativedelta(days=1) * 28,
223                         datetime(2000, 1, 29))
224        self.assertEqual(datetime(2000, 1, 1) + 28 * relativedelta(days=1),
225                         datetime(2000, 1, 29))
226
227    def testMultiplicationUnsupportedType(self):
228        self.assertIs(relativedelta(days=1) * NotAValue, NotAValue)
229
230    def testDivision(self):
231        self.assertEqual(datetime(2000, 1, 1) + relativedelta(days=28) / 28,
232                         datetime(2000, 1, 2))
233
234    def testDivisionUnsupportedType(self):
235        self.assertIs(relativedelta(days=1) / NotAValue, NotAValue)
236
237    def testBoolean(self):
238        self.assertFalse(relativedelta(days=0))
239        self.assertTrue(relativedelta(days=1))
240
241    def testComparison(self):
242        d1 = relativedelta(years=1, months=1, days=1, leapdays=0, hours=1,
243                           minutes=1, seconds=1, microseconds=1)
244        d2 = relativedelta(years=1, months=1, days=1, leapdays=0, hours=1,
245                           minutes=1, seconds=1, microseconds=1)
246        d3 = relativedelta(years=1, months=1, days=1, leapdays=0, hours=1,
247                           minutes=1, seconds=1, microseconds=2)
248
249        self.assertEqual(d1, d2)
250        self.assertNotEqual(d1, d3)
251
252    def testInequalityTypeMismatch(self):
253        # Different type
254        self.assertFalse(relativedelta(year=1) == 19)
255
256    def testInequalityUnsupportedType(self):
257        self.assertIs(relativedelta(hours=3) == NotAValue, NotAValue)
258
259    def testInequalityWeekdays(self):
260        # Different weekdays
261        no_wday = relativedelta(year=1997, month=4)
262        wday_mo_1 = relativedelta(year=1997, month=4, weekday=MO(+1))
263        wday_mo_2 = relativedelta(year=1997, month=4, weekday=MO(+2))
264        wday_tu = relativedelta(year=1997, month=4, weekday=TU)
265
266        self.assertTrue(wday_mo_1 == wday_mo_1)
267
268        self.assertFalse(no_wday == wday_mo_1)
269        self.assertFalse(wday_mo_1 == no_wday)
270
271        self.assertFalse(wday_mo_1 == wday_mo_2)
272        self.assertFalse(wday_mo_2 == wday_mo_1)
273
274        self.assertFalse(wday_mo_1 == wday_tu)
275        self.assertFalse(wday_tu == wday_mo_1)
276
277    def testMonthOverflow(self):
278        self.assertEqual(relativedelta(months=273),
279                         relativedelta(years=22, months=9))
280
281    def testWeeks(self):
282        # Test that the weeks property is working properly.
283        rd = relativedelta(years=4, months=2, weeks=8, days=6)
284        self.assertEqual((rd.weeks, rd.days), (8, 8 * 7 + 6))
285
286        rd.weeks = 3
287        self.assertEqual((rd.weeks, rd.days), (3, 3 * 7 + 6))
288
289    def testRelativeDeltaRepr(self):
290        self.assertEqual(repr(relativedelta(years=1, months=-1, days=15)),
291                         'relativedelta(years=+1, months=-1, days=+15)')
292
293        self.assertEqual(repr(relativedelta(months=14, seconds=-25)),
294                         'relativedelta(years=+1, months=+2, seconds=-25)')
295
296        self.assertEqual(repr(relativedelta(month=3, hour=3, weekday=SU(3))),
297                         'relativedelta(month=3, weekday=SU(+3), hour=3)')
298
299    def testRelativeDeltaFractionalYear(self):
300        with self.assertRaises(ValueError):
301            relativedelta(years=1.5)
302
303    def testRelativeDeltaFractionalMonth(self):
304        with self.assertRaises(ValueError):
305            relativedelta(months=1.5)
306
307    def testRelativeDeltaFractionalAbsolutes(self):
308        # Fractional absolute values will soon be unsupported,
309        # check for the deprecation warning.
310        with self.assertWarns(DeprecationWarning):
311            relativedelta(year=2.86)
312
313        with self.assertWarns(DeprecationWarning):
314            relativedelta(month=1.29)
315
316        with self.assertWarns(DeprecationWarning):
317            relativedelta(day=0.44)
318
319        with self.assertWarns(DeprecationWarning):
320            relativedelta(hour=23.98)
321
322        with self.assertWarns(DeprecationWarning):
323            relativedelta(minute=45.21)
324
325        with self.assertWarns(DeprecationWarning):
326            relativedelta(second=13.2)
327
328        with self.assertWarns(DeprecationWarning):
329            relativedelta(microsecond=157221.93)
330
331    def testRelativeDeltaFractionalRepr(self):
332        rd = relativedelta(years=3, months=-2, days=1.25)
333
334        self.assertEqual(repr(rd),
335                         'relativedelta(years=+3, months=-2, days=+1.25)')
336
337        rd = relativedelta(hours=0.5, seconds=9.22)
338        self.assertEqual(repr(rd),
339                         'relativedelta(hours=+0.5, seconds=+9.22)')
340
341    def testRelativeDeltaFractionalWeeks(self):
342        # Equivalent to days=8, hours=18
343        rd = relativedelta(weeks=1.25)
344        d1 = datetime(2009, 9, 3, 0, 0)
345        self.assertEqual(d1 + rd,
346                         datetime(2009, 9, 11, 18))
347
348    def testRelativeDeltaFractionalDays(self):
349        rd1 = relativedelta(days=1.48)
350
351        d1 = datetime(2009, 9, 3, 0, 0)
352        self.assertEqual(d1 + rd1,
353                         datetime(2009, 9, 4, 11, 31, 12))
354
355        rd2 = relativedelta(days=1.5)
356        self.assertEqual(d1 + rd2,
357                         datetime(2009, 9, 4, 12, 0, 0))
358
359    def testRelativeDeltaFractionalHours(self):
360        rd = relativedelta(days=1, hours=12.5)
361        d1 = datetime(2009, 9, 3, 0, 0)
362        self.assertEqual(d1 + rd,
363                         datetime(2009, 9, 4, 12, 30, 0))
364
365    def testRelativeDeltaFractionalMinutes(self):
366        rd = relativedelta(hours=1, minutes=30.5)
367        d1 = datetime(2009, 9, 3, 0, 0)
368        self.assertEqual(d1 + rd,
369                         datetime(2009, 9, 3, 1, 30, 30))
370
371    def testRelativeDeltaFractionalSeconds(self):
372        rd = relativedelta(hours=5, minutes=30, seconds=30.5)
373        d1 = datetime(2009, 9, 3, 0, 0)
374        self.assertEqual(d1 + rd,
375                         datetime(2009, 9, 3, 5, 30, 30, 500000))
376
377    def testRelativeDeltaFractionalPositiveOverflow(self):
378        # Equivalent to (days=1, hours=14)
379        rd1 = relativedelta(days=1.5, hours=2)
380        d1 = datetime(2009, 9, 3, 0, 0)
381        self.assertEqual(d1 + rd1,
382                         datetime(2009, 9, 4, 14, 0, 0))
383
384        # Equivalent to (days=1, hours=14, minutes=45)
385        rd2 = relativedelta(days=1.5, hours=2.5, minutes=15)
386        d1 = datetime(2009, 9, 3, 0, 0)
387        self.assertEqual(d1 + rd2,
388                         datetime(2009, 9, 4, 14, 45))
389
390        # Carry back up - equivalent to (days=2, hours=2, minutes=0, seconds=1)
391        rd3 = relativedelta(days=1.5, hours=13, minutes=59.5, seconds=31)
392        self.assertEqual(d1 + rd3,
393                         datetime(2009, 9, 5, 2, 0, 1))
394
395    def testRelativeDeltaFractionalNegativeDays(self):
396        # Equivalent to (days=-1, hours=-1)
397        rd1 = relativedelta(days=-1.5, hours=11)
398        d1 = datetime(2009, 9, 3, 12, 0)
399        self.assertEqual(d1 + rd1,
400                         datetime(2009, 9, 2, 11, 0, 0))
401
402        # Equivalent to (days=-1, hours=-9)
403        rd2 = relativedelta(days=-1.25, hours=-3)
404        self.assertEqual(d1 + rd2,
405            datetime(2009, 9, 2, 3))
406
407    def testRelativeDeltaNormalizeFractionalDays(self):
408        # Equivalent to (days=2, hours=18)
409        rd1 = relativedelta(days=2.75)
410
411        self.assertEqual(rd1.normalized(), relativedelta(days=2, hours=18))
412
413        # Equvalent to (days=1, hours=11, minutes=31, seconds=12)
414        rd2 = relativedelta(days=1.48)
415
416        self.assertEqual(rd2.normalized(),
417            relativedelta(days=1, hours=11, minutes=31, seconds=12))
418
419    def testRelativeDeltaNormalizeFractionalDays(self):
420        # Equivalent to (hours=1, minutes=30)
421        rd1 = relativedelta(hours=1.5)
422
423        self.assertEqual(rd1.normalized(), relativedelta(hours=1, minutes=30))
424
425        # Equivalent to (hours=3, minutes=17, seconds=5, microseconds=100)
426        rd2 = relativedelta(hours=3.28472225)
427
428        self.assertEqual(rd2.normalized(),
429            relativedelta(hours=3, minutes=17, seconds=5, microseconds=100))
430
431    def testRelativeDeltaNormalizeFractionalMinutes(self):
432        # Equivalent to (minutes=15, seconds=36)
433        rd1 = relativedelta(minutes=15.6)
434
435        self.assertEqual(rd1.normalized(),
436            relativedelta(minutes=15, seconds=36))
437
438        # Equivalent to (minutes=25, seconds=20, microseconds=25000)
439        rd2 = relativedelta(minutes=25.33375)
440
441        self.assertEqual(rd2.normalized(),
442            relativedelta(minutes=25, seconds=20, microseconds=25000))
443
444    def testRelativeDeltaNormalizeFractionalSeconds(self):
445        # Equivalent to (seconds=45, microseconds=25000)
446        rd1 = relativedelta(seconds=45.025)
447        self.assertEqual(rd1.normalized(),
448            relativedelta(seconds=45, microseconds=25000))
449
450    def testRelativeDeltaFractionalPositiveOverflow(self):
451        # Equivalent to (days=1, hours=14)
452        rd1 = relativedelta(days=1.5, hours=2)
453        self.assertEqual(rd1.normalized(),
454            relativedelta(days=1, hours=14))
455
456        # Equivalent to (days=1, hours=14, minutes=45)
457        rd2 = relativedelta(days=1.5, hours=2.5, minutes=15)
458        self.assertEqual(rd2.normalized(),
459            relativedelta(days=1, hours=14, minutes=45))
460
461        # Carry back up - equivalent to:
462        # (days=2, hours=2, minutes=0, seconds=2, microseconds=3)
463        rd3 = relativedelta(days=1.5, hours=13, minutes=59.50045,
464                            seconds=31.473, microseconds=500003)
465        self.assertEqual(rd3.normalized(),
466            relativedelta(days=2, hours=2, minutes=0,
467                          seconds=2, microseconds=3))
468
469    def testRelativeDeltaFractionalNegativeOverflow(self):
470        # Equivalent to (days=-1)
471        rd1 = relativedelta(days=-0.5, hours=-12)
472        self.assertEqual(rd1.normalized(),
473            relativedelta(days=-1))
474
475        # Equivalent to (days=-1)
476        rd2 = relativedelta(days=-1.5, hours=12)
477        self.assertEqual(rd2.normalized(),
478            relativedelta(days=-1))
479
480        # Equivalent to (days=-1, hours=-14, minutes=-45)
481        rd3 = relativedelta(days=-1.5, hours=-2.5, minutes=-15)
482        self.assertEqual(rd3.normalized(),
483            relativedelta(days=-1, hours=-14, minutes=-45))
484
485        # Equivalent to (days=-1, hours=-14, minutes=+15)
486        rd4 = relativedelta(days=-1.5, hours=-2.5, minutes=45)
487        self.assertEqual(rd4.normalized(),
488            relativedelta(days=-1, hours=-14, minutes=+15))
489
490        # Carry back up - equivalent to:
491        # (days=-2, hours=-2, minutes=0, seconds=-2, microseconds=-3)
492        rd3 = relativedelta(days=-1.5, hours=-13, minutes=-59.50045,
493                            seconds=-31.473, microseconds=-500003)
494        self.assertEqual(rd3.normalized(),
495            relativedelta(days=-2, hours=-2, minutes=0,
496                          seconds=-2, microseconds=-3))
497
498    def testInvalidYearDay(self):
499        with self.assertRaises(ValueError):
500            relativedelta(yearday=367)
501
502    def testAddTimedeltaToUnpopulatedRelativedelta(self):
503        td = timedelta(
504            days=1,
505            seconds=1,
506            microseconds=1,
507            milliseconds=1,
508            minutes=1,
509            hours=1,
510            weeks=1
511        )
512
513        expected = relativedelta(
514            weeks=1,
515            days=1,
516            hours=1,
517            minutes=1,
518            seconds=1,
519            microseconds=1001
520        )
521
522        self.assertEqual(expected, relativedelta() + td)
523
524    def testAddTimedeltaToPopulatedRelativeDelta(self):
525        td = timedelta(
526            days=1,
527            seconds=1,
528            microseconds=1,
529            milliseconds=1,
530            minutes=1,
531            hours=1,
532            weeks=1
533        )
534
535        rd = relativedelta(
536            year=1,
537            month=1,
538            day=1,
539            hour=1,
540            minute=1,
541            second=1,
542            microsecond=1,
543            years=1,
544            months=1,
545            days=1,
546            weeks=1,
547            hours=1,
548            minutes=1,
549            seconds=1,
550            microseconds=1
551        )
552
553        expected = relativedelta(
554            year=1,
555            month=1,
556            day=1,
557            hour=1,
558            minute=1,
559            second=1,
560            microsecond=1,
561            years=1,
562            months=1,
563            weeks=2,
564            days=2,
565            hours=2,
566            minutes=2,
567            seconds=2,
568            microseconds=1002,
569        )
570
571        self.assertEqual(expected, rd + td)
572