whichtime_sys/parsers/pt/
casual_date.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 chrono::{Datelike, Duration};
16use fancy_regex::Regex;
17use std::sync::LazyLock;
18
19static PATTERN: LazyLock<Regex> = LazyLock::new(|| {
20 Regex::new(
21 r"(?ix)
22 (?<![a-zA-Z])
23 (?:(?P<modifier>esta|este)\s+)?
24 (?P<keyword>hoje|amanhã|amanha|ontem|manhã|manha|tarde|noite)
25 (?:
26 \s+(?:à|a|ao)\s+
27 (?P<time_of_day>noite|tarde|manhã|manha|meio-dia|meia-noite)
28 )?
29 (?:
30 \s+(?:às|as|a|ao)\s+
31 (?P<hour>\d{1,2})
32 (?::(?P<minute>\d{1,2}))?
33 (?:\s*(?P<meridiem>pm|am))?
34 )?
35 (?=\W|$)
36 ",
37 )
38 .unwrap()
39});
40
41pub struct PTCasualDateParser;
43
44impl PTCasualDateParser {
45 pub fn new() -> Self {
46 Self
47 }
48
49 fn assign_time_part(components: &mut crate::components::FastComponents, time_part: &str) {
50 match time_part {
51 "manhã" | "manha" => {
52 components.imply(Component::Hour, 6);
53 components.imply(Component::Minute, 0);
54 components.assign(Component::Meridiem, Meridiem::AM as i32);
55 }
56 "tarde" => {
57 components.imply(Component::Hour, 15);
58 components.imply(Component::Minute, 0);
59 components.assign(Component::Meridiem, Meridiem::PM as i32);
60 }
61 "noite" => {
62 components.imply(Component::Hour, 22);
63 components.imply(Component::Minute, 0);
64 components.assign(Component::Meridiem, Meridiem::PM as i32);
65 }
66 "meio-dia" => {
67 components.imply(Component::Hour, 12);
68 components.imply(Component::Minute, 0);
69 components.assign(Component::Meridiem, Meridiem::PM as i32);
70 }
71 "meia-noite" => {
72 components.imply(Component::Hour, 0);
73 components.imply(Component::Minute, 0);
74 }
75 _ => {}
76 }
77 }
78}
79
80impl Parser for PTCasualDateParser {
81 fn name(&self) -> &'static str {
82 "PTCasualDateParser"
83 }
84
85 fn should_apply(&self, _context: &ParsingContext) -> bool {
86 true
87 }
88
89 fn parse(&self, context: &ParsingContext) -> Result<Vec<ParsedResult>> {
90 let mut results = Vec::new();
91 let ref_date = context.reference.instant;
92
93 let mut start = 0;
94 while start < context.text.len() {
95 let search_text = &context.text[start..];
96 let captures = match PATTERN.captures(search_text) {
97 Ok(Some(caps)) => caps,
98 Ok(None) => break,
99 Err(_) => break,
100 };
101
102 let full_match = match captures.get(0) {
103 Some(m) => m,
104 None => break,
105 };
106
107 let match_start = start + full_match.start();
108 let match_end = start + full_match.end();
109 let matched_text = full_match.as_str().to_lowercase();
110
111 let modifier = captures.name("modifier").map(|m| m.as_str().to_lowercase());
112 let keyword = captures
113 .name("keyword")
114 .map(|m| m.as_str().to_lowercase())
115 .unwrap_or_default();
116 let time_of_day = captures
117 .name("time_of_day")
118 .map(|m| m.as_str().to_lowercase());
119 let explicit_hour = captures
120 .name("hour")
121 .and_then(|m| m.as_str().parse::<i32>().ok());
122 let explicit_minute = captures
123 .name("minute")
124 .and_then(|m| m.as_str().parse::<i32>().ok());
125 let explicit_meridiem = captures.name("meridiem").map(|m| m.as_str().to_lowercase());
126
127 let mut components = context.create_components();
128 let mut target_date = ref_date;
129
130 let mut inferred_time_part = None;
131
132 match keyword.as_str() {
133 "hoje" => {
134 }
136 "amanhã" | "amanha" => {
137 target_date = ref_date + Duration::days(1);
138 }
139 "ontem" => {
140 target_date = ref_date - Duration::days(1);
141 }
142 "manhã" | "manha" | "tarde" | "noite" => {
143 if modifier.is_some()
145 || matched_text.contains("esta")
146 || matched_text.contains("este")
147 {
148 inferred_time_part = Some(keyword.clone());
149 } else {
150 start = match_end;
159 continue;
160 }
161 }
162 _ => {}
163 }
164
165 if let Some(tod) = time_of_day {
167 inferred_time_part = Some(tod);
168 }
169
170 components.assign(Component::Year, target_date.year());
172 components.assign(Component::Month, target_date.month() as i32);
173 components.assign(Component::Day, target_date.day() as i32);
174
175 if let Some(tp) = &inferred_time_part {
177 Self::assign_time_part(&mut components, tp);
178 }
179
180 if let Some(hour) = explicit_hour {
182 let mut adjusted_hour = hour;
183 let mut meridiem = None;
184
185 if let Some(m) = explicit_meridiem {
186 if m == "pm" {
187 meridiem = Some(Meridiem::PM);
188 if adjusted_hour < 12 {
189 adjusted_hour += 12;
190 }
191 } else if m == "am" {
192 meridiem = Some(Meridiem::AM);
193 if adjusted_hour == 12 {
194 adjusted_hour = 0;
195 }
196 }
197 } else if let Some(tp) = &inferred_time_part {
198 match tp.as_str() {
201 "noite" | "tarde" => {
202 if adjusted_hour < 12 {
203 adjusted_hour += 12;
204 meridiem = Some(Meridiem::PM);
205 }
206 }
207 "manhã" | "manha" => {
208 meridiem = Some(Meridiem::AM);
209 if adjusted_hour == 12 {
210 adjusted_hour = 0;
211 }
212 }
213 _ => {}
214 }
215 }
216
217 components.assign(Component::Hour, adjusted_hour);
218 components.assign(Component::Minute, explicit_minute.unwrap_or(0));
219 if let Some(m) = meridiem {
220 components.assign(Component::Meridiem, m as i32);
221 }
222 }
223
224 results.push(context.create_result(match_start, match_end, components, None));
225
226 start = match_end;
227 }
228
229 Ok(results)
230 }
231}
232
233impl Default for PTCasualDateParser {
234 fn default() -> Self {
235 Self::new()
236 }
237}