diff --git a/HISTORY.md b/HISTORY.md index 6a1985e3..9513bfd3 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,11 @@ # History +## Unreleased + +### Bugfixes + +- Fix time and datetime export in ODS format (#595). + ## 3.6.1 (2024-04-04) ### Bugfixes diff --git a/src/tablib/formats/_ods.py b/src/tablib/formats/_ods.py index 34b47a0d..cb5fbdab 100644 --- a/src/tablib/formats/_ods.py +++ b/src/tablib/formats/_ods.py @@ -92,7 +92,11 @@ def convert_date(val): return convert_date(date_value) if value_type == 'time': time_value = cell.getAttribute('timevalue') - return dt.datetime.strptime(time_value, "%H:%M:%S").time() + try: + return dt.datetime.strptime(time_value, "PT%HH%MM%SS").time() + except ValueError: + # backwards compatibility for times exported with older tablib versions + return dt.datetime.strptime(time_value, "%H:%M:%S").time() if value_type == 'boolean': bool_value = cell.getAttribute('booleanvalue') return bool_value == 'true' @@ -158,12 +162,12 @@ def dset_sheet(cls, dataset, ws): cell = table.TableCell(valuetype="float", value=col) elif isinstance(col, dt.datetime): cell = table.TableCell( - valuetype="date", value=col.strftime('%Y-%m-%dT%H:%M:%S') + valuetype="date", datevalue=col.strftime('%Y-%m-%dT%H:%M:%S') ) elif isinstance(col, dt.date): cell = table.TableCell(valuetype="date", datevalue=col.strftime('%Y-%m-%d')) elif isinstance(col, dt.time): - cell = table.TableCell(valuetype="time", timevalue=col.strftime('%H:%M:%S')) + cell = table.TableCell(valuetype="time", timevalue=col.strftime('PT%HH%MM%SS')) elif col is None: cell = table.TableCell(valuetype="void") else: diff --git a/tests/files/book.ods b/tests/files/book.ods index a2697680..4c2b49f9 100644 Binary files a/tests/files/book.ods and b/tests/files/book.ods differ diff --git a/tests/test_tablib.py b/tests/test_tablib.py index d557d25e..aad6ae3c 100755 --- a/tests/test_tablib.py +++ b/tests/test_tablib.py @@ -5,6 +5,7 @@ import doctest import json import pickle +import re import tempfile import unittest from decimal import Decimal @@ -13,6 +14,7 @@ from uuid import uuid4 import xlrd +from odf import opendocument, table from openpyxl.reader.excel import load_workbook import tablib @@ -1214,6 +1216,26 @@ def test_ods_unknown_value_type(self): dataset = tablib.Dataset().load(fh, 'ods') self.assertEqual(dataset.pop(), ('abcd',)) + def test_ods_export_dates(self): + """test against odf specification""" + date = dt.date(2019, 10, 4) + date_time = dt.datetime(2019, 10, 4, 12, 30, 8) + time = dt.time(14, 30) + data.append((date, time, date_time)) + data.headers = ('date', 'time', 'date/time') + _ods = data.ods + ods_book = opendocument.load(BytesIO(_ods)) + cells = ods_book.spreadsheet.getElementsByType(table.TableRow)[1].childNodes + # date value + self.assertEqual(cells[0].getAttribute('datevalue'), '2019-10-04') + # time value + duration_exp = re.compile(r"^P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?" + r"(?:T(?:(\d+)H)?(?:(\d+)M)?(?:([\d.]+)S)?)?$") + duration = duration_exp.match(cells[1].getAttribute('timevalue')).groups() + self.assertListEqual([0, 0, 0, 14, 30, 0], [int(v or 0) for v in duration]) + # datetime value + self.assertEqual(cells[2].getAttribute('datevalue'), '2019-10-04T12:30:08') + class XLSTests(BaseTestCase): def test_xls_format_detect(self):