From 2a57d2002249eb4d205fe7db72f6e62a71cef788 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Mon, 20 Jan 2014 12:15:44 +0100 Subject: [PATCH 1/2] ENH: ExcelWriter() accepts date and datetime formats with ExcelWriter(date_format='YYYY-MM-DD', datetime_format='YYYY-MM-DD HH:MM:SS') you can set the formatstrings for Excel export --- doc/source/release.rst | 3 +++ pandas/io/excel.py | 42 +++++++++++++++++++++++++---------- pandas/io/tests/test_excel.py | 40 ++++++++++++++++++++++++++++++++- 3 files changed, 72 insertions(+), 13 deletions(-) diff --git a/doc/source/release.rst b/doc/source/release.rst index 2153c50155ad0..37e2f3357acd9 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -53,6 +53,9 @@ pandas 0.13.1 New features ~~~~~~~~~~~~ + - Added ``date_format`` and ``datetime_format`` attribute to ExcelWriter. + (:issue:`4133`) + API Changes ~~~~~~~~~~~ diff --git a/pandas/io/excel.py b/pandas/io/excel.py index ad7c37fba4c2f..16b6848ca2526 100644 --- a/pandas/io/excel.py +++ b/pandas/io/excel.py @@ -355,6 +355,11 @@ class ExcelWriter(object): Engine to use for writing. If None, defaults to ``io.excel..writer``. NOTE: can only be passed as a keyword argument. + date_format : string, default None + Format string for dates written into Excel files (e.g. 'YYYY-MM-DD') + datetime_format : string, default None + Format string for datetime objects written into Excel files + (e.g. 'YYYY-MM-DD HH:MM:SS') """ # Defining an ExcelWriter implementation (see abstract methods for more...) @@ -429,8 +434,9 @@ def save(self): """ pass - def __init__(self, path, engine=None, **engine_kwargs): - # validate that this engine can handle the extnesion + def __init__(self, path, engine=None, + date_format=None, datetime_format=None, **engine_kwargs): + # validate that this engine can handle the extension ext = os.path.splitext(path)[-1] self.check_extension(ext) @@ -438,6 +444,15 @@ def __init__(self, path, engine=None, **engine_kwargs): self.sheets = {} self.cur_sheet = None + if date_format is None: + self.date_format = 'YYYY-MM-DD' + else: + self.date_format = date_format + if datetime_format is None: + self.datetime_format = 'YYYY-MM-DD HH:MM:SS' + else: + self.datetime_format = datetime_format + def _get_sheet_name(self, sheet_name): if sheet_name is None: sheet_name = self.cur_sheet @@ -518,9 +533,9 @@ def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0): style.__getattribute__(field)) if isinstance(cell.val, datetime.datetime): - xcell.style.number_format.format_code = "YYYY-MM-DD HH:MM:SS" + xcell.style.number_format.format_code = self.datetime_format elif isinstance(cell.val, datetime.date): - xcell.style.number_format.format_code = "YYYY-MM-DD" + xcell.style.number_format.format_code = self.date_format if cell.mergestart is not None and cell.mergeend is not None: cletterstart = get_column_letter(startcol + cell.col + 1) @@ -585,8 +600,8 @@ def __init__(self, path, engine=None, **engine_kwargs): super(_XlwtWriter, self).__init__(path, **engine_kwargs) self.book = xlwt.Workbook() - self.fm_datetime = xlwt.easyxf(num_format_str='YYYY-MM-DD HH:MM:SS') - self.fm_date = xlwt.easyxf(num_format_str='YYYY-MM-DD') + self.fm_datetime = xlwt.easyxf(num_format_str=self.datetime_format) + self.fm_date = xlwt.easyxf(num_format_str=self.date_format) def save(self): """ @@ -612,9 +627,9 @@ def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0): num_format_str = None if isinstance(cell.val, datetime.datetime): - num_format_str = "YYYY-MM-DD HH:MM:SS" + num_format_str = self.datetime_format if isinstance(cell.val, datetime.date): - num_format_str = "YYYY-MM-DD" + num_format_str = self.date_format stylekey = json.dumps(cell.style) if num_format_str: @@ -699,11 +714,14 @@ class _XlsxWriter(ExcelWriter): engine = 'xlsxwriter' supported_extensions = ('.xlsx',) - def __init__(self, path, engine=None, **engine_kwargs): + def __init__(self, path, engine=None, + date_format=None, datetime_format=None, **engine_kwargs): # Use the xlsxwriter module as the Excel writer. import xlsxwriter - super(_XlsxWriter, self).__init__(path, engine=engine, **engine_kwargs) + super(_XlsxWriter, self).__init__(path, engine=engine, + date_format=date_format, datetime_format=datetime_format, + **engine_kwargs) self.book = xlsxwriter.Workbook(path, **engine_kwargs) @@ -729,9 +747,9 @@ def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0): for cell in cells: num_format_str = None if isinstance(cell.val, datetime.datetime): - num_format_str = "YYYY-MM-DD HH:MM:SS" + num_format_str = self.datetime_format if isinstance(cell.val, datetime.date): - num_format_str = "YYYY-MM-DD" + num_format_str = self.date_format stylekey = json.dumps(cell.style) if num_format_str: diff --git a/pandas/io/tests/test_excel.py b/pandas/io/tests/test_excel.py index edcb80ae74f6f..5335c7691195f 100644 --- a/pandas/io/tests/test_excel.py +++ b/pandas/io/tests/test_excel.py @@ -1,7 +1,7 @@ # pylint: disable=E1101 from pandas.compat import u, range, map -from datetime import datetime +from datetime import datetime, date import os import nose @@ -661,6 +661,44 @@ def test_excel_roundtrip_datetime(self): recons = reader.parse('test1') tm.assert_frame_equal(self.tsframe, recons) + # GH4133 - excel output format strings + def test_excel_date_datetime_format(self): + df = DataFrame([[date(2014, 1, 31), + date(1999, 9, 24)], + [datetime(1998, 5, 26, 23, 33, 4), + datetime(2014, 2, 28, 13, 5, 13)]], + index=['DATE', 'DATETIME'], columns=['X', 'Y']) + df_expected = DataFrame([[datetime(2014, 1, 31), + datetime(1999, 9, 24)], + [datetime(1998, 5, 26, 23, 33, 4), + datetime(2014, 2, 28, 13, 5, 13)]], + index=['DATE', 'DATETIME'], columns=['X', 'Y']) + + with ensure_clean(self.ext) as filename1: + with ensure_clean(self.ext) as filename2: + writer1 = ExcelWriter(filename1) + writer2 = ExcelWriter(filename2, + date_format='DD.MM.YYYY', + datetime_format='DD.MM.YYYY HH-MM-SS') + + df.to_excel(writer1, 'test1') + df.to_excel(writer2, 'test1') + + writer1.close() + writer2.close() + + reader1 = ExcelFile(filename1) + reader2 = ExcelFile(filename2) + + rs1 = reader1.parse('test1', index_col=None) + rs2 = reader2.parse('test1', index_col=None) + + tm.assert_frame_equal(rs1, rs2) + + # since the reader returns a datetime object for dates, we need + # to use df_expected to check the result + tm.assert_frame_equal(rs2, df_expected) + def test_to_excel_periodindex(self): _skip_if_no_xlrd() From d3ed7b5d1a733dc8a7879ebf932577bf34cc1b5b Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Mon, 20 Jan 2014 12:20:54 +0100 Subject: [PATCH 2/2] BUG: xlsxwriter was not able to write date and datetime objects --- pandas/io/excel.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pandas/io/excel.py b/pandas/io/excel.py index 16b6848ca2526..29a7031c79ae4 100644 --- a/pandas/io/excel.py +++ b/pandas/io/excel.py @@ -780,12 +780,16 @@ def _convert_to_style(self, style_dict, num_format_str=None): style_dict: style dictionary to convert num_format_str: optional number format string """ - if style_dict is None: - return None # Create a XlsxWriter format object. xl_format = self.book.add_format() + + if num_format_str is not None: + xl_format.set_num_format(num_format_str) + if style_dict is None: + return xl_format + # Map the cell font to XlsxWriter font properties. if style_dict.get('font'): font = style_dict['font'] @@ -806,9 +810,6 @@ def _convert_to_style(self, style_dict, num_format_str=None): if style_dict.get('borders'): xl_format.set_border() - if num_format_str is not None: - xl_format.set_num_format(num_format_str) - return xl_format register_writer(_XlsxWriter)