Repository: obspy/obspy

Issue:
Error when comparing obspy.core.utcdatetime.UTCDateTime and datetime.datetime objects
It seems that comparing an UTCDateTime and a datetime object always return False, no matter the actual date values. The following was tested on Linux Redhat 6.4 with Python 2.7.10 and obspy 1.0.2:

```
>>> from obspy.core.utcdatetime import UTCDateTime
>>> from datetime import datetime
>>> a = datetime(1990, 1, 1, 0, 0, 0)
>>> e = UTCDateTime(2000, 1, 2, 1, 39, 37)
>>>
>>> a < e
False
>>> e < a
False
>>> a <= e
False
>>> e <= a
False
>>> a > e
False
>>> e > a
False
>>> a >= e
False
>>> e >= a
False
>>> a == e
False
>>> e == a
False
```


Discussion:
Further investigation shows that in all UTCDateTime comparing methods (`__eq__`, `__ne__`, `__lt__`, `__le__`, `__gt__`, `__ge__`), the "other" date is converted to float using`float(other)`. When this other date is a datetime object, this raises a `TypeError` that is catched silently, and makes the method return with `False`.

I don't know exactly why this silent catch was added though.
Yea - we could easily make this more general by replacing the `self.timestamp - float(other)` in all the comparison methods with `(self - UTCDateTime(other)).timestamp` - then all the comparison methods will work with everything that can be converted to a `UTCDateTime` object.

This is definitely convenient but maybe a bit much "magic" from a design perspective? I'm still for it though. Other thoughts?
I think it requires a careful approach, as it could cause performance issues. This is the kind of method that might be intensely used, for instance when sorting a huge list of dates or a dated objects.

Maybe it would be better to convert the other date only in the except block ? Something like

```
def __lt__(self, other):
        """
        [Same documentation]
        """
        try:
            return round(self.timestamp - float(other), self.__precision) < 0
        except (TypeError):
            return self.__lt__(UTCDateTime(other)) 
        except (ValueError):
            return False
```
True - it would be quite a bit slower if `other` is already a float or a `UTCDateTime` object (other cases will require conversion to a `UTCDateTime` object in any case). But then we are talking about really small numbers so I'm not sure it really matters for anything practical.

```python
In [1]: from obspy import UTCDateTime

In [2]: dt = UTCDateTime()

In [3]: %timeit UTCDateTime(dt)
The slowest run took 6.54 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 1.84 µs per loop

In [4]: %timeit float(dt)
The slowest run took 24.84 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 510 ns per loop

In [5]: ts = dt.timestamp

In [6]: %timeit UTCDateTime(ts)
The slowest run took 9.17 times longer than the fastest. This could mean that an intermediate result is being cached.
100000 loops, best of 3: 2.25 µs per loop
```
This is also related to #975 which should be resolved before this issue here can be attacked.
this needs a rewrite anyway - comparison should be done on the nanoseconds integer value if its an UTCDateTime object - and we need special cases for float timestamps or datetime objects

so I guess we have to do a few isinstance() checks ...


Resolving patch:
diff --git a/obspy/core/utcdatetime.py b/obspy/core/utcdatetime.py
--- a/obspy/core/utcdatetime.py
+++ b/obspy/core/utcdatetime.py
@@ -941,7 +941,7 @@ def __str__(self):
             dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second,
             ns[2:self.precision + 2])
 
-    def _repr_pretty_(self, p, cycle):
+    def _repr_pretty_(self, p, cycle):  # @UnusedVariable
         p.text(str(self))
 
     def __unicode__(self):
@@ -983,10 +983,13 @@ def __eq__(self, other):
         >>> t1 == t2
         False
         """
-        try:
+        if isinstance(other, UTCDateTime):
+            return round((self._ns - other._ns) / 1e9, self.__precision) == 0
+        elif isinstance(other, float) or isinstance(other, int):
             return round(self.timestamp - float(other), self.__precision) == 0
-        except (TypeError, ValueError):
-            return False
+        elif isinstance(other, datetime.datetime):
+            return self.datetime == other
+        return False
 
     def __ne__(self, other):
         """
@@ -1040,10 +1043,13 @@ def __lt__(self, other):
         >>> t1 < t2
         True
         """
-        try:
+        if isinstance(other, UTCDateTime):
+            return round((self._ns - other._ns) / 1e9, self.__precision) < 0
+        elif isinstance(other, float) or isinstance(other, int):
             return round(self.timestamp - float(other), self.__precision) < 0
-        except (TypeError, ValueError):
-            return False
+        elif isinstance(other, datetime.datetime):
+            return self.datetime < other
+        return False
 
     def __le__(self, other):
         """
@@ -1070,10 +1076,13 @@ def __le__(self, other):
         >>> t1 <= t2
         False
         """
-        try:
+        if isinstance(other, UTCDateTime):
+            return round((self._ns - other._ns) / 1e9, self.__precision) <= 0
+        elif isinstance(other, float) or isinstance(other, int):
             return round(self.timestamp - float(other), self.__precision) <= 0
-        except (TypeError, ValueError):
-            return False
+        elif isinstance(other, datetime.datetime):
+            return self.datetime <= other
+        return False
 
     def __gt__(self, other):
         """
@@ -1100,10 +1109,13 @@ def __gt__(self, other):
         >>> t1 > t2
         True
         """
-        try:
+        if isinstance(other, UTCDateTime):
+            return round((self._ns - other._ns) / 1e9, self.__precision) > 0
+        elif isinstance(other, float) or isinstance(other, int):
             return round(self.timestamp - float(other), self.__precision) > 0
-        except (TypeError, ValueError):
-            return False
+        elif isinstance(other, datetime.datetime):
+            return self.datetime > other
+        return False
 
     def __ge__(self, other):
         """
@@ -1130,10 +1142,13 @@ def __ge__(self, other):
         >>> t1 >= t2
         False
         """
-        try:
+        if isinstance(other, UTCDateTime):
+            return round((self._ns - other._ns) / 1e9, self.__precision) >= 0
+        elif isinstance(other, float) or isinstance(other, int):
             return round(self.timestamp - float(other), self.__precision) >= 0
-        except (TypeError, ValueError):
-            return False
+        elif isinstance(other, datetime.datetime):
+            return self.datetime >= other
+        return False
 
     def __repr__(self):
         """
diff --git a/obspy/io/mseed/util.py b/obspy/io/mseed/util.py
--- a/obspy/io/mseed/util.py
+++ b/obspy/io/mseed/util.py
@@ -9,7 +9,6 @@
 
 import collections
 import ctypes as C
-import math
 import os
 import sys
 import warnings
@@ -815,8 +814,8 @@ def _convert_datetime_to_mstime(dt):
 
     :param dt: obspy.util.UTCDateTime object.
     """
-    _fsec, _sec = math.modf(dt.timestamp)
-    return int(round(_fsec * HPTMODULUS)) + int(_sec * HPTMODULUS)
+    rest = (dt._ns % 10**3) >= 500 and 1 or 0
+    return dt._ns // 10**3 + rest
 
 
 def _convert_mstime_to_datetime(timestring):
@@ -825,7 +824,7 @@ def _convert_mstime_to_datetime(timestring):
 
     :param timestamp: MiniSEED timestring (Epoch time string in ms).
     """
-    return UTCDateTime(timestring / HPTMODULUS)
+    return UTCDateTime(ns=int(round(timestring * 10**3)))
 
 
 def _unpack_steim_1(data, npts, swapflag=0, verbose=0):


Test patch:
diff --git a/obspy/core/tests/test_utcdatetime.py b/obspy/core/tests/test_utcdatetime.py
--- a/obspy/core/tests/test_utcdatetime.py
+++ b/obspy/core/tests/test_utcdatetime.py
@@ -1104,6 +1104,23 @@ def test_1008(self):
         self.assertEqual(str(UTCDateTime("9999-12-31T23:59:59.999999")),
                          "9999-12-31T23:59:59.999999Z")
 
+    def test_1652(self):
+        """
+        Comparing UTCDateTime and datetime.datetime objects - see #1652
+        """
+        a = datetime.datetime(1990, 1, 1, 0, 0)
+        e = UTCDateTime(2000, 1, 2, 1, 39, 37)
+        self.assertTrue(a < e)
+        self.assertFalse(a > e)
+        self.assertTrue(a <= e)
+        self.assertFalse(e <= a)
+        self.assertFalse(a > e)
+        self.assertTrue(e > a)
+        self.assertFalse(a >= e)
+        self.assertTrue(e >= a)
+        self.assertFalse(a == e)
+        self.assertFalse(e == a)
+
 
 def suite():
     return unittest.makeSuite(UTCDateTimeTestCase, 'test')
diff --git a/obspy/io/mseed/tests/test_mseed_util.py b/obspy/io/mseed/tests/test_mseed_util.py
--- a/obspy/io/mseed/tests/test_mseed_util.py
+++ b/obspy/io/mseed/tests/test_mseed_util.py
@@ -134,6 +134,32 @@ def test_convert_datetime(self):
         self.assertEqual(timestring, util._convert_datetime_to_mstime(
             util._convert_mstime_to_datetime(timestring)))
 
+    def test_convert_datetime2(self):
+        """
+        Some failing test discovered in #1670
+        """
+        # 1
+        dt = UTCDateTime(ns=1487021451935737333)
+        self.assertEqual(str(dt), "2017-02-13T21:30:51.935737Z")
+        self.assertEqual(util._convert_datetime_to_mstime(dt),
+                         1487021451935737)
+        self.assertEqual(dt, util._convert_mstime_to_datetime(
+            util._convert_datetime_to_mstime(dt)))
+        # 2
+        dt = UTCDateTime(ns=1487021451935736449)
+        self.assertEqual(str(dt), "2017-02-13T21:30:51.935736Z")
+        self.assertEqual(util._convert_datetime_to_mstime(dt),
+                         1487021451935736)
+        self.assertEqual(dt, util._convert_mstime_to_datetime(
+            util._convert_datetime_to_mstime(dt)))
+        # 3
+        dt = UTCDateTime(ns=1487021451935736501)
+        self.assertEqual(str(dt), "2017-02-13T21:30:51.935737Z")
+        self.assertEqual(util._convert_datetime_to_mstime(dt),
+                         1487021451935737)
+        self.assertEqual(dt, util._convert_mstime_to_datetime(
+            util._convert_datetime_to_mstime(dt)))
+
     def test_get_record_information(self):
         """
         Tests the util._get_ms_file_info method with known values.