Skip to content

Commit 5f2824f

Browse files
committed
feat(asc): add timestamps_format parameter to ASCWriter
Allow callers to choose between 'absolute' (default, existing behaviour) and 'relative' when creating an ASC log file. The value is written into the 'base hex timestamps ...' header line so that other tools (CANalyzer, CANoe, etc.) can interpret the file correctly. Closes #2022
1 parent 10ba2eb commit 5f2824f

2 files changed

Lines changed: 60 additions & 1 deletion

File tree

can/io/asc.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ def __init__(
358358
self,
359359
file: StringPathLike | TextIO,
360360
channel: int = 1,
361+
timestamps_format: str = "absolute",
361362
**kwargs: Any,
362363
) -> None:
363364
"""
@@ -366,7 +367,21 @@ def __init__(
366367
write mode, not binary write mode.
367368
:param channel: a default channel to use when the message does not
368369
have a channel set
370+
:param timestamps_format: the format of timestamps in the header.
371+
Use ``"absolute"`` (default) so that readers can recover
372+
the original wall-clock timestamps by combining the
373+
per-message offset with the trigger-block start time.
374+
Use ``"relative"`` when only the elapsed time from the
375+
start of the recording matters and no absolute time
376+
recovery is needed.
377+
:raises ValueError: if *timestamps_format* is not ``"absolute"`` or
378+
``"relative"``
369379
"""
380+
if timestamps_format not in ("absolute", "relative"):
381+
raise ValueError(
382+
f"timestamps_format must be 'absolute' or 'relative', "
383+
f"got {timestamps_format!r}"
384+
)
370385
if kwargs.get("append", False):
371386
raise ValueError(
372387
f"{self.__class__.__name__} is currently not equipped to "
@@ -375,11 +390,12 @@ def __init__(
375390
super().__init__(file, mode="w")
376391

377392
self.channel = channel
393+
self.timestamps_format = timestamps_format
378394

379395
# write start of file header
380396
start_time = self._format_header_datetime(datetime.now())
381397
self.file.write(f"date {start_time}\n")
382-
self.file.write("base hex timestamps absolute\n")
398+
self.file.write(f"base hex timestamps {self.timestamps_format}\n")
383399
self.file.write("internal events logged\n")
384400

385401
# the last part is written with the timestamp of the first message

test/logformats_test.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,49 @@ def test_write(self):
680680

681681
self.assertEqual(expected_file.read_text(), actual_file.read_text())
682682

683+
def test_write_timestamps_format_default_is_absolute(self):
684+
"""ASCWriter should write 'timestamps absolute' in the header by default."""
685+
with can.ASCWriter(self.test_file_name) as writer:
686+
pass
687+
688+
content = Path(self.test_file_name).read_text()
689+
self.assertIn("timestamps absolute", content)
690+
691+
def test_write_timestamps_format_relative(self):
692+
"""ASCWriter should write 'timestamps relative' when requested."""
693+
with can.ASCWriter(self.test_file_name, timestamps_format="relative") as writer:
694+
pass
695+
696+
content = Path(self.test_file_name).read_text()
697+
self.assertIn("timestamps relative", content)
698+
self.assertNotIn("timestamps absolute", content)
699+
700+
def test_write_timestamps_format_invalid(self):
701+
"""ASCWriter should raise ValueError for an unsupported timestamps_format."""
702+
with self.assertRaises(ValueError):
703+
can.ASCWriter(self.test_file_name, timestamps_format="unix")
704+
705+
def test_write_relative_timestamp_roundtrip(self):
706+
"""Messages written with relative format round-trip with relative timestamps."""
707+
msgs = [
708+
can.Message(timestamp=100.0, arbitration_id=0x1, data=b"\x01"),
709+
can.Message(timestamp=100.5, arbitration_id=0x2, data=b"\x02"),
710+
]
711+
712+
with can.ASCWriter(
713+
self.test_file_name, timestamps_format="relative"
714+
) as writer:
715+
for m in msgs:
716+
writer.on_message_received(m)
717+
718+
with can.ASCReader(self.test_file_name, relative_timestamp=True) as reader:
719+
result = list(reader)
720+
721+
self.assertEqual(len(result), len(msgs))
722+
# With relative_timestamp=True timestamps are offsets from the first message
723+
self.assertAlmostEqual(result[0].timestamp, 0.0, places=5)
724+
self.assertAlmostEqual(result[1].timestamp, 0.5, places=5)
725+
683726
@parameterized.expand(
684727
[
685728
(

0 commit comments

Comments
 (0)