Repository: jazzband/tablib

Issue:
Question / feature request: Excel - format as date 
I can feed a tablib cell a datetime, and formatting that cell as date in Excel works - the number shown initially gets converted to a valid date.

Can i tell tablib to format the cell as datetime right away, so the Excel file shows date formatting right after being opened?

(Looks like XlsxWriter and xlwt can do that, but i'm reluctant to switch.)



Discussion:
Can you share the code of how to do that? We use xlwt under the covers.

```python
def to_excel(dt_obj):
    """Convert a datetime object to a float for Excel's date formatting.

    Excel stores dates and times internally as floating point numbers
    where the integer part is the number of days since 31st December 1899
    and the fractional part is the time of day.
    When loaded into a tablib Dataset these values remain as floating point.
    """
    REF_DATE = datetime(1899, 12, 31)
    SECS_IN_DAY = 24 * 60 * 60.0
    delta = dt_obj - REF_DATE
    excel_date = delta.days + delta.seconds / SECS_IN_DAY
    return excel_date

```

The transformation above is already handled by xlwt:

https://github.com/python-excel/xlwt/blob/5a222d0315b6d3ce52a3cedd7c3e41309587c107/xlwt/Row.py#L86-L106

In order to get them to display correctly you need to specify a formatter, such as:

```py
if isinstance(col, date):
    date_fmt = ws.write(i, j, col, xlwt.easyxf(num_format_str="M/D/YY"))
elif isinstance(col, datetime):
    date_fmt = ws.write(i, j, col, xlwt.easyxf(num_format_str="M/D/YY h:mm"))
```

The format string should be taken from the list of Excel's [built-in formats](https://github.com/python-excel/xlwt/blob/5a222d0315b6d3ce52a3cedd7c3e41309587c107/xlwt/Style.py#L24).

Despite selecting American M/D/YY format, this is correctly localised to D/M/YY on my British computer.
A related request would be to format as currency (and also set the cell format to currency, so excel/libreoffice displays a currency symbol on the field).



Patch:
diff --git a/src/tablib/formats/_xls.py b/src/tablib/formats/_xls.py
index 2c6c5272..947625dc 100644
--- a/src/tablib/formats/_xls.py
+++ b/src/tablib/formats/_xls.py
@@ -1,6 +1,6 @@
 """ Tablib - XLS Support.
 """
-
+import datetime
 from io import BytesIO
 
 import xlrd
@@ -12,6 +12,9 @@
 # special styles
 wrap = xlwt.easyxf("alignment: wrap on")
 bold = xlwt.easyxf("font: bold on")
+datetime_style = xlwt.easyxf(num_format_str='M/D/YY h:mm')
+date_style = xlwt.easyxf(num_format_str='M/D/YY')
+time_style = xlwt.easyxf(num_format_str='h:mm:ss')
 
 
 class XLSFormat:
@@ -138,6 +141,13 @@ def dset_sheet(cls, dataset, ws):
                 elif len(row) < dataset.width:
                     ws.write(i, j, col, bold)
 
+                # format date types
+                elif isinstance(col, datetime.datetime):
+                    ws.write(i, j, col, datetime_style)
+                elif isinstance(col, datetime.date):
+                    ws.write(i, j, col, date_style)
+                elif isinstance(col, datetime.time):
+                    ws.write(i, j, col, time_style)
                 # wrap the rest
                 else:
                     try:


Test patch:
diff --git a/tests/test_tablib.py b/tests/test_tablib.py
index fbb26f96..d557d25e 100755
--- a/tests/test_tablib.py
+++ b/tests/test_tablib.py
@@ -12,6 +12,7 @@
 from pathlib import Path
 from uuid import uuid4
 
+import xlrd
 from openpyxl.reader.excel import load_workbook
 
 import tablib
@@ -1254,6 +1255,23 @@ def test_book_import_from_stream(self):
         book = tablib.Databook().load(in_stream, 'xls')
         self.assertEqual(book.sheets()[0].title, 'Founders')
 
+    def test_xls_export_with_dates(self):
+        date = dt.date(2019, 10, 4)
+        time = dt.time(14, 30)
+        date_time = dt.datetime(2019, 10, 4, 12, 30, 8)
+        data.append((date, time, date_time))
+        data.headers = ('date', 'time', 'date/time')
+        _xls = data.xls
+        xls_book = xlrd.open_workbook(file_contents=_xls, formatting_info=True)
+        row = xls_book.sheet_by_index(0).row(1)
+
+        def get_format_str(cell):
+            return xls_book.format_map[xls_book.xf_list[cell.xf_index].format_key].format_str
+
+        self.assertEqual('m/d/yy', get_format_str(row[0]))
+        self.assertEqual('h:mm:ss', get_format_str(row[1]))
+        self.assertEqual('m/d/yy h:mm', get_format_str(row[2]))
+
 
 class XLSXTests(BaseTestCase):
     def test_xlsx_format_detect(self):