Repository: dateutil/dateutil (Python)

Issue:
Creating a relativedelta instance with floating point value in integer quantity for months or years
I ran into an issue when creating a `relativedelta` instance with a floating point argument for `months` or `years` (albeit containing an integer quantity) :

```python
>>> import datetime
>>> from dateutil.relativedelta import relativedelta

>>> relativedelta(months=3.5)                # This raises an error, as expected
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "dateutil/relativedelta.py", line 100, in __init__
    raise ValueError("Non-integer years and months are "
ValueError: Non-integer years and months are ambiguous and not currently supported.

>>> relativedelta(months=3)                                # This is fine
relativedelta(months=+3)

>>> relativedelta(months=float(3))                         # So is this, but...
relativedelta(months=+3)

>>> datetime.date.today() + relativedelta(months=float(3)) # This doesn't work
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "dateutil/relativedelta.py", line 391, in __radd__
    return self.__add__(other)
  File "dateutil/relativedelta.py", line 363, in __add__
    day = min(calendar.monthrange(year, month)[1],
  File "/usr/lib/python2.7/calendar.py", line 121, in monthrange
    day1 = weekday(year, month, 1)
  File "/usr/lib/python2.7/calendar.py", line 113, in weekday
    return datetime.date(year, month, day).weekday()
TypeError: integer argument expected, got float
```

What is the expected behaviour? Should non-integer values in integer quantities raise an error? [This check](https://github.com/dateutil/dateutil/blob/master/dateutil/relativedelta.py#L98) leads me to believe this is not the case:

```python
# Check for non-integer values in integer-only quantities
if any(x is not None and x != int(x) for x in (years, months)):
    raise ValueError("Non-integer years and months are "
                     "ambiguous and not currently supported.")
```

Or should `months` and `years` arguments be converted to int internally when given as non-int? It doesn't seem quite right to me that `calendar.monthrange()` could be allowed to fail when the check for non-integer values lets a floating-point value through if it contains an integer quantity.



Hints:
@arouanet Ah, good catch. I guess the check is more strictly enforced in `__radd__`. We should modify the constructor to coerce floats to ints if it can be done losslessly.

Patch:
diff --git a/dateutil/relativedelta.py b/dateutil/relativedelta.py
--- a/dateutil/relativedelta.py
+++ b/dateutil/relativedelta.py
@@ -34,7 +34,7 @@ class relativedelta(object):
 
         year, month, day, hour, minute, second, microsecond:
             Absolute information (argument is singular); adding or subtracting a
-            relativedelta with absolute information does not perform an aritmetic
+            relativedelta with absolute information does not perform an arithmetic
             operation, but rather REPLACES the corresponding value in the
             original datetime with the value(s) in relativedelta.
 
@@ -95,11 +95,6 @@ def __init__(self, dt1=None, dt2=None,
                  yearday=None, nlyearday=None,
                  hour=None, minute=None, second=None, microsecond=None):
 
-        # Check for non-integer values in integer-only quantities
-        if any(x is not None and x != int(x) for x in (years, months)):
-            raise ValueError("Non-integer years and months are "
-                             "ambiguous and not currently supported.")
-
         if dt1 and dt2:
             # datetime is a subclass of date. So both must be date
             if not (isinstance(dt1, datetime.date) and
@@ -159,9 +154,14 @@ def __init__(self, dt1=None, dt2=None,
             self.seconds = delta.seconds + delta.days * 86400
             self.microseconds = delta.microseconds
         else:
+            # Check for non-integer values in integer-only quantities
+            if any(x is not None and x != int(x) for x in (years, months)):
+                raise ValueError("Non-integer years and months are "
+                                 "ambiguous and not currently supported.")
+
             # Relative information
-            self.years = years
-            self.months = months
+            self.years = int(years)
+            self.months = int(months)
             self.days = days + weeks * 7
             self.leapdays = leapdays
             self.hours = hours


Test patch:
diff --git a/dateutil/test/test_relativedelta.py b/dateutil/test/test_relativedelta.py
--- a/dateutil/test/test_relativedelta.py
+++ b/dateutil/test/test_relativedelta.py
@@ -203,6 +203,31 @@ def testAdditionUnsupportedType(self):
         # For unsupported types that define their own comparators, etc.
         self.assertIs(relativedelta(days=1) + NotAValue, NotAValue)
 
+    def testAdditionFloatValue(self):
+        self.assertEqual(datetime(2000, 1, 1) + relativedelta(days=float(1)),
+                         datetime(2000, 1, 2))
+        self.assertEqual(datetime(2000, 1, 1) + relativedelta(months=float(1)),
+                         datetime(2000, 2, 1))
+        self.assertEqual(datetime(2000, 1, 1) + relativedelta(years=float(1)),
+                         datetime(2001, 1, 1))
+
+    def testAdditionFloatFractionals(self):
+        self.assertEqual(datetime(2000, 1, 1, 0) +
+                         relativedelta(days=float(0.5)),
+                         datetime(2000, 1, 1, 12))
+        self.assertEqual(datetime(2000, 1, 1, 0, 0) +
+                         relativedelta(hours=float(0.5)),
+                         datetime(2000, 1, 1, 0, 30))
+        self.assertEqual(datetime(2000, 1, 1, 0, 0, 0) +
+                         relativedelta(minutes=float(0.5)),
+                         datetime(2000, 1, 1, 0, 0, 30))
+        self.assertEqual(datetime(2000, 1, 1, 0, 0, 0, 0) +
+                         relativedelta(seconds=float(0.5)),
+                         datetime(2000, 1, 1, 0, 0, 0, 500000))
+        self.assertEqual(datetime(2000, 1, 1, 0, 0, 0, 0) +
+                         relativedelta(microseconds=float(500000.25)),
+                         datetime(2000, 1, 1, 0, 0, 0, 500000))
+
     def testSubtraction(self):
         self.assertEqual(relativedelta(days=10) -
                          relativedelta(years=1, months=2, days=3, hours=4,