rm-date-parser 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dateparserpython/__init__.py +13 -0
- dateparserpython/dictionary.py +449 -0
- dateparserpython/display.py +22 -0
- dateparserpython/helper.py +169 -0
- dateparserpython/models.py +55 -0
- dateparserpython/parser.py +594 -0
- dateparserpython/prediction.py +37 -0
- dateparserpython/py.typed +0 -0
- rm_date_parser-0.1.0.dist-info/METADATA +81 -0
- rm_date_parser-0.1.0.dist-info/RECORD +13 -0
- rm_date_parser-0.1.0.dist-info/WHEEL +5 -0
- rm_date_parser-0.1.0.dist-info/licenses/LICENSE +21 -0
- rm_date_parser-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Iterable, List, Optional
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
from . import dictionary as Dictionary
|
|
8
|
+
from . import helper as Helper
|
|
9
|
+
from .models import DateElement, LocalDateModel
|
|
10
|
+
except ImportError: # pragma: no cover - fallback for direct module execution
|
|
11
|
+
from dateparserpython import dictionary as Dictionary
|
|
12
|
+
from dateparserpython import helper as Helper
|
|
13
|
+
from dateparserpython.models import DateElement, LocalDateModel
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Parser:
|
|
17
|
+
"""
|
|
18
|
+
Python port of the Java Parser class. The public API mirrors the Java
|
|
19
|
+
version: call parse(text) to receive a list of LocalDateModel instances.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self) -> None:
|
|
23
|
+
self.delim = "-.\\/|:, "
|
|
24
|
+
self.learnedPatternString: Optional[str] = None
|
|
25
|
+
self.learnPattern = False
|
|
26
|
+
self._tokenizer_cache: dict[str, re.Pattern[str]] = {}
|
|
27
|
+
|
|
28
|
+
def parse(self, text: str) -> List[LocalDateModel]:
|
|
29
|
+
date_groups: List[LocalDateModel] = []
|
|
30
|
+
groups = self.getDateGroups(text)
|
|
31
|
+
if not groups:
|
|
32
|
+
return date_groups
|
|
33
|
+
for element in groups:
|
|
34
|
+
localdate = self.getDateFromPhrase(element)
|
|
35
|
+
if localdate is None:
|
|
36
|
+
continue
|
|
37
|
+
localdate.start = element.startPos
|
|
38
|
+
localdate.end = element.endPos
|
|
39
|
+
if element.timeFragment:
|
|
40
|
+
localdate = self.putTimeInDate(localdate, element)
|
|
41
|
+
date_fragment = element.getDateFragment() or ""
|
|
42
|
+
delims = re.sub(r"[A-Za-z0-9]", "", date_fragment)
|
|
43
|
+
found_format = localdate.identified_date_format or ""
|
|
44
|
+
if len(delims) == 2:
|
|
45
|
+
found_format = found_format.replace("$", delims[0])
|
|
46
|
+
found_format = found_format.replace("&", delims[1])
|
|
47
|
+
if len(delims) == 3:
|
|
48
|
+
found_format = found_format.replace("$", delims[0])
|
|
49
|
+
found_format = found_format.replace("&", delims[1] + delims[2])
|
|
50
|
+
if element.isAlphaNumeric and Helper.isFullMonth(element.data):
|
|
51
|
+
found_format = found_format.replace("MMM", "MMMMM")
|
|
52
|
+
if element.hasAmPm:
|
|
53
|
+
found_format = f"{found_format} a"
|
|
54
|
+
localdate.identified_date_format = found_format
|
|
55
|
+
date_groups.append(localdate)
|
|
56
|
+
return date_groups
|
|
57
|
+
|
|
58
|
+
def _tokenize(self, text: str, delimiters: str) -> List[str]:
|
|
59
|
+
if delimiters not in self._tokenizer_cache:
|
|
60
|
+
pattern = "[" + re.escape(delimiters) + "]+"
|
|
61
|
+
self._tokenizer_cache[delimiters] = re.compile(pattern)
|
|
62
|
+
return [token for token in self._tokenizer_cache[delimiters].split(text) if token]
|
|
63
|
+
|
|
64
|
+
def getDateFromPhrase(self, element: DateElement) -> Optional[LocalDateModel]:
|
|
65
|
+
if element.timeFragment is None:
|
|
66
|
+
s = element.data
|
|
67
|
+
else:
|
|
68
|
+
s = element.dateFragment or element.data
|
|
69
|
+
if element.isAlphaNumeric:
|
|
70
|
+
tokens = self._tokenize(s, self.delim)
|
|
71
|
+
if len(tokens) < 3:
|
|
72
|
+
return None
|
|
73
|
+
t1, t2, t3 = tokens[:3]
|
|
74
|
+
if Helper.isDigit(t1):
|
|
75
|
+
year = int(t1)
|
|
76
|
+
if year > 31:
|
|
77
|
+
present_format = "yy$" if year < 99 else "yyyy$"
|
|
78
|
+
if Helper.isDigit(t2):
|
|
79
|
+
present_format = present_format + "dd&MMM"
|
|
80
|
+
day = int(t2)
|
|
81
|
+
month = self.monthToDigit(t3)
|
|
82
|
+
else:
|
|
83
|
+
present_format = present_format + "MMM&dd"
|
|
84
|
+
month = self.monthToDigit(t2)
|
|
85
|
+
day = int(t3)
|
|
86
|
+
local_date = self.getYyyyMmDdProbable(year, month, day)
|
|
87
|
+
if local_date:
|
|
88
|
+
local_date.original_text = element.data
|
|
89
|
+
local_date.identified_date_format = present_format
|
|
90
|
+
return local_date
|
|
91
|
+
if Helper.isDigit(t3):
|
|
92
|
+
year = int(t3)
|
|
93
|
+
if year > 31:
|
|
94
|
+
present_format = "yy" if year < 99 else "yyyy"
|
|
95
|
+
if Helper.isDigit(t1):
|
|
96
|
+
present_format = f"dd$MMM&{present_format}"
|
|
97
|
+
day = int(t1)
|
|
98
|
+
month = self.monthToDigit(t2)
|
|
99
|
+
else:
|
|
100
|
+
present_format = f"MMM$dd&{present_format}"
|
|
101
|
+
month = self.monthToDigit(t1)
|
|
102
|
+
day = int(t2)
|
|
103
|
+
local_date = self.getYyyyMmDdProbable(year, month, day)
|
|
104
|
+
if local_date:
|
|
105
|
+
local_date.original_text = element.data
|
|
106
|
+
local_date.identified_date_format = present_format
|
|
107
|
+
return local_date
|
|
108
|
+
return None
|
|
109
|
+
else:
|
|
110
|
+
if "T" in s or "_" in s:
|
|
111
|
+
return None
|
|
112
|
+
tokens = self._tokenize(s, self.delim)
|
|
113
|
+
if len(tokens) < 3:
|
|
114
|
+
return None
|
|
115
|
+
d1, d2, d3 = (int(token) for token in tokens[:3])
|
|
116
|
+
if d1 > 999:
|
|
117
|
+
local_date = self.getYyyyMmDdProbable(d1, d2, d3)
|
|
118
|
+
if local_date:
|
|
119
|
+
local_date.original_text = element.data
|
|
120
|
+
if self.learnPattern and self.learnedPatternString is not None:
|
|
121
|
+
self.learnedPatternString = local_date.con_date_format
|
|
122
|
+
return local_date
|
|
123
|
+
if 31 < d1 < 100:
|
|
124
|
+
local_date = self.getYyMmDdProbable(d1, d2, d3)
|
|
125
|
+
if local_date:
|
|
126
|
+
local_date.original_text = element.data
|
|
127
|
+
if self.learnPattern and self.learnedPatternString is not None:
|
|
128
|
+
self.learnedPatternString = local_date.con_date_format
|
|
129
|
+
return local_date
|
|
130
|
+
if d3 > 999 and ((0 < d1 < 32) or (0 < d2 < 32)) and d1 > 0 and d2 > 0:
|
|
131
|
+
local_date = self.getDetemintaionForYyyyPrefix(d1, d2, d3)
|
|
132
|
+
if local_date:
|
|
133
|
+
local_date.original_text = element.data
|
|
134
|
+
if self.learnPattern and self.learnedPatternString is not None:
|
|
135
|
+
self.learnedPatternString = local_date.con_date_format
|
|
136
|
+
return local_date
|
|
137
|
+
if 31 < d3 < 100 and ((0 < d1 < 32) or (0 < d2 < 32)):
|
|
138
|
+
local_date = self.getDetemintaionForYyyyPrefix(d1, d2, d3)
|
|
139
|
+
if local_date:
|
|
140
|
+
local_date.original_text = element.data
|
|
141
|
+
if self.learnPattern and self.learnedPatternString is not None:
|
|
142
|
+
self.learnedPatternString = local_date.con_date_format
|
|
143
|
+
return local_date
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
def putTimeInDate(self, localdate: LocalDateModel, element: DateElement) -> LocalDateModel:
|
|
147
|
+
format_string = "HH:mm:ss"
|
|
148
|
+
s = element.timeFragment or ""
|
|
149
|
+
probable_time_format = format_string
|
|
150
|
+
tokens: Iterable[str]
|
|
151
|
+
if "." in s or "," in s:
|
|
152
|
+
tokens = self._tokenize(s, ":., ")
|
|
153
|
+
hour, minute, second, millis = (int(token) for token in tokens)
|
|
154
|
+
if "." in s:
|
|
155
|
+
probable_time_format = format_string + ".SSS"
|
|
156
|
+
if "," in s:
|
|
157
|
+
probable_time_format = format_string + ",SSS"
|
|
158
|
+
else:
|
|
159
|
+
tokens = self._tokenize(s, ": ")
|
|
160
|
+
hour, minute, second = (int(token) for token in tokens)
|
|
161
|
+
millis = 0
|
|
162
|
+
if element.hasAmPm:
|
|
163
|
+
if "pm" in s.lower():
|
|
164
|
+
hour = hour + 12
|
|
165
|
+
probable_time_format = f"{probable_time_format} aaa"
|
|
166
|
+
format_string = format_string.replace("HH", "hh")
|
|
167
|
+
probable_time_format = probable_time_format.replace("HH", "hh")
|
|
168
|
+
if hour < 24 and minute < 60 and second < 60 and millis < 10000:
|
|
169
|
+
time_piece = f"{hour:02d}:{minute:02d}:{second:02d}"
|
|
170
|
+
if millis >= 0 and ("." in s or "," in s):
|
|
171
|
+
time_piece = f"{time_piece}.{millis:03d}"
|
|
172
|
+
localdate.con_date_format = f"{localdate.con_date_format} {format_string}"
|
|
173
|
+
localdate.date_time_string = f"{localdate.date_time_string} {time_piece}"
|
|
174
|
+
sep = "'T'" if element.dateTimeSeprator == "T" else element.dateTimeSeprator
|
|
175
|
+
identified = localdate.identified_date_format or ""
|
|
176
|
+
localdate.identified_date_format = f"{identified}{sep}{probable_time_format}"
|
|
177
|
+
return localdate
|
|
178
|
+
|
|
179
|
+
def getDetemintaionForYyPrefix(self, d1: int, d2: int, pYear: int) -> Optional[LocalDateModel]:
|
|
180
|
+
return self.getDetemintaionForYyyyPrefix(d1, d2, pYear)
|
|
181
|
+
|
|
182
|
+
def getDetemintaionForYyyyPrefix(self, d1: int, d2: int, pYear: int) -> Optional[LocalDateModel]:
|
|
183
|
+
if d1 > 12 and 0 < d2 <= 12:
|
|
184
|
+
p_date = d1
|
|
185
|
+
p_month = d2
|
|
186
|
+
local_date = self.getYyyyMmDdProbable(pYear, p_month, p_date)
|
|
187
|
+
if local_date:
|
|
188
|
+
if 9 < pYear < 100:
|
|
189
|
+
local_date.identified_date_format = "dd$MM&yy"
|
|
190
|
+
if 99 < pYear < 10000:
|
|
191
|
+
local_date.identified_date_format = "dd$MM&yyyy"
|
|
192
|
+
return local_date
|
|
193
|
+
if d2 > 12 and 0 < d1 <= 12:
|
|
194
|
+
p_date = d2
|
|
195
|
+
p_month = d1
|
|
196
|
+
local_date = self.getYyyyMmDdProbable(pYear, p_month, p_date)
|
|
197
|
+
if local_date:
|
|
198
|
+
if 9 < pYear < 100:
|
|
199
|
+
local_date.identified_date_format = "MM$dd&yy"
|
|
200
|
+
if 99 < pYear < 10000:
|
|
201
|
+
local_date.identified_date_format = "MM$dd&yyyy"
|
|
202
|
+
return local_date
|
|
203
|
+
if d1 <= 12 and d2 <= 12:
|
|
204
|
+
p_month = d1
|
|
205
|
+
p_date = d2
|
|
206
|
+
local_date = self.getYyyyMmDdProbable(pYear, p_month, p_date)
|
|
207
|
+
if local_date:
|
|
208
|
+
if 9 < pYear < 100:
|
|
209
|
+
local_date.identified_date_format = "MM$dd&yy"
|
|
210
|
+
if 99 < pYear < 10000:
|
|
211
|
+
local_date.identified_date_format = "MM$dd&yyyy"
|
|
212
|
+
return local_date
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
def getYyMmDdProbable(self, pYear: int, pMonth: int, pDay: int) -> Optional[LocalDateModel]:
|
|
216
|
+
return self.getYyyyMmDdProbable(pYear, pMonth, pDay)
|
|
217
|
+
|
|
218
|
+
def getYyyyMmDdProbable(self, pYear: int, pMonth: int, pDay: int) -> Optional[LocalDateModel]:
|
|
219
|
+
year = pYear
|
|
220
|
+
month = -100
|
|
221
|
+
day = -100
|
|
222
|
+
format_probable = ""
|
|
223
|
+
if 0 < pMonth < 13:
|
|
224
|
+
month = pMonth
|
|
225
|
+
format_probable = "MM&dd"
|
|
226
|
+
elif pMonth > 12 and pDay < 13:
|
|
227
|
+
month = pDay
|
|
228
|
+
pDay = pMonth
|
|
229
|
+
format_probable = "dd&MM"
|
|
230
|
+
else:
|
|
231
|
+
return None
|
|
232
|
+
if month == 2:
|
|
233
|
+
if year % 4 > 0 and pDay < 29:
|
|
234
|
+
day = pDay
|
|
235
|
+
elif year % 4 == 0 and pDay < 30:
|
|
236
|
+
day = pDay
|
|
237
|
+
else:
|
|
238
|
+
return None
|
|
239
|
+
elif self.is31DayMonth(month):
|
|
240
|
+
if pDay < 32:
|
|
241
|
+
day = pDay
|
|
242
|
+
else:
|
|
243
|
+
return None
|
|
244
|
+
elif self.is30DayMonth(month):
|
|
245
|
+
if pDay < 31:
|
|
246
|
+
day = pDay
|
|
247
|
+
else:
|
|
248
|
+
return None
|
|
249
|
+
else:
|
|
250
|
+
return None
|
|
251
|
+
localdate = LocalDateModel()
|
|
252
|
+
localdate.date_time_string = f"{year:04d}-{month:02d}-{day:02d}"
|
|
253
|
+
localdate.con_date_format = "yyyy-MM-dd"
|
|
254
|
+
if 9 < year < 100:
|
|
255
|
+
format_probable = "yy$" + format_probable
|
|
256
|
+
if 999 < year < 10000:
|
|
257
|
+
format_probable = "yyyy$" + format_probable
|
|
258
|
+
localdate.identified_date_format = format_probable
|
|
259
|
+
return localdate
|
|
260
|
+
|
|
261
|
+
def getDateGroups(self, text: str) -> Optional[List[DateElement]]:
|
|
262
|
+
date_groups: Optional[List[DateElement]] = None
|
|
263
|
+
possible_date = ["\x00"] * 30
|
|
264
|
+
possible_time = ["\x00"] * 17
|
|
265
|
+
i = 0
|
|
266
|
+
end_found_earlier = False
|
|
267
|
+
month_determined = False
|
|
268
|
+
search_for_time_piece = False
|
|
269
|
+
time_frg_length = 0
|
|
270
|
+
marker = "0"
|
|
271
|
+
is_alphanumeric = False
|
|
272
|
+
date_time_separator = " "
|
|
273
|
+
whitespace_count = 0
|
|
274
|
+
tree = Dictionary.patternPredictionTree
|
|
275
|
+
month = Dictionary.monthPredictionTree
|
|
276
|
+
time = Dictionary.timePredictionTree
|
|
277
|
+
text_length = len(text)
|
|
278
|
+
for count in range(text_length):
|
|
279
|
+
c = text[count]
|
|
280
|
+
if i == 0:
|
|
281
|
+
tree = Dictionary.patternPredictionTree
|
|
282
|
+
is_alphanumeric = False
|
|
283
|
+
if i > 1 and possible_date[i - 1] == " " and c == " ":
|
|
284
|
+
whitespace_count += 1
|
|
285
|
+
continue
|
|
286
|
+
if search_for_time_piece:
|
|
287
|
+
time_determined = time.explict_date_fragment
|
|
288
|
+
if time_frg_length > 12 and possible_time[time_frg_length - 1] == " ":
|
|
289
|
+
time_determined = True
|
|
290
|
+
if Helper.isDigit(c):
|
|
291
|
+
time = time.get_child("D")
|
|
292
|
+
else:
|
|
293
|
+
time = time.get_child(c.lower())
|
|
294
|
+
if time is None:
|
|
295
|
+
if time_determined:
|
|
296
|
+
date_groups = self.addTimeFragment(
|
|
297
|
+
date_groups, possible_date, possible_time, count, i, time_frg_length, date_time_separator
|
|
298
|
+
)
|
|
299
|
+
if time_frg_length > 0 and self.isValidTimeFragmentWithEndingDelim(possible_time):
|
|
300
|
+
possible_time[time_frg_length - 1] = " "
|
|
301
|
+
if i > 0:
|
|
302
|
+
possible_date[i - 1] = " "
|
|
303
|
+
date_groups = self.addTimeFragment(
|
|
304
|
+
date_groups, possible_date, possible_time, count, i - 1, time_frg_length, date_time_separator
|
|
305
|
+
)
|
|
306
|
+
search_for_time_piece = False
|
|
307
|
+
possible_date = self.nullifyBuffer(possible_date)
|
|
308
|
+
possible_time = self.nullifyBuffer(possible_time)
|
|
309
|
+
end_found_earlier = False
|
|
310
|
+
time = Dictionary.timePredictionTree
|
|
311
|
+
i = 0
|
|
312
|
+
whitespace_count = 0
|
|
313
|
+
time_frg_length = 0
|
|
314
|
+
continue
|
|
315
|
+
else:
|
|
316
|
+
if time_determined:
|
|
317
|
+
if count == text_length - 1:
|
|
318
|
+
date_groups = self.addTimeFragment(
|
|
319
|
+
date_groups, possible_date, possible_time, count, i, time_frg_length, date_time_separator
|
|
320
|
+
)
|
|
321
|
+
search_for_time_piece = False
|
|
322
|
+
possible_date = self.nullifyBuffer(possible_date)
|
|
323
|
+
possible_time = self.nullifyBuffer(possible_time)
|
|
324
|
+
end_found_earlier = False
|
|
325
|
+
time = Dictionary.timePredictionTree
|
|
326
|
+
i = 0
|
|
327
|
+
whitespace_count = 0
|
|
328
|
+
time_frg_length = 0
|
|
329
|
+
continue
|
|
330
|
+
if c == " ":
|
|
331
|
+
try:
|
|
332
|
+
next_chars = text[count + 1 : count + 3]
|
|
333
|
+
if next_chars.lower() in {"am", "pm"}:
|
|
334
|
+
possible_time[time_frg_length] = c
|
|
335
|
+
time_frg_length += 1
|
|
336
|
+
possible_date[i] = c
|
|
337
|
+
i += 1
|
|
338
|
+
|
|
339
|
+
possible_time[time_frg_length] = text[count + 1]
|
|
340
|
+
time_frg_length += 1
|
|
341
|
+
possible_date[i] = text[count + 1]
|
|
342
|
+
i += 1
|
|
343
|
+
|
|
344
|
+
possible_time[time_frg_length] = text[count + 2]
|
|
345
|
+
time_frg_length += 1
|
|
346
|
+
possible_date[i] = text[count + 2]
|
|
347
|
+
i += 1
|
|
348
|
+
except IndexError:
|
|
349
|
+
pass
|
|
350
|
+
date_groups = self.addTimeFragment(
|
|
351
|
+
date_groups, possible_date, possible_time, count, i, time_frg_length, date_time_separator
|
|
352
|
+
)
|
|
353
|
+
search_for_time_piece = False
|
|
354
|
+
possible_date = self.nullifyBuffer(possible_date)
|
|
355
|
+
possible_time = self.nullifyBuffer(possible_time)
|
|
356
|
+
end_found_earlier = False
|
|
357
|
+
time = Dictionary.timePredictionTree
|
|
358
|
+
i = 0
|
|
359
|
+
whitespace_count = 0
|
|
360
|
+
time_frg_length = 0
|
|
361
|
+
continue
|
|
362
|
+
else:
|
|
363
|
+
if count == text_length - 1 and time.explict_date_fragment:
|
|
364
|
+
if Helper.isDigit(c):
|
|
365
|
+
possible_time[time_frg_length] = c
|
|
366
|
+
time_frg_length += 1
|
|
367
|
+
possible_date[i] = c
|
|
368
|
+
i += 1
|
|
369
|
+
date_groups = self.addTimeFragment(
|
|
370
|
+
date_groups, possible_date, possible_time, count, i, time_frg_length, date_time_separator
|
|
371
|
+
)
|
|
372
|
+
search_for_time_piece = False
|
|
373
|
+
possible_date = self.nullifyBuffer(possible_date)
|
|
374
|
+
possible_time = self.nullifyBuffer(possible_time)
|
|
375
|
+
end_found_earlier = False
|
|
376
|
+
time = Dictionary.timePredictionTree
|
|
377
|
+
i = 0
|
|
378
|
+
whitespace_count = 0
|
|
379
|
+
time_frg_length = 0
|
|
380
|
+
continue
|
|
381
|
+
possible_time[time_frg_length] = c
|
|
382
|
+
time_frg_length += 1
|
|
383
|
+
possible_date[i] = c
|
|
384
|
+
i += 1
|
|
385
|
+
continue
|
|
386
|
+
if Helper.isDigit(c) or Helper.isDelimeter(c) or Helper.isTimeSeprator(c):
|
|
387
|
+
if marker == "M":
|
|
388
|
+
if month_determined:
|
|
389
|
+
is_alphanumeric = True
|
|
390
|
+
tree = tree.get_child(marker)
|
|
391
|
+
if tree is None and end_found_earlier:
|
|
392
|
+
continue
|
|
393
|
+
else:
|
|
394
|
+
tree = Dictionary.patternPredictionTree
|
|
395
|
+
possible_date = self.nullifyBuffer(possible_date)
|
|
396
|
+
i = 0
|
|
397
|
+
whitespace_count = 0
|
|
398
|
+
end_found_earlier = False
|
|
399
|
+
month = Dictionary.monthPredictionTree
|
|
400
|
+
marker = "D" if Helper.isDigit(c) else "*"
|
|
401
|
+
if tree is None:
|
|
402
|
+
if end_found_earlier:
|
|
403
|
+
date_groups = self.addDateFragment(
|
|
404
|
+
date_groups, possible_date, count, time_frg_length + i + whitespace_count, is_alphanumeric
|
|
405
|
+
)
|
|
406
|
+
is_alphanumeric = False
|
|
407
|
+
if Helper.isDigit(c):
|
|
408
|
+
time = time.get_child("D")
|
|
409
|
+
if time is not None:
|
|
410
|
+
possible_time[time_frg_length] = c
|
|
411
|
+
time_frg_length += 1
|
|
412
|
+
search_for_time_piece = True
|
|
413
|
+
possible_date[i] = c
|
|
414
|
+
i += 1
|
|
415
|
+
else:
|
|
416
|
+
possible_date = self.nullifyBuffer(possible_date)
|
|
417
|
+
i = 0
|
|
418
|
+
whitespace_count = 0
|
|
419
|
+
end_found_earlier = False
|
|
420
|
+
else:
|
|
421
|
+
possible_date = self.nullifyBuffer(possible_date)
|
|
422
|
+
i = 0
|
|
423
|
+
whitespace_count = 0
|
|
424
|
+
end_found_earlier = False
|
|
425
|
+
end_found_earlier = False
|
|
426
|
+
tree = Dictionary.patternPredictionTree
|
|
427
|
+
month = Dictionary.monthPredictionTree
|
|
428
|
+
continue
|
|
429
|
+
else:
|
|
430
|
+
possible_date = self.nullifyBuffer(possible_date)
|
|
431
|
+
i = 0
|
|
432
|
+
whitespace_count = 0
|
|
433
|
+
end_found_earlier = False
|
|
434
|
+
tree = Dictionary.patternPredictionTree
|
|
435
|
+
month = Dictionary.monthPredictionTree
|
|
436
|
+
continue
|
|
437
|
+
tree = tree.get_child(marker)
|
|
438
|
+
if tree is None:
|
|
439
|
+
if end_found_earlier:
|
|
440
|
+
date_groups = self.addDateFragment(
|
|
441
|
+
date_groups, possible_date, count, time_frg_length + i + whitespace_count, is_alphanumeric
|
|
442
|
+
)
|
|
443
|
+
end_found_earlier = False
|
|
444
|
+
is_alphanumeric = False
|
|
445
|
+
if Helper.isTimeSeprator(c):
|
|
446
|
+
search_for_time_piece = True
|
|
447
|
+
possible_date[i] = c
|
|
448
|
+
i += 1
|
|
449
|
+
date_time_separator = c
|
|
450
|
+
else:
|
|
451
|
+
possible_date = self.nullifyBuffer(possible_date)
|
|
452
|
+
i = 0
|
|
453
|
+
whitespace_count = 0
|
|
454
|
+
tree = Dictionary.patternPredictionTree
|
|
455
|
+
month = Dictionary.monthPredictionTree
|
|
456
|
+
continue
|
|
457
|
+
else:
|
|
458
|
+
possible_date[i] = c
|
|
459
|
+
i += 1
|
|
460
|
+
end_found_earlier = tree.explict_date_fragment
|
|
461
|
+
if count == text_length - 1 and end_found_earlier:
|
|
462
|
+
date_groups = self.addDateFragment(
|
|
463
|
+
date_groups, possible_date, count, time_frg_length + i + whitespace_count, is_alphanumeric
|
|
464
|
+
)
|
|
465
|
+
is_alphanumeric = False
|
|
466
|
+
break
|
|
467
|
+
else:
|
|
468
|
+
marker = "M"
|
|
469
|
+
c_lower = c.lower()
|
|
470
|
+
month = month.get_child(c_lower)
|
|
471
|
+
if month is None:
|
|
472
|
+
if end_found_earlier:
|
|
473
|
+
date_groups = self.addDateFragment(
|
|
474
|
+
date_groups, possible_date, count, time_frg_length + i + whitespace_count, is_alphanumeric
|
|
475
|
+
)
|
|
476
|
+
is_alphanumeric = False
|
|
477
|
+
end_found_earlier = False
|
|
478
|
+
possible_date = self.nullifyBuffer(possible_date)
|
|
479
|
+
i = 0
|
|
480
|
+
whitespace_count = 0
|
|
481
|
+
month = Dictionary.monthPredictionTree
|
|
482
|
+
tree = Dictionary.patternPredictionTree
|
|
483
|
+
month_determined = False
|
|
484
|
+
end_found_earlier = False
|
|
485
|
+
continue
|
|
486
|
+
else:
|
|
487
|
+
month_determined = month.explict_date_fragment
|
|
488
|
+
possible_date[i] = c_lower
|
|
489
|
+
i += 1
|
|
490
|
+
return date_groups
|
|
491
|
+
|
|
492
|
+
def addDateFragment(
|
|
493
|
+
self,
|
|
494
|
+
date_groups: Optional[List[DateElement]],
|
|
495
|
+
possible_date: List[str],
|
|
496
|
+
count: int,
|
|
497
|
+
pattern_length: int,
|
|
498
|
+
is_alphanumeric: bool,
|
|
499
|
+
) -> Optional[List[DateElement]]:
|
|
500
|
+
if date_groups is None:
|
|
501
|
+
date_groups = []
|
|
502
|
+
date_element = self.createDateFragment(possible_date, count, pattern_length, is_alphanumeric)
|
|
503
|
+
if date_element is not None:
|
|
504
|
+
date_groups.append(date_element)
|
|
505
|
+
return date_groups
|
|
506
|
+
|
|
507
|
+
def isValidTimeFragmentWithEndingDelim(self, possible_time: List[str]) -> bool:
|
|
508
|
+
s = self._buffer_to_string(possible_time)
|
|
509
|
+
original = s
|
|
510
|
+
s = re.sub(r"[0-9]", "", s)
|
|
511
|
+
if 2 < len(s) <= 3:
|
|
512
|
+
if s.startswith("::") and (original.endswith(",") or original.endswith(".")):
|
|
513
|
+
return True
|
|
514
|
+
return False
|
|
515
|
+
|
|
516
|
+
def addTimeFragment(
|
|
517
|
+
self,
|
|
518
|
+
date_groups: Optional[List[DateElement]],
|
|
519
|
+
possible_date: List[str],
|
|
520
|
+
possible_time: List[str],
|
|
521
|
+
count: int,
|
|
522
|
+
i: int,
|
|
523
|
+
time_frg_length: int,
|
|
524
|
+
date_time_separator: str,
|
|
525
|
+
) -> Optional[List[DateElement]]:
|
|
526
|
+
if not date_groups:
|
|
527
|
+
return date_groups
|
|
528
|
+
ele = date_groups[-1]
|
|
529
|
+
ele.data = self._buffer_to_string(possible_date)
|
|
530
|
+
ele.endPos = count
|
|
531
|
+
ele.timeFragment = self._buffer_to_string(possible_time)
|
|
532
|
+
if time_frg_length >= 2:
|
|
533
|
+
ampm = f"{possible_time[time_frg_length - 2]}{possible_time[time_frg_length - 1]}"
|
|
534
|
+
if ampm.lower() in {"am", "pm"}:
|
|
535
|
+
ele.hasAmPm = True
|
|
536
|
+
ele.dateTimeSeprator = date_time_separator
|
|
537
|
+
return date_groups
|
|
538
|
+
|
|
539
|
+
def createDateFragment(
|
|
540
|
+
self, buffer: List[str], position: int, length: int, is_alphanumeric: bool
|
|
541
|
+
) -> Optional[DateElement]:
|
|
542
|
+
date_text = self._buffer_to_string(buffer)
|
|
543
|
+
if not date_text:
|
|
544
|
+
return None
|
|
545
|
+
ele = DateElement(date_text)
|
|
546
|
+
length -= 1
|
|
547
|
+
ele.endPos = position
|
|
548
|
+
ele.startPos = position - length
|
|
549
|
+
ele.isAlphaNumeric = is_alphanumeric
|
|
550
|
+
ele.dateFragment = ele.data
|
|
551
|
+
return ele
|
|
552
|
+
|
|
553
|
+
def nullifyBuffer(self, buffer: List[str]) -> List[str]:
|
|
554
|
+
for idx in range(len(buffer)):
|
|
555
|
+
buffer[idx] = "\x00"
|
|
556
|
+
return buffer
|
|
557
|
+
|
|
558
|
+
def _buffer_to_string(self, buffer: List[str]) -> str:
|
|
559
|
+
return "".join(ch for ch in buffer if ch and ch != "\x00").strip()
|
|
560
|
+
|
|
561
|
+
def is31DayMonth(self, value: int) -> bool:
|
|
562
|
+
return value in {1, 3, 5, 7, 8, 10, 12}
|
|
563
|
+
|
|
564
|
+
def is30DayMonth(self, value: int) -> bool:
|
|
565
|
+
return value in {4, 6, 9, 11}
|
|
566
|
+
|
|
567
|
+
def monthToDigit(self, text: str) -> int:
|
|
568
|
+
text = text.lower()
|
|
569
|
+
month_map = {
|
|
570
|
+
"jan": 1,
|
|
571
|
+
"january": 1,
|
|
572
|
+
"feb": 2,
|
|
573
|
+
"february": 2,
|
|
574
|
+
"mar": 3,
|
|
575
|
+
"march": 3,
|
|
576
|
+
"apr": 4,
|
|
577
|
+
"april": 4,
|
|
578
|
+
"may": 5,
|
|
579
|
+
"jun": 6,
|
|
580
|
+
"june": 6,
|
|
581
|
+
"jul": 7,
|
|
582
|
+
"july": 7,
|
|
583
|
+
"aug": 8,
|
|
584
|
+
"august": 8,
|
|
585
|
+
"sep": 9,
|
|
586
|
+
"september": 9,
|
|
587
|
+
"oct": 10,
|
|
588
|
+
"october": 10,
|
|
589
|
+
"nov": 11,
|
|
590
|
+
"november": 11,
|
|
591
|
+
"dec": 12,
|
|
592
|
+
"december": 12,
|
|
593
|
+
}
|
|
594
|
+
return month_map.get(text, -1)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class PredictionModelNode:
|
|
9
|
+
"""
|
|
10
|
+
Python equivalent of the Java PredictionModelNode used by the parser's
|
|
11
|
+
dictionary tries.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
charcter: str = "\x00"
|
|
15
|
+
level: int = 0
|
|
16
|
+
is_digit: bool = False
|
|
17
|
+
explict_date_fragment: bool = False
|
|
18
|
+
parent: Optional["PredictionModelNode"] = None
|
|
19
|
+
childern: List["PredictionModelNode"] = field(default_factory=list)
|
|
20
|
+
|
|
21
|
+
def get_child(self, char: str) -> Optional["PredictionModelNode"]:
|
|
22
|
+
for child in self.childern:
|
|
23
|
+
if child.charcter == char:
|
|
24
|
+
return child
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
def add_child(self, child: "PredictionModelNode") -> None:
|
|
28
|
+
child.level = self.level + 1
|
|
29
|
+
child.parent = self
|
|
30
|
+
self.childern.append(child)
|
|
31
|
+
|
|
32
|
+
def has_childern(self) -> bool:
|
|
33
|
+
return bool(self.childern)
|
|
34
|
+
|
|
35
|
+
def children_count(self) -> int:
|
|
36
|
+
return len(self.childern)
|
|
37
|
+
|
|
File without changes
|