From 5f90341774be44395291bf00358e6e42a4b4e501 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Zuzek?= Date: Fri, 29 Sep 2023 14:22:05 +0200 Subject: [PATCH 1/8] BUG: parse ODF time values with comments --- doc/source/whatsnew/v2.2.0.rst | 1 + pandas/io/excel/_odfreader.py | 18 +++++++++++++++++- pandas/tests/io/data/excel/times_1900.ods | Bin 3181 -> 14304 bytes pandas/tests/io/data/excel/times_1904.ods | Bin 3215 -> 13712 bytes 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v2.2.0.rst b/doc/source/whatsnew/v2.2.0.rst index 7743f762d8898..eaa5eb7e25fdd 100644 --- a/doc/source/whatsnew/v2.2.0.rst +++ b/doc/source/whatsnew/v2.2.0.rst @@ -283,6 +283,7 @@ Bug fixes - Bug in :meth:`DataFrame.apply` where passing ``raw=True`` ignored ``args`` passed to the applied function (:issue:`55009`) - Bug in :meth:`pandas.DataFrame.melt` where it would not preserve the datetime (:issue:`55254`) - Bug in :meth:`pandas.read_excel` with a ODS file without cached formatted cell for float values (:issue:`55219`) +- Bug in :meth:`pandas.read_excel` where ODS files with comments on time value cells failed to parse (related to :issue:`55200`) Categorical ^^^^^^^^^^^ diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index 277f64f636731..ac74960fa2b89 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -1,5 +1,6 @@ from __future__ import annotations +import re from typing import ( TYPE_CHECKING, cast, @@ -182,6 +183,20 @@ def _get_column_repeat(self, cell) -> int: return int(cell.attributes.get((TABLENS, "number-columns-repeated"), 1)) + def _parse_odf_time(self, value: str) -> pd.Timestamp: + """ + Helper function to convert ODF variant of ISO 8601 formatted duration + "PnYnMnDTnHnMnS" - see https://www.w3.org/TR/xmlschema-2/#duration + """ + parts = re.match(r"^\s*PT\s*(\d+)\s*H\s*(\d+)\s*M\s*(\d+(\.\d+)?)\s*S$", value) + if parts is None: + raise ValueError(f"Failed to parse ODF time value: {value}") + h, m, s = parts.group(1, 2, 3) + # ignore date part from some representations as both pd.Timestamp + # and datetime.time restrict hour values to 0..23 + h = str(int(h) % 24) + return pd.Timestamp(f"{h}:{m}:{s}") + def _get_cell_value(self, cell) -> Scalar | NaTType: from odf.namespaces import OFFICENS @@ -214,7 +229,8 @@ def _get_cell_value(self, cell) -> Scalar | NaTType: cell_value = cell.attributes.get((OFFICENS, "date-value")) return pd.Timestamp(cell_value) elif cell_type == "time": - stamp = pd.Timestamp(str(cell)) + cell_value = cell.attributes.get((OFFICENS, "time-value")) + stamp = self._parse_odf_time(str(cell_value)) # cast needed here because Scalar doesn't include datetime.time return cast(Scalar, stamp.time()) else: diff --git a/pandas/tests/io/data/excel/times_1900.ods b/pandas/tests/io/data/excel/times_1900.ods index 79e031c721ea34fc6097556d7a8d427f72f236aa..c77fd257f5ccf47fd1f743edba0b3ce11eee7663 100644 GIT binary patch literal 14304 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 From 12749bb894d25fbefdba3d078e342735853de420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Zuzek?= Date: Tue, 3 Oct 2023 22:47:35 +0200 Subject: [PATCH 2/8] add test for incorrect values --- .../tests/io/data/excel/test_corrupted_time.ods | Bin 0 -> 9007 bytes pandas/tests/io/excel/test_readers.py | 5 +++++ 2 files changed, 5 insertions(+) create mode 100644 pandas/tests/io/data/excel/test_corrupted_time.ods diff --git a/pandas/tests/io/data/excel/test_corrupted_time.ods b/pandas/tests/io/data/excel/test_corrupted_time.ods new file mode 100644 index 0000000000000000000000000000000000000000..c3c3d105a4e0c9501ebba02f69d82d2cb550fbb0 GIT binary patch literal 9007 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 literal 0 HcmV?d00001 diff --git a/pandas/tests/io/excel/test_readers.py b/pandas/tests/io/excel/test_readers.py index 8dd9f96a05a90..b76f0d035d7af 100644 --- a/pandas/tests/io/excel/test_readers.py +++ b/pandas/tests/io/excel/test_readers.py @@ -1014,6 +1014,11 @@ def test_reader_seconds(self, request, engine, read_ext): actual = pd.read_excel("times_1904" + read_ext, sheet_name="Sheet1") tm.assert_frame_equal(actual, expected) + if read_ext == ".ods": + msg = "Failed to parse ODF time value: PT01H5a2M00S" + with pytest.raises(ValueError, match=msg): + pd.read_excel("test_corrupted_time" + read_ext) + def test_read_excel_multiindex(self, request, engine, read_ext): # see gh-4679 if engine == "pyxlsb": From 34930d5e413b3923db81c7676d8c9ff40bbed95f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Zuzek?= Date: Tue, 3 Oct 2023 22:51:06 +0200 Subject: [PATCH 3/8] return `datetime.time` directly --- pandas/io/excel/_odfreader.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index ac74960fa2b89..555a6fe487d7d 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -1,5 +1,6 @@ from __future__ import annotations +import datetime import re from typing import ( TYPE_CHECKING, @@ -183,7 +184,7 @@ def _get_column_repeat(self, cell) -> int: return int(cell.attributes.get((TABLENS, "number-columns-repeated"), 1)) - def _parse_odf_time(self, value: str) -> pd.Timestamp: + def _parse_odf_time(self, value: str) -> datetime.time: """ Helper function to convert ODF variant of ISO 8601 formatted duration "PnYnMnDTnHnMnS" - see https://www.w3.org/TR/xmlschema-2/#duration @@ -191,11 +192,19 @@ def _parse_odf_time(self, value: str) -> pd.Timestamp: parts = re.match(r"^\s*PT\s*(\d+)\s*H\s*(\d+)\s*M\s*(\d+(\.\d+)?)\s*S$", value) if parts is None: raise ValueError(f"Failed to parse ODF time value: {value}") - h, m, s = parts.group(1, 2, 3) - # ignore date part from some representations as both pd.Timestamp - # and datetime.time restrict hour values to 0..23 - h = str(int(h) % 24) - return pd.Timestamp(f"{h}:{m}:{s}") + hours, minutes, seconds, _, second_part = parts.group(*range(1, 6)) + if second_part is None: + microseconds = 0 + else: + microseconds = int(int(second_part) * pow(10, 6 - len(second_part))) + return datetime.time( + # ignore date part from some representations + # and datetime.time restrict hour values to 0..23 + hour=int(hours) % 24, + minute=int(minutes), + second=int(seconds), + microsecond=microseconds, + ) def _get_cell_value(self, cell) -> Scalar | NaTType: from odf.namespaces import OFFICENS @@ -232,7 +241,7 @@ def _get_cell_value(self, cell) -> Scalar | NaTType: cell_value = cell.attributes.get((OFFICENS, "time-value")) stamp = self._parse_odf_time(str(cell_value)) # cast needed here because Scalar doesn't include datetime.time - return cast(Scalar, stamp.time()) + return cast(Scalar, stamp) else: self.close() raise ValueError(f"Unrecognized type {cell_type}") From 3300618cecc930d15fc88fba6abb2646bdf066ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Zuzek?= Date: Tue, 3 Oct 2023 22:51:57 +0200 Subject: [PATCH 4/8] precompile regex --- pandas/io/excel/_odfreader.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index 555a6fe487d7d..07ea242c352e2 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -28,6 +28,12 @@ from pandas._libs.tslibs.nattype import NaTType +# ODF variant of ISO 8601 time/duration format: "PThhhHmmMss.sssS" +# see https://www.w3.org/TR/xmlschema-2/#duration for details +ODF_ISOTIME_PATTERN = re.compile( + r"^\s*PT\s*(\d+)\s*H\s*(\d+)\s*M\s*(\d+)(\.(\d+))?\s*S$" +) + @doc(storage_options=_shared_docs["storage_options"]) class ODFReader(BaseExcelReader["OpenDocument"]): @@ -186,10 +192,9 @@ def _get_column_repeat(self, cell) -> int: def _parse_odf_time(self, value: str) -> datetime.time: """ - Helper function to convert ODF variant of ISO 8601 formatted duration - "PnYnMnDTnHnMnS" - see https://www.w3.org/TR/xmlschema-2/#duration + This helper function parses ODF time value """ - parts = re.match(r"^\s*PT\s*(\d+)\s*H\s*(\d+)\s*M\s*(\d+(\.\d+)?)\s*S$", value) + parts = ODF_ISOTIME_PATTERN.match(value) if parts is None: raise ValueError(f"Failed to parse ODF time value: {value}") hours, minutes, seconds, _, second_part = parts.group(*range(1, 6)) From d88715d91afac58a06aeee2802eec5be5e1ef70d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Zuzek?= <29018159+mzuzek@users.noreply.github.com> Date: Wed, 4 Oct 2023 11:54:53 +0200 Subject: [PATCH 5/8] fix docs - suggested by @mroeschke Co-authored-by: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> --- doc/source/whatsnew/v2.2.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v2.2.0.rst b/doc/source/whatsnew/v2.2.0.rst index eaa5eb7e25fdd..ce9255142d0c1 100644 --- a/doc/source/whatsnew/v2.2.0.rst +++ b/doc/source/whatsnew/v2.2.0.rst @@ -282,8 +282,8 @@ Bug fixes - Bug in :func:`pandas.api.types.is_string_dtype` while checking object array with no elements is of the string dtype (:issue:`54661`) - Bug in :meth:`DataFrame.apply` where passing ``raw=True`` ignored ``args`` passed to the applied function (:issue:`55009`) - Bug in :meth:`pandas.DataFrame.melt` where it would not preserve the datetime (:issue:`55254`) +- Bug in :meth:`pandas.read_excel` where ODS files with comments on time value cells failed to parse (:issue:`55200`) - Bug in :meth:`pandas.read_excel` with a ODS file without cached formatted cell for float values (:issue:`55219`) -- Bug in :meth:`pandas.read_excel` where ODS files with comments on time value cells failed to parse (related to :issue:`55200`) Categorical ^^^^^^^^^^^ From 592d9a234f02f7abb24567a41d7c7206d9568dc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Zuzek?= Date: Wed, 4 Oct 2023 13:12:16 +0200 Subject: [PATCH 6/8] clean up modified test files (apply only minimal required changes) --- pandas/tests/io/data/excel/times_1900.ods | Bin 14304 -> 3110 bytes pandas/tests/io/data/excel/times_1904.ods | Bin 13712 -> 3138 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/pandas/tests/io/data/excel/times_1900.ods b/pandas/tests/io/data/excel/times_1900.ods index c77fd257f5ccf47fd1f743edba0b3ce11eee7663..7e307952bf89f218664f92bf59c04e8e443c99d1 100644 GIT binary patch literal 3110 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 T4I-id03hO}MzpupUxohz52|s} literal 13712 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 Date: Wed, 4 Oct 2023 13:12:56 +0200 Subject: [PATCH 7/8] refactor helper function to preserve original class layout --- pandas/io/excel/_odfreader.py | 51 ++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index 07ea242c352e2..93bea0f5a473e 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -190,27 +190,6 @@ def _get_column_repeat(self, cell) -> int: return int(cell.attributes.get((TABLENS, "number-columns-repeated"), 1)) - def _parse_odf_time(self, value: str) -> datetime.time: - """ - This helper function parses ODF time value - """ - parts = ODF_ISOTIME_PATTERN.match(value) - if parts is None: - raise ValueError(f"Failed to parse ODF time value: {value}") - hours, minutes, seconds, _, second_part = parts.group(*range(1, 6)) - if second_part is None: - microseconds = 0 - else: - microseconds = int(int(second_part) * pow(10, 6 - len(second_part))) - return datetime.time( - # ignore date part from some representations - # and datetime.time restrict hour values to 0..23 - hour=int(hours) % 24, - minute=int(minutes), - second=int(seconds), - microsecond=microseconds, - ) - def _get_cell_value(self, cell) -> Scalar | NaTType: from odf.namespaces import OFFICENS @@ -243,10 +222,9 @@ def _get_cell_value(self, cell) -> Scalar | NaTType: cell_value = cell.attributes.get((OFFICENS, "date-value")) return pd.Timestamp(cell_value) elif cell_type == "time": - cell_value = cell.attributes.get((OFFICENS, "time-value")) - stamp = self._parse_odf_time(str(cell_value)) + cell_value = self._get_cell_time_value(cell) # cast needed here because Scalar doesn't include datetime.time - return cast(Scalar, stamp) + return cast(Scalar, cell_value) else: self.close() raise ValueError(f"Unrecognized type {cell_type}") @@ -277,3 +255,28 @@ def _get_cell_string_value(self, cell) -> str: else: value.append(str(fragment).strip("\n")) return "".join(value) + + def _get_cell_time_value(self, cell) -> datetime.time: + """ + This helper function parses ODF time value + """ + from odf.namespaces import OFFICENS + + value = cell.attributes.get((OFFICENS, "time-value")) + parts = ODF_ISOTIME_PATTERN.match(value) + if parts is None: + raise ValueError(f"Failed to parse ODF time value: {value}") + hours, minutes, seconds, _, second_part = parts.group(*range(1, 6)) + if second_part is None: + microseconds = 0 + else: + microseconds = int(int(second_part) * pow(10, 6 - len(second_part))) + + return datetime.time( + # ignore date part from some representations + # and datetime.time restrict hour values to 0..23 + hour=int(hours) % 24, + minute=int(minutes), + second=int(seconds), + microsecond=microseconds, + ) From ea23a2e501a8db58b07907899383d3ce3e49faa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Zuzek?= Date: Wed, 4 Oct 2023 17:20:00 +0200 Subject: [PATCH 8/8] fix type mismatch on assignment --- pandas/io/excel/_odfreader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index 93bea0f5a473e..05f5ee5951c6c 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -222,9 +222,9 @@ def _get_cell_value(self, cell) -> Scalar | NaTType: cell_value = cell.attributes.get((OFFICENS, "date-value")) return pd.Timestamp(cell_value) elif cell_type == "time": - cell_value = self._get_cell_time_value(cell) + stamp = self._get_cell_time_value(cell) # cast needed here because Scalar doesn't include datetime.time - return cast(Scalar, cell_value) + return cast(Scalar, stamp) else: self.close() raise ValueError(f"Unrecognized type {cell_type}")