Repository: pandas-dev/pandas
Pull request: Datetime mergeasof tolerance
- [x] closes #28098
- [x] tests 1 / 1
- [x] passes `black pandas`
- [x] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`
- [x] whatsnew entry


Problem statement:
merge_asof() tolerance cannot be datetime.timedelta 
#### Code Sample
```
import pandas as pd
import numpy as np
from datetime import timedelta

left = pd.DataFrame(
    list(zip([0, 5, 10, 15, 20, 25], [0, 1, 2, 3, 4, 5])),
    columns=["time", "left"],
)
left["time"] = pd.to_timedelta(left["time"], "ms")

right = pd.DataFrame(
    list(zip([0, 3, 9, 12, 15, 18], [0, 1, 2, 3, 4, 5])),
    columns=["time", "right"],
)
right["time"] = pd.to_timedelta(right["time"], "ms")

expected = pd.DataFrame(
    list(
        zip(
            [0, 5, 10, 15, 20, 25],
            [0, 1, 2, 3, 4, 5],
            [0, np.nan, 2, 4, np.nan, np.nan],
        )
    ),
    columns=["time", "left", "right"],
)
expected["time"] = pd.to_timedelta(expected["time"], "ms")
print(expected)

# this throws MergeError: incompatible tolerance <class 'datetime.timedelta'>, must be compat with type dtype('<m8[ns]')
result = pd.merge_asof(left, right, on="time", tolerance=timedelta(microseconds=1000), direction="nearest")

# this works!
# result = pd.merge_asof(left, right, on="time", tolerance=pd.Timedelta("1ms"), direction="nearest")

print(result)
```
#### Problem description
I can use pd.Timedelta as tolerance, but not datetime.timedelta. 

#### Expected Output
No error

#### Output of ``pd.show_versions()``

<details>
INSTALLED VERSIONS
------------------
commit           : None
python           : 3.7.3.final.0
python-bits      : 64
OS               : Linux
OS-release       : 4.19.0-5-amd64
machine          : x86_64
processor        : 
byteorder        : little
LC_ALL           : None
LANG             : en_US.UTF-8
LOCALE           : en_US.UTF-8

pandas           : 0.25.0+222.gdef01cf7b
numpy            : 1.17.0
pytz             : 2019.2
dateutil         : 2.8.0
pip              : 19.2.2
setuptools       : 41.2.0
Cython           : 0.29.13
pytest           : 5.1.1
hypothesis       : 4.33.1
sphinx           : 1.8.5
blosc            : 1.8.1
feather          : None
xlsxwriter       : 1.1.9
lxml.etree       : 4.4.1
html5lib         : 1.0.1
pymysql          : None
psycopg2         : None
jinja2           : 2.10.1
IPython          : 7.7.0
pandas_datareader: None
bs4              : 4.8.0
bottleneck       : 1.2.1
fastparquet      : 0.3.2
gcsfs            : None
lxml.etree       : 4.4.1
matplotlib       : 3.1.1
numexpr          : 2.7.0
odfpy            : None
openpyxl         : 2.6.3
pandas_gbq       : None
pyarrow          : 0.14.1
pytables         : None
s3fs             : 0.3.3
scipy            : 1.3.1
sqlalchemy       : 1.3.7
tables           : 3.5.2
xarray           : 0.12.3
xlrd             : 1.2.0
xlwt             : 1.3.0
xlsxwriter       : 1.1.9
</details>

Hints from issue discussion:

Commit message: BUG (#28098) some more changes

Patch to review:
diff --git a/doc/source/whatsnew/v0.25.1.rst b/doc/source/whatsnew/v0.25.1.rst
index 63dd56f4a3793..86ae7b7169883 100644
--- a/doc/source/whatsnew/v0.25.1.rst
+++ b/doc/source/whatsnew/v0.25.1.rst
@@ -99,6 +99,7 @@ Reshaping
 - Bug in :meth:`DataFrame.crosstab` when ``margins`` set to ``True`` and ``normalize`` is not ``False``, an error is raised. (:issue:`27500`)
 - :meth:`DataFrame.join` now suppresses the ``FutureWarning`` when the sort parameter is specified (:issue:`21952`)
 - Bug in :meth:`DataFrame.join` raising with readonly arrays (:issue:`27943`)
+- Bug :meth:`merge_asof` could not use datetime.timedelta for `tolerance` kwarg (:issue:`28098`)
 
 Sparse
 ^^^^^^
diff --git a/pandas/core/reshape/merge.py b/pandas/core/reshape/merge.py
index d7fbe464cb1e5..8c4e409cb0545 100644
--- a/pandas/core/reshape/merge.py
+++ b/pandas/core/reshape/merge.py
@@ -3,6 +3,7 @@
 """
 
 import copy
+import datetime
 from functools import partial
 import string
 import warnings
@@ -1619,7 +1620,7 @@ def _get_merge_keys(self):
                     )
                 raise MergeError(msg)
 
-        # validate tolerance; must be a Timedelta if we have a DTI
+        # validate tolerance; datetime.timedelta or Timedelta if we have a DTI
         if self.tolerance is not None:
 
             if self.left_index:
@@ -1635,7 +1636,7 @@ def _get_merge_keys(self):
             )
 
             if is_datetimelike(lt):
-                if not isinstance(self.tolerance, Timedelta):
+                if not isinstance(self.tolerance, datetime.timedelta):
                     raise MergeError(msg)
                 if self.tolerance < Timedelta(0):
                     raise MergeError("tolerance must be positive")
@@ -1705,6 +1706,8 @@ def flip(xs):
             left_values = left_values.view("i8")
             right_values = right_values.view("i8")
             if tolerance is not None:
+                if not isinstance(self.tolerance, Timedelta):
+                    tolerance = Timedelta(tolerance)
                 tolerance = tolerance.value
 
         # a "by" parameter requires special handling
diff --git a/pandas/tests/reshape/merge/test_merge_asof.py b/pandas/tests/reshape/merge/test_merge_asof.py
index 7412b1de643a1..caf2539a9e150 100644
--- a/pandas/tests/reshape/merge/test_merge_asof.py
+++ b/pandas/tests/reshape/merge/test_merge_asof.py
@@ -592,13 +592,7 @@ def test_non_sorted(self):
 
     @pytest.mark.parametrize(
         "tolerance",
-        [
-            Timedelta("1day"),
-            pytest.param(
-                datetime.timedelta(days=1),
-                marks=pytest.mark.xfail(reason="not implemented", strict=True),
-            ),
-        ],
+        [Timedelta("1day"), datetime.timedelta(days=1)],
         ids=["pd.Timedelta", "datetime.timedelta"],
     )
     def test_tolerance(self, tolerance):


Code review comments:
diff_hunk: @@ -99,6 +99,7 @@ Reshaping
 - Bug in :meth:`DataFrame.crosstab` when ``margins`` set to ``True`` and ``normalize`` is not ``False``, an error is raised. (:issue:`27500`)
 - :meth:`DataFrame.join` now suppresses the ``FutureWarning`` when the sort parameter is specified (:issue:`21952`)
 - Bug in :meth:`DataFrame.join` raising with readonly arrays (:issue:`27943`)
+- Bug :meth:`merge_asof` could not use datetime.timedelta for `tolerance` kwarg (:issue:`28098`)
path: doc/source/whatsnew/v0.25.1.rst
text: @user1:
* ````Bug in :func:`merge_asof` ````
* ````:class:`datetime.timedelta` ````
* ```` ``tolerance`` ````

@user2:
move this to 0.25.2

@user2:
sorry, move to 1.0

diff_hunk: @@ -1705,6 +1706,8 @@ def flip(xs):
             left_values = left_values.view("i8")
             right_values = right_values.view("i8")
             if tolerance is not None:
+                if not isinstance(self.tolerance, Timedelta):
path: pandas/core/reshape/merge.py
text: @user1:
Don't think we need this `if` statement. (it's okay to pass a `Timedelta` into `Timedelta` I think).