whichtime_sys/parsers/es/
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, Timelike};
16use fancy_regex::Regex;
17use std::sync::LazyLock;
18
19static PATTERN: LazyLock<Regex> = LazyLock::new(|| {
20 Regex::new(
21 r"(?i)(?<![a-zA-ZáéíóúüñÁÉÍÓÚÜÑ])(ahora|hoy|esta\s+mañana|esta\s+manana|esta\s+tarde|esta\s+noche|mañana|manana|ayer|pasado\s*mañana|pasado\s*manana|anteayer|antes\s*de\s*ayer|anoche)(?:\s+(?:de\s+)?(la\s+)?(mañana|manana|tarde|noche))?(?:\s+a(?:\s+las?)?\s+(\d{1,2})(?::(\d{2}))?(?:\s*(a\.?m\.?|p\.?m\.?))?|\s+a\s+(mediodía|mediodia))?(?=\W|$)"
22 ).unwrap()
23});
24
25const DATE_GROUP: usize = 1;
26const TIME_GROUP: usize = 3;
27const HOUR_GROUP: usize = 4;
28const MINUTE_GROUP: usize = 5;
29const MERIDIEM_GROUP: usize = 6;
30const MEDIODIA_GROUP: usize = 7;
31
32pub struct ESCasualDateParser;
34
35impl ESCasualDateParser {
36 pub fn new() -> Self {
37 Self
38 }
39
40 fn extract_time_components(
41 components: &mut crate::components::FastComponents,
42 time_keyword: &str,
43 ) {
44 let lower = time_keyword.to_lowercase();
45 if lower.contains("mañana") || lower.contains("manana") {
46 components.imply(Component::Hour, 6);
48 components.imply(Component::Minute, 0);
49 components.assign(Component::Meridiem, Meridiem::AM as i32);
50 } else if lower.contains("tarde") {
51 components.imply(Component::Hour, 15);
52 components.imply(Component::Minute, 0);
53 components.assign(Component::Meridiem, Meridiem::PM as i32);
54 } else if lower.contains("noche") {
55 components.imply(Component::Hour, 22);
56 components.imply(Component::Minute, 0);
57 components.assign(Component::Meridiem, Meridiem::PM as i32);
58 }
59 }
60}
61
62impl Parser for ESCasualDateParser {
63 fn name(&self) -> &'static str {
64 "ESCasualDateParser"
65 }
66
67 fn should_apply(&self, _context: &ParsingContext) -> bool {
68 true
69 }
70
71 fn parse(&self, context: &ParsingContext) -> Result<Vec<ParsedResult>> {
72 let mut results = Vec::new();
73 let ref_date = context.reference.instant;
74
75 let mut start = 0;
76 while start < context.text.len() {
77 let search_text = &context.text[start..];
78 let captures = match PATTERN.captures(search_text) {
79 Ok(Some(caps)) => caps,
80 Ok(None) => break,
81 Err(_) => break,
82 };
83
84 let full_match = match captures.get(0) {
85 Some(m) => m,
86 None => break,
87 };
88
89 let match_start = start + full_match.start();
90 let match_end = start + full_match.end();
91
92 let date_keyword = captures
93 .get(DATE_GROUP)
94 .map(|m| m.as_str().to_lowercase())
95 .unwrap_or_default();
96
97 let time_keyword = captures.get(TIME_GROUP).map(|m| m.as_str().to_lowercase());
98
99 let mut components = context.create_components();
100 let mut target_date = ref_date;
101
102 match date_keyword.as_str() {
103 "ahora" => {
104 components.assign(Component::Year, ref_date.year());
105 components.assign(Component::Month, ref_date.month() as i32);
106 components.assign(Component::Day, ref_date.day() as i32);
107 components.assign(Component::Hour, ref_date.hour() as i32);
108 components.assign(Component::Minute, ref_date.minute() as i32);
109 components.assign(Component::Second, ref_date.second() as i32);
110 }
111 "hoy" => {
112 components.assign(Component::Year, ref_date.year());
113 components.assign(Component::Month, ref_date.month() as i32);
114 components.assign(Component::Day, ref_date.day() as i32);
115 }
116 "mañana" | "manana" => {
117 target_date = ref_date + Duration::days(1);
119 components.assign(Component::Year, target_date.year());
120 components.assign(Component::Month, target_date.month() as i32);
121 components.assign(Component::Day, target_date.day() as i32);
122 }
123 "ayer" => {
124 target_date = ref_date - Duration::days(1);
125 components.assign(Component::Year, target_date.year());
126 components.assign(Component::Month, target_date.month() as i32);
127 components.assign(Component::Day, target_date.day() as i32);
128 }
129 _ if date_keyword.contains("pasado")
130 && (date_keyword.contains("mañana") || date_keyword.contains("manana")) =>
131 {
132 target_date = ref_date + Duration::days(2);
134 components.assign(Component::Year, target_date.year());
135 components.assign(Component::Month, target_date.month() as i32);
136 components.assign(Component::Day, target_date.day() as i32);
137 }
138 "anteayer" => {
139 target_date = ref_date - Duration::days(2);
141 components.assign(Component::Year, target_date.year());
142 components.assign(Component::Month, target_date.month() as i32);
143 components.assign(Component::Day, target_date.day() as i32);
144 }
145 _ if date_keyword.contains("antes") && date_keyword.contains("ayer") => {
146 target_date = ref_date - Duration::days(2);
148 components.assign(Component::Year, target_date.year());
149 components.assign(Component::Month, target_date.month() as i32);
150 components.assign(Component::Day, target_date.day() as i32);
151 }
152 "anoche" => {
153 if ref_date.hour() > 6 {
154 target_date = ref_date - Duration::days(1);
155 }
156 components.assign(Component::Year, target_date.year());
157 components.assign(Component::Month, target_date.month() as i32);
158 components.assign(Component::Day, target_date.day() as i32);
159 components.imply(Component::Hour, 22);
160 components.assign(Component::Meridiem, Meridiem::PM as i32);
161 }
162 _ if date_keyword.contains("esta") && date_keyword.contains("noche") => {
163 components.assign(Component::Year, ref_date.year());
164 components.assign(Component::Month, ref_date.month() as i32);
165 components.assign(Component::Day, ref_date.day() as i32);
166 components.imply(Component::Hour, 22);
167 components.assign(Component::Meridiem, Meridiem::PM as i32);
168 }
169 _ if date_keyword.contains("esta")
170 && (date_keyword.contains("mañana") || date_keyword.contains("manana")) =>
171 {
172 components.assign(Component::Year, ref_date.year());
173 components.assign(Component::Month, ref_date.month() as i32);
174 components.assign(Component::Day, ref_date.day() as i32);
175 components.imply(Component::Hour, 6);
176 components.assign(Component::Meridiem, Meridiem::AM as i32);
177 }
178 _ if date_keyword.contains("esta") && date_keyword.contains("tarde") => {
179 components.assign(Component::Year, ref_date.year());
180 components.assign(Component::Month, ref_date.month() as i32);
181 components.assign(Component::Day, ref_date.day() as i32);
182 components.imply(Component::Hour, 15);
183 components.assign(Component::Meridiem, Meridiem::PM as i32);
184 }
185 _ => {
186 start = match_end;
187 continue;
188 }
189 }
190
191 if let Some(ref time_kw) = time_keyword {
193 Self::extract_time_components(&mut components, time_kw);
194 }
195
196 if captures.get(MEDIODIA_GROUP).is_some() {
198 components.assign(Component::Hour, 12);
200 components.assign(Component::Minute, 0);
201 components.assign(Component::Meridiem, Meridiem::PM as i32);
202 } else if let Some(hour_match) = captures.get(HOUR_GROUP) {
203 let hour_str = hour_match.as_str();
204 let mut hour: i32 = hour_str.parse().unwrap_or(0);
205
206 let minute: i32 = captures
207 .get(MINUTE_GROUP)
208 .map(|m| m.as_str().parse().unwrap_or(0))
209 .unwrap_or(0);
210
211 let has_pm = captures
213 .get(MERIDIEM_GROUP)
214 .map(|m| m.as_str().to_lowercase().starts_with('p'))
215 .unwrap_or(false);
216 let has_am = captures
217 .get(MERIDIEM_GROUP)
218 .map(|m| m.as_str().to_lowercase().starts_with('a'))
219 .unwrap_or(false);
220
221 let infer_pm = time_keyword
223 .as_ref()
224 .map(|t| t.contains("noche"))
225 .unwrap_or(false)
226 || date_keyword.contains("noche");
227
228 if has_pm {
229 if hour < 12 {
230 hour += 12;
231 }
232 components.assign(Component::Meridiem, Meridiem::PM as i32);
233 } else if has_am {
234 if hour == 12 {
235 hour = 0;
236 }
237 components.assign(Component::Meridiem, Meridiem::AM as i32);
238 } else if infer_pm && hour <= 12 {
239 if hour < 12 {
240 hour += 12;
241 }
242 components.assign(Component::Meridiem, Meridiem::PM as i32);
243 }
244
245 components.assign(Component::Hour, hour);
246 components.assign(Component::Minute, minute);
247 }
248
249 results.push(context.create_result(match_start, match_end, components, None));
250
251 start = match_end;
252 }
253
254 Ok(results)
255 }
256}
257
258impl Default for ESCasualDateParser {
259 fn default() -> Self {
260 Self::new()
261 }
262}