@@ -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