Repository: pandas-dev/pandas

Issue:
to_excel with engine='xlwt' doesn't support pandas.tslib.Timedelta
Hello,

`to_excel` method with `engine='xlwt'` doesn't support `pandas.tslib.Timedelta`

```
import numpy as np
import pandas as pd

idx = pd.date_range('20140101', '20140201')
df = pd.DataFrame(index=idx)
df['col0'] = np.random.randn(len(idx))
s_idx = pd.Series(idx, index=idx)
diff_idx = (s_idx-s_idx.shift(1)).fillna(pd.Timedelta(0))
df['diff_dt'] = diff_idx
df.to_excel("out.xls", engine='xlwt')
```

it works fine with xlsx files (engine `openpyxl` or `xlsxwriter`).

Kind regards



Discussion:
showing the versions is always helpful (`pd.show_versions()`). 

```
INSTALLED VERSIONS
------------------
commit: None
python: 2.7.6.final.0
python-bits: 64
OS: Darwin
OS-release: 14.0.0
machine: x86_64
processor: i386
byteorder: little
LC_ALL: None
LANG: fr_FR.UTF-8

pandas: 0.15.2
nose: 1.3.4
Cython: 0.21.1
numpy: 1.9.1
scipy: 0.14.1rc1
statsmodels: 0.6.1
IPython: 2.3.1
sphinx: 1.2.3
patsy: 0.3.0
dateutil: 2.3
pytz: 2014.9
bottleneck: None
tables: 3.1.1
numexpr: 2.4
matplotlib: 1.4.2
openpyxl: 2.0.3
xlrd: 0.9.3
xlwt: 0.7.5
xlsxwriter: 0.6.4
lxml: 3.4.1
bs4: 4.3.2
html5lib: 0.999
httplib2: 0.8
apiclient: None
rpy2: None
sqlalchemy: 0.9.8
pymysql: 0.6.2.None
psycopg2: None
```

@scls19fr does this work with the plain `datetime.timedelta` type instead of `pd.Timedelta`? This might be another case where `Timedelta` does not quite conform to the `timedelta` API.

+1


Resolving patch:
diff --git a/pandas/io/excel.py b/pandas/io/excel.py
--- a/pandas/io/excel.py
+++ b/pandas/io/excel.py
@@ -4,7 +4,7 @@
 
 # ---------------------------------------------------------------------
 # ExcelFile class
-from datetime import datetime, date, time, MINYEAR
+from datetime import datetime, date, time, MINYEAR, timedelta
 
 import os
 import abc
@@ -21,7 +21,6 @@
 from pandas.io.common import (_is_url, _urlopen, _validate_header_arg,
                               get_filepath_or_buffer, _NA_VALUES,
                               _stringify_path)
-from pandas.core.indexes.period import Period
 import pandas._libs.json as json
 from pandas.compat import (map, zip, reduce, range, lrange, u, add_metaclass,
                            string_types, OrderedDict)
@@ -777,17 +776,30 @@ def _pop_header_name(row, index_col):
 
 
 def _conv_value(val):
-    # Convert numpy types to Python types for the Excel writers.
+    """ Convert numpy types to Python types for the Excel writers.
+
+        Parameters
+        ----------
+        val : object
+            Value to be written into cells
+
+        Returns
+        -------
+        If val is a numpy int, float, or bool, then the equivalent Python
+        types are returned. :obj:`datetime`, :obj:`date`, and :obj:`timedelta`
+        are passed and formatting must be handled in the writer. :obj:`str`
+        representation is returned for all other types.
+    """
     if is_integer(val):
         val = int(val)
     elif is_float(val):
         val = float(val)
     elif is_bool(val):
         val = bool(val)
-    elif isinstance(val, Period):
-        val = "{val}".format(val=val)
-    elif is_list_like(val):
-        val = str(val)
+    elif isinstance(val, (datetime, date, timedelta)):
+        pass
+    else:
+        val = compat.to_str(val)
 
     return val
 
@@ -1460,6 +1472,9 @@ def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0,
                 num_format_str = self.datetime_format
             elif isinstance(cell.val, date):
                 num_format_str = self.date_format
+            elif isinstance(cell.val, timedelta):
+                delta = cell.val
+                val = delta.total_seconds() / float(86400)
 
             stylekey = json.dumps(cell.style)
             if num_format_str:


Test patch:
diff --git a/pandas/tests/io/test_excel.py b/pandas/tests/io/test_excel.py
--- a/pandas/tests/io/test_excel.py
+++ b/pandas/tests/io/test_excel.py
@@ -2,7 +2,7 @@
 import os
 import sys
 import warnings
-from datetime import datetime, date, time
+from datetime import datetime, date, time, timedelta
 from distutils.version import LooseVersion
 from functools import partial
 from warnings import catch_warnings
@@ -1440,6 +1440,56 @@ def test_excel_date_datetime_format(self):
                 # to use df_expected to check the result
                 tm.assert_frame_equal(rs2, df_expected)
 
+    def test_to_excel_interval_no_labels(self):
+        # GH19242 - test writing Interval without labels
+        _skip_if_no_xlrd()
+
+        with ensure_clean(self.ext) as path:
+            frame = DataFrame(np.random.randint(-10, 10, size=(20, 1)),
+                              dtype=np.int64)
+            expected = frame.copy()
+            frame['new'] = pd.cut(frame[0], 10)
+            expected['new'] = pd.cut(expected[0], 10).astype(str)
+            frame.to_excel(path, 'test1')
+            reader = ExcelFile(path)
+            recons = read_excel(reader, 'test1')
+            tm.assert_frame_equal(expected, recons)
+
+    def test_to_excel_interval_labels(self):
+        # GH19242 - test writing Interval with labels
+        _skip_if_no_xlrd()
+
+        with ensure_clean(self.ext) as path:
+            frame = DataFrame(np.random.randint(-10, 10, size=(20, 1)),
+                              dtype=np.int64)
+            expected = frame.copy()
+            intervals = pd.cut(frame[0], 10, labels=['A', 'B', 'C', 'D', 'E',
+                                                     'F', 'G', 'H', 'I', 'J'])
+            frame['new'] = intervals
+            expected['new'] = pd.Series(list(intervals))
+            frame.to_excel(path, 'test1')
+            reader = ExcelFile(path)
+            recons = read_excel(reader, 'test1')
+            tm.assert_frame_equal(expected, recons)
+
+    def test_to_excel_timedelta(self):
+        # GH 19242, GH9155 - test writing timedelta to xls
+        _skip_if_no_xlrd()
+
+        with ensure_clean('.xls') as path:
+            frame = DataFrame(np.random.randint(-10, 10, size=(20, 1)),
+                              columns=['A'],
+                              dtype=np.int64
+                              )
+            expected = frame.copy()
+            frame['new'] = frame['A'].apply(lambda x: timedelta(seconds=x))
+            expected['new'] = expected['A'].apply(
+                lambda x: timedelta(seconds=x).total_seconds() / float(86400))
+            frame.to_excel(path, 'test1')
+            reader = ExcelFile(path)
+            recons = read_excel(reader, 'test1')
+            tm.assert_frame_equal(expected, recons)
+
     def test_to_excel_periodindex(self):
         _skip_if_no_xlrd()