whichtime_sys/parsers/pt/
time_expression.rs1use crate::components::Component;
10use crate::context::ParsingContext;
11use crate::error::Result;
12use crate::parsers::Parser;
13use crate::results::ParsedResult;
14use crate::types::Meridiem;
15use fancy_regex::Regex;
16use std::sync::LazyLock;
17
18static PRIMARY_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
25 Regex::new(
26 r"(?ix)
27 (?<!\d[:\.])(?<!\w)
28 (?:de\s+)?
29 (?:às|as|a|ao)?\s*
30 (?P<hour1>\d{1,2})
31 (?:[:\.](?P<minute1>\d{2}))?
32 (?:[:\.](?P<second1>\d{2}))?
33 (?:\s*(?P<meridiem1>a\.?m\.?|p\.?m\.?))?
34 (?:
35 \s*(?:a|às|as|[\-–~])\s*
36 (?P<hour2>\d{1,2})
37 (?:[:\.](?P<minute2>\d{2}))?
38 (?:[:\.](?P<second2>\d{2}))?
39 (?:\s*(?P<meridiem2>a\.?m\.?|p\.?m\.?))?
40 )?
41 (?=\W|$)
42 ",
43 )
44 .unwrap()
45});
46
47pub struct PTTimeExpressionParser;
49
50impl PTTimeExpressionParser {
51 pub fn new() -> Self {
52 Self
53 }
54
55 fn parse_meridiem(s: &str) -> Option<Meridiem> {
56 let lower = s.to_lowercase();
57 if lower.starts_with('p') {
58 Some(Meridiem::PM)
59 } else if lower.starts_with('a') {
60 Some(Meridiem::AM)
61 } else {
62 None
63 }
64 }
65
66 fn adjust_hour(hour: i32, meridiem: Option<Meridiem>) -> i32 {
67 match meridiem {
68 Some(Meridiem::PM) => {
69 if hour < 12 {
70 hour + 12
71 } else {
72 hour
73 }
74 }
75 Some(Meridiem::AM) => {
76 if hour == 12 {
77 0
78 } else {
79 hour
80 }
81 }
82 None => hour,
83 }
84 }
85}
86
87impl Parser for PTTimeExpressionParser {
88 fn name(&self) -> &'static str {
89 "PTTimeExpressionParser"
90 }
91
92 fn should_apply(&self, context: &ParsingContext) -> bool {
93 let text = context.text;
94 text.bytes().any(|b| b.is_ascii_digit())
95 }
96
97 fn parse(&self, context: &ParsingContext) -> Result<Vec<ParsedResult>> {
98 let mut results = Vec::new();
99 let ref_date = context.reference.instant;
100
101 let mut start = 0;
102 while start < context.text.len() {
103 let search_text = &context.text[start..];
104 let captures = match PRIMARY_PATTERN.captures(search_text) {
105 Ok(Some(caps)) => caps,
106 Ok(None) => break,
107 Err(_) => break,
108 };
109
110 let full_match = match captures.get(0) {
111 Some(m) => m,
112 None => break,
113 };
114
115 let match_start = start + full_match.start();
116 let match_end = start + full_match.end();
117 let matched_text = full_match.as_str();
118
119 let hour1: i32 = captures
120 .name("hour1")
121 .and_then(|m| m.as_str().parse().ok())
122 .unwrap_or(0);
123 let minute1: i32 = captures
124 .name("minute1")
125 .and_then(|m| m.as_str().parse().ok())
126 .unwrap_or(0);
127 let second1: i32 = captures
128 .name("second1")
129 .and_then(|m| m.as_str().parse().ok())
130 .unwrap_or(0);
131 let meridiem1 = captures
132 .name("meridiem1")
133 .map(|m| m.as_str())
134 .and_then(Self::parse_meridiem);
135
136 if hour1 > 23 {
138 start = match_end;
139 continue;
140 }
141
142 let hour2_opt = captures.name("hour2").and_then(|m| m.as_str().parse().ok());
143 let minute2: i32 = captures
144 .name("minute2")
145 .and_then(|m| m.as_str().parse().ok())
146 .unwrap_or(0);
147 let second2: i32 = captures
148 .name("second2")
149 .and_then(|m| m.as_str().parse().ok())
150 .unwrap_or(0);
151 let meridiem2 = captures
152 .name("meridiem2")
153 .map(|m| m.as_str())
154 .and_then(Self::parse_meridiem);
155
156 let has_context_prefix = matched_text.to_lowercase().contains("às")
160 || matched_text.to_lowercase().contains("as")
161 || matched_text.to_lowercase().contains("de ")
162 || matched_text.to_lowercase().starts_with("a "); let has_time_separator = matched_text.contains(':')
165 || (matched_text.contains('.')
166 && matched_text
167 .chars()
168 .any(|c| c == 'a' || c == 'p' || c == 'A' || c == 'P')); let has_meridiem = meridiem1.is_some() || meridiem2.is_some();
171
172 if !has_time_separator && !has_meridiem && !has_context_prefix && hour2_opt.is_none() {
173 start = match_end;
181 continue;
182 }
183
184 let adj_hour1 = Self::adjust_hour(hour1, meridiem1);
186
187 let mut components = context.create_components();
188 components.assign(Component::Hour, adj_hour1);
189 components.assign(Component::Minute, minute1);
190 components.assign(Component::Second, second1);
191
192 if let Some(m) = meridiem1 {
193 components.assign(Component::Meridiem, m as i32);
194 } else {
195 if hour1 >= 12 {
197 components.assign(Component::Meridiem, Meridiem::PM as i32);
198 } else if hour1 < 12 {
199 }
201 }
202
203 let end_components = if let Some(hour2) = hour2_opt {
205 if hour2 > 23 {
206 None
207 } else {
208 let final_meridiem2 = meridiem2.or(meridiem1);
213
214 let mut end_comp = context.create_components();
223 let adj_hour2 = Self::adjust_hour(hour2, final_meridiem2);
224
225 end_comp.assign(Component::Hour, adj_hour2);
226 end_comp.assign(Component::Minute, minute2);
227 end_comp.assign(Component::Second, second2);
228 if let Some(m) = final_meridiem2 {
229 end_comp.assign(Component::Meridiem, m as i32);
230 } else if hour2 >= 12 {
231 end_comp.assign(Component::Meridiem, Meridiem::PM as i32);
232 }
233
234 use chrono::Datelike;
236 end_comp.imply(Component::Year, ref_date.year());
237 end_comp.imply(Component::Month, ref_date.month() as i32);
238 end_comp.imply(Component::Day, ref_date.day() as i32);
239
240 Some(end_comp)
241 }
242 } else {
243 None
244 };
245
246 results.push(context.create_result(match_start, match_end, components, end_components));
261
262 start = match_end;
263 }
264
265 Ok(results)
266 }
267}
268
269impl Default for PTTimeExpressionParser {
270 fn default() -> Self {
271 Self::new()
272 }
273}