Repository: dateutil/dateutil (Python)

Issue:
relativedelta does not implement __lt__
`relativedelta(months=+2) < relativedelta(months=+6)`
should return be True but gets evaluated to False.

I see that relative delta does not implement __lt__. Is there any reason for that?



Hints:
I'm not sure I agree that comparison should be implemented such that it returns `True` or `False` in that way, since I don't think it really makes sense to compare to `relativedelta` objects like that. Consider, what should be the answers to the following comparisons?

```python
relativedelta(months=1) < relativedelta(days=30)
relativedelta(months=5) < relativedelta(day=9, hour=2)
relativedelta(weekday=MO) < relativedelta(weekday=FR)
```

Basically, because `relativedelta` doesn't really map to any fixed time period, it doesn't make much sense to directly compare them.

@binnisb What version of `dateutil` and `python` are you using, specifically? I would expect `relativedelta` to raise a `TypeError`, not return `False`, and that's what I get with Python 3.6.0, dateutil 2.6.0:

```python
>>> from dateutil import __version__
>>> __version__
'2.6.0'
>>> from dateutil.relativedelta import relativedelta
>>> relativedelta(months=3) < relativedelta(months=4)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-2-cf9dd5f49f4d> in <module>()
----> 1 relativedelta(months=3) < relativedelta(months=4)

TypeError: '<' not supported between instances of 'relativedelta' and 'relativedelta'
```

My version of dateutil is a bit older:
```
In[35]: from dateutil import __version__
In[36]: __version__
Out[37]: '2.4.1'
```
Using Python Anaconda version 2.7.13

@pganssle I still feel you should be able to compare them. First, you can compare datetime.timedelta, second you can do:
```
  ref_date = datetime.date(2001,1,1)
  if ref_date + relativedelta(dt1=dates[-2], dt2=dates[-1]) < ref_date + self.period:
    print "yay"
  else:
    print "nay"
```
where self.period is a relativedelta you have a comparison, and mathematically you should be able to cancel out the ref_date to compare. Therefore there must be an ordering to the relativedeltas? Or am I misunderstanding something? Maybe I am missing the point since I only use the relative deltas as a better datetime.timedelta :)

However, since newer versions of dateutil throw a type error when comparing two relativedelta values, that solves the problem of returning False when it should obviously return True, so I don't really see this as an issue anymore. 
Update to my previous comment. I uninstalled the conda version of dateutil and installed through pip. Now it seems to have my expected behaviour, at least in the simple sense:
```python
In[8]: from dateutil import __version__
In[9]: __version__
Out[9]: '2.6.0'
In[10]: from dateutil.relativedelta import relativedelta
In[11]: relativedelta(months=3) < relativedelta(months=4)
Out[11]: True
In[12]: relativedelta(months=+6) < relativedelta(months=+5)
Out[12]: False
```

@pganssle I am not getting the type error you got above.
Can we at least have a way to know a delta is positive? I haven't found a good one yet. Implementing `__abs__` would probably be enough.

(I can work on a PR if you think it's a good idea)
@pferreir As I said above, `relativedelta` is not inherently positive or negative or larger or smaller than another `relativedelta`. Consider:

```python
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta

TD_ZERO = timedelta(0)

def diff_is_negative(dt, delta):
    return (dt + delta) - dt < TD_ZERO

rd = relativedelta(month=3)
print(diff_is_negative(datetime(2016, 4, 1), rd))    # True
print(diff_is_negative(datetime(2016, 2, 1), rd))    # False
```

I don't think there will be any intuitive semantics for comparing `relativedelta` objects *in general* that allows them to be meaningfully categorized as bigger or smaller than one another. There are some subsets of `relativedelta` for which comparison is meaningful (e.g. `relativedelta(months=1)` is unambiguously smaller than `relativedelta(months=2)`), but there are a lot of edge cases (e.g. `relativedelta(months=1)` vs. `relativedelta(days=30)`) where the difference in magnitude depends on the context.

Presumably if you're looking for a way to compare `relativedelta` objects, you have some specific semantics in mind. Likely those semantics won't generalize and so shouldn't be implemented in `dateutil`, but there's nothing stopping you from subclassing `relativedelta` to implement your own comparison semantics, e.g.:

```python
from dateutil.relativedelta import relativedelta

class RelativeDeltaMonthComparisons(relativedelta):
    """ A comparable version of `relativedelta` that compares less
        than based on month offset """
    def __lt__(self, other):
        return self.months < other.months
```
@pganssle, I get that, it makes perfect sense. However, would it really hurt implementing `__abs__`? Only the actual "delta" part would change sign. You're already defining `__neg__`, which does something very similar.
I'd have to think about it in terms of implementing `__abs__`. Given that we implement `__neg__` already, that does seem like it would have clear enough semantics. The main thing I'm not certain is obvious would be what would happen with something like `relativedelta(weekday=MO(-1))`. I'm guessing it's unaffected by `__neg__` and should also be unaffected by `__abs__`, but that may be surprising.

Patch:
diff --git a/dateutil/relativedelta.py b/dateutil/relativedelta.py
--- a/dateutil/relativedelta.py
+++ b/dateutil/relativedelta.py
@@ -422,6 +422,24 @@ def __sub__(self, other):
                                           is not None else
                                           other.microsecond))
 
+    def __abs__(self):
+        return self.__class__(years=abs(self.years),
+                              months=abs(self.months),
+                              days=abs(self.days),
+                              hours=abs(self.hours),
+                              minutes=abs(self.minutes),
+                              seconds=abs(self.seconds),
+                              microseconds=abs(self.microseconds),
+                              leapdays=self.leapdays,
+                              year=self.year,
+                              month=self.month,
+                              day=self.day,
+                              weekday=self.weekday,
+                              hour=self.hour,
+                              minute=self.minute,
+                              second=self.second,
+                              microsecond=self.microsecond)
+
     def __neg__(self):
         return self.__class__(years=-self.years,
                              months=-self.months,


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
@@ -245,6 +245,20 @@ def testBoolean(self):
         self.assertFalse(relativedelta(days=0))
         self.assertTrue(relativedelta(days=1))
 
+    def testAbsoluteValueNegative(self):
+        rd_base = relativedelta(years=-1, months=-5, days=-2, hours=-3,
+                                minutes=-5, seconds=-2, microseconds=-12)
+        rd_expected = relativedelta(years=1, months=5, days=2, hours=3,
+                                    minutes=5, seconds=2, microseconds=12)
+        self.assertEqual(abs(rd_base), rd_expected)
+
+    def testAbsoluteValuePositive(self):
+        rd_base = relativedelta(years=1, months=5, days=2, hours=3,
+                                minutes=5, seconds=2, microseconds=12)
+        rd_expected = rd_base
+
+        self.assertEqual(abs(rd_base), rd_expected)
+
     def testComparison(self):
         d1 = relativedelta(years=1, months=1, days=1, leapdays=0, hours=1,
                            minutes=1, seconds=1, microseconds=1)