From 2dcb22fff1844a975f1f0064d33893b8ae500d26 Mon Sep 17 00:00:00 2001 From: Ged Date: Thu, 27 Nov 2025 10:11:08 +0100 Subject: [PATCH 1/5] ASCReader: Improve datetime parsing and support double-defined AM/PM cases --- can/io/asc.py | 56 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index 2c80458c4..d53d92a71 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -116,41 +116,51 @@ def _extract_header(self) -> None: @staticmethod def _datetime_to_timestamp(datetime_string: str) -> float: - # ugly locale independent solution - month_map = { - "Jan": 1, - "Feb": 2, - "Mar": 3, - "Apr": 4, - "May": 5, - "Jun": 6, - "Jul": 7, - "Aug": 8, - "Sep": 9, - "Oct": 10, - "Nov": 11, - "Dec": 12, - "Mär": 3, - "Mai": 5, - "Okt": 10, - "Dez": 12, + MONTH_MAP = { + "jan": 1, + "feb": 2, + "mar": 3, + "apr": 4, + "may": 5, + "jun": 6, + "jul": 7, + "aug": 8, + "sep": 9, + "oct": 10, + "nov": 11, + "dec": 12, + "mär": 3, + "mai": 5, + "okt": 10, + "dez": 12, } - for name, number in month_map.items(): - datetime_string = datetime_string.replace(name, str(number).zfill(2)) - datetime_formats = ( + DATETIME_FORMATS = ( "%m %d %I:%M:%S.%f %p %Y", "%m %d %I:%M:%S %p %Y", "%m %d %H:%M:%S.%f %Y", "%m %d %H:%M:%S %Y", + "%m %d %H:%M:%S.%f %p %Y", + "%m %d %H:%M:%S %p %Y" ) - for format_str in datetime_formats: + + datetime_string_parts = datetime_string.split(" ", 1) + month = datetime_string_parts[0].strip().lower() + + try: + datetime_string_parts[0] = f"{MONTH_MAP[month]:02d}" + except KeyError: + raise ValueError(f"Unsupported month abbreviation: {month}") from None + datetime_string = " ".join(datetime_string_parts) + + + for format_str in DATETIME_FORMATS: try: return datetime.strptime(datetime_string, format_str).timestamp() except ValueError: continue - raise ValueError(f"Incompatible datetime string {datetime_string}") + raise ValueError(f"Unsupported datetime format: '{datetime_string}'") def _extract_can_id(self, str_can_id: str, msg_kwargs: dict[str, Any]) -> None: if str_can_id[-1:].lower() == "x": From c2e0fe6f5901e8026edd00289498cc4769713d4b Mon Sep 17 00:00:00 2001 From: Ged Date: Mon, 1 Dec 2025 17:59:41 +0100 Subject: [PATCH 2/5] Fixed code formatting and add a news fragment for PR 2009 --- can/io/asc.py | 3 +-- doc/changelog.d/2009.changed.md | 1 + test/logformats_test.py | 13 +++++++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 doc/changelog.d/2009.changed.md diff --git a/can/io/asc.py b/can/io/asc.py index d53d92a71..0f5b5782a 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -141,7 +141,7 @@ def _datetime_to_timestamp(datetime_string: str) -> float: "%m %d %H:%M:%S.%f %Y", "%m %d %H:%M:%S %Y", "%m %d %H:%M:%S.%f %p %Y", - "%m %d %H:%M:%S %p %Y" + "%m %d %H:%M:%S %p %Y", ) datetime_string_parts = datetime_string.split(" ", 1) @@ -153,7 +153,6 @@ def _datetime_to_timestamp(datetime_string: str) -> float: raise ValueError(f"Unsupported month abbreviation: {month}") from None datetime_string = " ".join(datetime_string_parts) - for format_str in DATETIME_FORMATS: try: return datetime.strptime(datetime_string, format_str).timestamp() diff --git a/doc/changelog.d/2009.changed.md b/doc/changelog.d/2009.changed.md new file mode 100644 index 000000000..6e68198a1 --- /dev/null +++ b/doc/changelog.d/2009.changed.md @@ -0,0 +1 @@ +Improved datetime parsing and added support for “double-defined” datetime strings (such as, e.g., `"30 15:06:13.191 pm 2017"`) for ASCReader class. \ No newline at end of file diff --git a/test/logformats_test.py b/test/logformats_test.py index f4bd1191f..2a15d093e 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -680,6 +680,19 @@ def test_write(self): self.assertEqual(expected_file.read_text(), actual_file.read_text()) + @parameterized.expand( + [ + ("May 27 04:09:35.000 pm 2014", 1401199775.0), + ("Mai 27 04:09:35.000 pm 2014", 1401199775.0), + ("Apr 28 10:44:52.480 2022", 1651135492.48), + ("Sep 30 15:06:13.191 2017", 1506776773.191), + ("Sep 30 15:06:13.191 pm 2017", 1506776773.191), + ("Sep 30 15:06:13.191 am 2017", 1506776773.191), + ] + ) + def test_datetime_to_timestamp(self, datetime_string: str, expected_timestamp: float): + timestamp = can.ASCReader._datetime_to_timestamp(datetime_string) + self.assertAlmostEqual(timestamp, expected_timestamp) class TestBlfFileFormat(ReaderWriterTest): """Tests can.BLFWriter and can.BLFReader. From 11cfac67c200a3e8b29cdf26e0d0b7967753920f Mon Sep 17 00:00:00 2001 From: Ged Date: Mon, 1 Dec 2025 19:08:42 +0100 Subject: [PATCH 3/5] Updated formatting of logformats_test.py --- test/logformats_test.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/logformats_test.py b/test/logformats_test.py index 2a15d093e..fa02c4e4f 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -690,10 +690,13 @@ def test_write(self): ("Sep 30 15:06:13.191 am 2017", 1506776773.191), ] ) - def test_datetime_to_timestamp(self, datetime_string: str, expected_timestamp: float): + def test_datetime_to_timestamp( + self, datetime_string: str, expected_timestamp: float + ): timestamp = can.ASCReader._datetime_to_timestamp(datetime_string) self.assertAlmostEqual(timestamp, expected_timestamp) + class TestBlfFileFormat(ReaderWriterTest): """Tests can.BLFWriter and can.BLFReader. From 4cf16ef3e3bb4260280601f9ccc243ab8db72e47 Mon Sep 17 00:00:00 2001 From: Ged Date: Mon, 1 Dec 2025 20:52:38 +0100 Subject: [PATCH 4/5] Updated test for cross-platform compatibility and (hopefully) fixed formatting for good now --- can/io/asc.py | 8 ++++---- test/logformats_test.py | 30 ++++++++++++++++++++++++------ 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index 0f5b5782a..fcf8fc5e4 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -116,7 +116,7 @@ def _extract_header(self) -> None: @staticmethod def _datetime_to_timestamp(datetime_string: str) -> float: - MONTH_MAP = { + month_map = { "jan": 1, "feb": 2, "mar": 3, @@ -135,7 +135,7 @@ def _datetime_to_timestamp(datetime_string: str) -> float: "dez": 12, } - DATETIME_FORMATS = ( + datetime_formats = ( "%m %d %I:%M:%S.%f %p %Y", "%m %d %I:%M:%S %p %Y", "%m %d %H:%M:%S.%f %Y", @@ -148,12 +148,12 @@ def _datetime_to_timestamp(datetime_string: str) -> float: month = datetime_string_parts[0].strip().lower() try: - datetime_string_parts[0] = f"{MONTH_MAP[month]:02d}" + datetime_string_parts[0] = f"{month_map[month]:02d}" except KeyError: raise ValueError(f"Unsupported month abbreviation: {month}") from None datetime_string = " ".join(datetime_string_parts) - for format_str in DATETIME_FORMATS: + for format_str in datetime_formats: try: return datetime.strptime(datetime_string, format_str).timestamp() except ValueError: diff --git a/test/logformats_test.py b/test/logformats_test.py index fa02c4e4f..dc5c0fdd7 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -682,12 +682,30 @@ def test_write(self): @parameterized.expand( [ - ("May 27 04:09:35.000 pm 2014", 1401199775.0), - ("Mai 27 04:09:35.000 pm 2014", 1401199775.0), - ("Apr 28 10:44:52.480 2022", 1651135492.48), - ("Sep 30 15:06:13.191 2017", 1506776773.191), - ("Sep 30 15:06:13.191 pm 2017", 1506776773.191), - ("Sep 30 15:06:13.191 am 2017", 1506776773.191), + ( + "May 27 04:09:35.000 pm 2014", + datetime(2014, 5, 27, 16, 9, 35, 0).timestamp(), + ), + ( + "Mai 27 04:09:35.000 pm 2014", + datetime(2014, 5, 27, 16, 9, 35, 0).timestamp(), + ), + ( + "Apr 28 10:44:52.480 2022", + datetime(2022, 4, 28, 10, 44, 52, 480).timestamp(), + ), + ( + "Sep 30 15:06:13.191 2017", + datetime(2017, 9, 30, 15, 6, 13, 191).timestamp(), + ), + ( + "Sep 30 15:06:13.191 pm 2017", + datetime(2017, 9, 30, 15, 6, 13, 191).timestamp(), + ), + ( + "Sep 30 15:06:13.191 am 2017", + datetime(2017, 9, 30, 15, 6, 13, 191).timestamp(), + ), ] ) def test_datetime_to_timestamp( From e68cfa31fa6f0ead59c695dec74123b0d8e1a98d Mon Sep 17 00:00:00 2001 From: Ged Date: Mon, 1 Dec 2025 21:40:52 +0100 Subject: [PATCH 5/5] Corrected micro- to milliseconds --- test/logformats_test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/logformats_test.py b/test/logformats_test.py index dc5c0fdd7..f8a8de91d 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -692,19 +692,19 @@ def test_write(self): ), ( "Apr 28 10:44:52.480 2022", - datetime(2022, 4, 28, 10, 44, 52, 480).timestamp(), + datetime(2022, 4, 28, 10, 44, 52, 480000).timestamp(), ), ( "Sep 30 15:06:13.191 2017", - datetime(2017, 9, 30, 15, 6, 13, 191).timestamp(), + datetime(2017, 9, 30, 15, 6, 13, 191000).timestamp(), ), ( "Sep 30 15:06:13.191 pm 2017", - datetime(2017, 9, 30, 15, 6, 13, 191).timestamp(), + datetime(2017, 9, 30, 15, 6, 13, 191000).timestamp(), ), ( "Sep 30 15:06:13.191 am 2017", - datetime(2017, 9, 30, 15, 6, 13, 191).timestamp(), + datetime(2017, 9, 30, 15, 6, 13, 191000).timestamp(), ), ] )