Skip to content

Commit

Permalink
feat: ods: add style to datetime, date and time values
Browse files Browse the repository at this point in the history
  • Loading branch information
pascalfree committed Jul 14, 2024
1 parent d3f6857 commit fd67ead
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 4 deletions.
70 changes: 66 additions & 4 deletions src/tablib/formats/_ods.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import numbers
from io import BytesIO

from odf import opendocument, style, table, text
from odf import number, opendocument, style, table, text

import tablib

Expand All @@ -17,6 +17,52 @@
))


def set_date_style(style):
style.addElement(number.Year(style="long"))
style.addElement(number.Text(text="-"))
style.addElement(number.Month(style="long"))
style.addElement(number.Text(text="-"))
style.addElement(number.Day(style="long"))


def set_time_style(style):
style.addElement(number.Hours(style="long"))
style.addElement(number.Text(text=":"))
style.addElement(number.Minutes(style="long"))
style.addElement(number.Text(text=":"))
style.addElement(number.Seconds(style="long", decimalplaces="0"))


date_style = number.DateStyle(name="date-style1")
set_date_style(date_style)
ds = style.Style(
name="ds1",
datastylename="date-style1",
parentstylename="Default",
family="table-cell",
)

time_style = number.DateStyle(name="time-style1")
set_time_style(time_style)
ts = style.Style(
name="ts1",
datastylename="time-style1",
parentstylename="Default",
family="table-cell",
)

datetime_style = number.DateStyle(name="datetime-style1")
set_date_style(datetime_style)
datetime_style.addElement(number.Text(text=" "))
set_time_style(datetime_style)
dts = style.Style(
name="dts1",
datastylename="datetime-style1",
parentstylename="Default",
family="table-cell",
)


class ODSFormat:
title = 'ods'
extensions = ('ods',)
Expand All @@ -27,6 +73,12 @@ def export_set(cls, dataset):

wb = opendocument.OpenDocumentSpreadsheet()
wb.automaticstyles.addElement(bold)
wb.styles.addElement(date_style)
wb.automaticstyles.addElement(ds)
wb.styles.addElement(time_style)
wb.automaticstyles.addElement(ts)
wb.styles.addElement(datetime_style)
wb.automaticstyles.addElement(dts)

ws = table.Table(name=dataset.title if dataset.title else 'Tablib Dataset')
wb.spreadsheet.addElement(ws)
Expand Down Expand Up @@ -162,12 +214,22 @@ def dset_sheet(cls, dataset, ws):
cell = table.TableCell(valuetype="float", value=col)
elif isinstance(col, dt.datetime):
cell = table.TableCell(
valuetype="date", datevalue=col.strftime('%Y-%m-%dT%H:%M:%S')
valuetype="date",
datevalue=col.strftime('%Y-%m-%dT%H:%M:%S'),
stylename=dts,
)
cell.addElement(text.P(text=col.strftime('%Y-%m-%d %H:%M:%S')))
elif isinstance(col, dt.date):
cell = table.TableCell(valuetype="date", datevalue=col.strftime('%Y-%m-%d'))
date_value = col.strftime('%Y-%m-%d')
cell = table.TableCell(valuetype="date", datevalue=date_value, stylename=ds)
cell.addElement(text.P(text=date_value))
elif isinstance(col, dt.time):
cell = table.TableCell(valuetype="time", timevalue=col.strftime('PT%HH%MM%SS'))
cell = table.TableCell(
valuetype="time",
timevalue=col.strftime('PT%HH%MM%SS'),
stylename=ts,
)
cell.addElement(text.P(text=col.strftime('%H:%M:%S')))
elif col is None:
cell = table.TableCell(valuetype="void")
else:
Expand Down
43 changes: 43 additions & 0 deletions tests/test_tablib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1168,6 +1168,16 @@ def test_tsv_export(self):


class ODSTests(BaseTestCase):
FORMAT_CONVERT = {
'yearlong': '%Y',
'monthlong': '%m',
'daylong': '%d',
'hourslong': '%H',
'minuteslong': '%M',
'secondslong': '%S',
'secondslong0': '%S',
}

def test_ods_export_import_set(self):
date = dt.date(2019, 10, 4)
date_time = dt.datetime(2019, 10, 4, 12, 30, 8)
Expand All @@ -1188,6 +1198,39 @@ def test_ods_export_import_set(self):
self.assertEqual(data.dict[0]['date/time'], date_time)
self.assertEqual(data.dict[0]['None'], '')

def test_ods_export_display(self):
"""Test that exported datetime types are displayed correctly in office software"""
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))
styles = {style.getAttribute('name'): style for style in ods_book.styles.childNodes}
automatic_styles = {
style.getAttribute('name'): style.getAttribute('datastylename')
for style in ods_book.automaticstyles.childNodes
}

def get_format(cell):
style = styles[automatic_styles[cell.getAttribute('stylename')]]
f = []
for number in style.childNodes:
name = number.qname[1] + ''.join(number.attributes.values())
f.append(self.FORMAT_CONVERT.get(name, str(number)))
return ''.join(f)

cells = ods_book.spreadsheet.getElementsByType(table.TableRow)[1].childNodes
self.assertEqual(str(date), str(cells[0]))
self.assertEqual('%Y-%m-%d', get_format(cells[0]))

self.assertEqual(str(time), str(cells[1]))
self.assertEqual('%H:%M:%S', get_format(cells[1]))

self.assertEqual(str(date_time), str(cells[2]))
self.assertEqual('%Y-%m-%d %H:%M:%S', get_format(cells[2]))

def test_ods_import_book(self):
ods_source = Path(__file__).parent / 'files' / 'book.ods'
with ods_source.open('rb') as fh:
Expand Down

0 comments on commit fd67ead

Please sign in to comment.