Skip to content

Commit 7fcc813

Browse files
authored
bugfix ASCReader (#1257)
1 parent f1a012c commit 7fcc813

File tree

3 files changed

+1556
-36
lines changed

3 files changed

+1556
-36
lines changed

can/io/asc.py

Lines changed: 92 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ class ASCReader(MessageReader):
3434

3535
file: TextIO
3636

37-
FORMAT_START_OF_FILE_DATE = "%a %b %d %I:%M:%S.%f %p %Y"
38-
3937
def __init__(
4038
self,
4139
file: Union[StringPathLike, TextIO],
@@ -60,51 +58,95 @@ def __init__(
6058
self.base = base
6159
self._converted_base = self._check_base(base)
6260
self.relative_timestamp = relative_timestamp
63-
self.date = None
61+
self.date: Optional[str] = None
62+
self.start_time = 0.0
6463
# TODO - what is this used for? The ASC Writer only prints `absolute`
65-
self.timestamps_format = None
66-
self.internal_events_logged = None
64+
self.timestamps_format: Optional[str] = None
65+
self.internal_events_logged = False
6766

68-
def _extract_header(self):
67+
def _extract_header(self) -> None:
6968
for line in self.file:
7069
line = line.strip()
71-
lower_case = line.lower()
72-
if lower_case.startswith("date"):
73-
self.date = line[5:]
74-
elif lower_case.startswith("base"):
75-
try:
76-
_, base, _, timestamp_format = line.split()
77-
except ValueError as exception:
78-
raise Exception(
79-
f"Unsupported header string format: {line}"
80-
) from exception
70+
71+
datetime_match = re.match(
72+
r"date\s+\w+\s+(?P<datetime_string>.+)", line, re.IGNORECASE
73+
)
74+
base_match = re.match(
75+
r"base\s+(?P<base>hex|dec)(?:\s+timestamps\s+"
76+
r"(?P<timestamp_format>absolute|relative))?",
77+
line,
78+
re.IGNORECASE,
79+
)
80+
comment_match = re.match(r"//.*", line)
81+
events_match = re.match(
82+
r"(?P<no_events>no)?\s*internal\s+events\s+logged", line, re.IGNORECASE
83+
)
84+
85+
if datetime_match:
86+
self.date = datetime_match.group("datetime_string")
87+
self.start_time = (
88+
0.0
89+
if self.relative_timestamp
90+
else self._datetime_to_timestamp(self.date)
91+
)
92+
continue
93+
94+
elif base_match:
95+
base = base_match.group("base")
96+
timestamp_format = base_match.group("timestamp_format")
8197
self.base = base
8298
self._converted_base = self._check_base(self.base)
83-
self.timestamps_format = timestamp_format
84-
elif lower_case.endswith("internal events logged"):
85-
self.internal_events_logged = not lower_case.startswith("no")
86-
elif lower_case.startswith("//"):
87-
# ignore comments
99+
self.timestamps_format = timestamp_format or "absolute"
88100
continue
89-
# grab absolute timestamp
90-
elif lower_case.startswith("begin triggerblock"):
91-
if self.relative_timestamp:
92-
self.start_time = 0.0
93-
else:
94-
try:
95-
_, _, start_time = lower_case.split(None, 2)
96-
start_time = datetime.strptime(
97-
start_time, self.FORMAT_START_OF_FILE_DATE
98-
).timestamp()
99-
except (ValueError, OSError):
100-
# `OSError` to handle non-POSIX capable timestamps
101-
start_time = 0.0
102-
self.start_time = start_time
103-
# Currently the last line in the header which is parsed
101+
102+
elif comment_match:
103+
continue
104+
105+
elif events_match:
106+
self.internal_events_logged = events_match.group("no_events") is None
104107
break
108+
105109
else:
106110
break
107111

112+
@staticmethod
113+
def _datetime_to_timestamp(datetime_string: str) -> float:
114+
# ugly locale independent solution
115+
month_map = {
116+
"Jan": 1,
117+
"Feb": 2,
118+
"Mar": 3,
119+
"Apr": 4,
120+
"May": 5,
121+
"Jun": 6,
122+
"Jul": 7,
123+
"Aug": 8,
124+
"Sep": 9,
125+
"Oct": 10,
126+
"Nov": 11,
127+
"Dec": 12,
128+
"Mär": 3,
129+
"Mai": 5,
130+
"Okt": 10,
131+
"Dez": 12,
132+
}
133+
for name, number in month_map.items():
134+
datetime_string = datetime_string.replace(name, str(number).zfill(2))
135+
136+
datetime_formats = (
137+
"%m %d %I:%M:%S.%f %p %Y",
138+
"%m %d %I:%M:%S %p %Y",
139+
"%m %d %H:%M:%S.%f %Y",
140+
"%m %d %H:%M:%S %Y",
141+
)
142+
for format_str in datetime_formats:
143+
try:
144+
return datetime.strptime(datetime_string, format_str).timestamp()
145+
except ValueError:
146+
continue
147+
148+
raise ValueError(f"Incompatible datetime string {datetime_string}")
149+
108150
def _extract_can_id(self, str_can_id: str, msg_kwargs: Dict[str, Any]) -> None:
109151
if str_can_id[-1:].lower() == "x":
110152
msg_kwargs["is_extended_id"] = True
@@ -219,6 +261,20 @@ def __iter__(self) -> Generator[Message, None, None]:
219261
for line in self.file:
220262
line = line.strip()
221263

264+
trigger_match = re.match(
265+
r"begin\s+triggerblock\s+\w+\s+(?P<datetime_string>.+)",
266+
line,
267+
re.IGNORECASE,
268+
)
269+
if trigger_match:
270+
datetime_str = trigger_match.group("datetime_string")
271+
self.start_time = (
272+
0.0
273+
if self.relative_timestamp
274+
else self._datetime_to_timestamp(datetime_str)
275+
)
276+
continue
277+
222278
if not re.match(
223279
r"\d+\.\d+\s+(\d+\s+(\w+\s+(Tx|Rx)|ErrorFrame)|CANFD)",
224280
line,

0 commit comments

Comments
 (0)