whichtime_sys/parsers/ru/
time_unit_relative.rs1use crate::components::Component;
10use crate::context::ParsingContext;
11use crate::dictionaries::ru::{get_time_unit, parse_number_pattern};
12use crate::error::Result;
13use crate::parsers::Parser;
14use crate::results::ParsedResult;
15use crate::types::{Duration, TimeUnit, add_duration};
16use chrono::{Datelike, Timelike};
17use fancy_regex::Regex;
18use std::sync::LazyLock;
19
20static RELATIVE_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
22 Regex::new(
23 r"(?i)(?<![а-яА-Я])(?:(?P<modifier>следующи[еих]|ближайши[еих]|прошлы[еих]|последни[еих]|предыдущи[еих])\s+)?(?P<num>\d+|один|одна|одну|два|две|три|четыре|пять|шесть|семь|восемь|девять|десять)\s+(?P<unit>секунд[уы]?|минут[уы]?|час(?:ов|а)?|дн(?:ей|я|и|ь)?|день|недел[юиьей]|месяц(?:ев|а)?|год(?:а|ов)?|лет)(?![а-яА-Я])"
24 ).unwrap()
25});
26
27static THIS_WEEK_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
29 Regex::new(
30 r"(?i)(?<![а-яА-Я])(?:на|в)\s+(?P<modifier>это[йм]|следующ(?:ей|ем|ую)|прошло[йм]|будущ(?:ей|ем|ую))\s+(?P<unit>недел[еюи]|месяц[еа]?|году?)(?![а-яА-Я])"
31 ).unwrap()
32});
33
34static AGO_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
36 Regex::new(
37 r"(?i)(?<![а-яА-Я])(?P<num>\d+|один|одна|одну|два|две|три|четыре|пять|шесть|семь|восемь|девять|десять)\s+(?P<unit>секунд[уы]?|минут[уы]?|час(?:ов|а)?|дн(?:ей|я|и|ь)?|день|недел[юиьей]|месяц(?:ев|а)?|год(?:а|ов)?|лет)\s+назад(?![а-яА-Я])"
38 ).unwrap()
39});
40
41pub struct RUTimeUnitRelativeParser;
43
44impl RUTimeUnitRelativeParser {
45 pub fn new() -> Self {
46 Self
47 }
48
49 fn parse_unit(unit_str: &str) -> Option<TimeUnit> {
50 let lower = unit_str.to_lowercase();
51 get_time_unit(&lower)
52 }
53}
54
55impl Default for RUTimeUnitRelativeParser {
56 fn default() -> Self {
57 Self::new()
58 }
59}
60
61impl Parser for RUTimeUnitRelativeParser {
62 fn name(&self) -> &'static str {
63 "RUTimeUnitRelativeParser"
64 }
65
66 fn should_apply(&self, _context: &ParsingContext) -> bool {
67 true
68 }
69
70 fn parse(&self, context: &ParsingContext) -> Result<Vec<ParsedResult>> {
71 let mut results = Vec::new();
72 let ref_date = context.reference.instant;
73
74 let mut start = 0;
76 while start < context.text.len() {
77 let search_text = &context.text[start..];
78 let captures = match AGO_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 num_str = captures.name("num").map(|m| m.as_str()).unwrap_or("1");
93 let unit_str = captures
94 .name("unit")
95 .map(|m| m.as_str())
96 .unwrap_or_default();
97
98 let num = parse_number_pattern(num_str);
99 if let Some(unit) = Self::parse_unit(unit_str) {
100 let mut duration = Duration::new();
101 match unit {
102 TimeUnit::Second => duration.second = Some(-num),
103 TimeUnit::Minute => duration.minute = Some(-num),
104 TimeUnit::Hour => duration.hour = Some(-num),
105 TimeUnit::Day => duration.day = Some(-num),
106 TimeUnit::Week => duration.week = Some(-num),
107 TimeUnit::Month => duration.month = Some(-num),
108 TimeUnit::Year => duration.year = Some(-num),
109 _ => {}
110 }
111
112 let target_date = add_duration(ref_date, &duration);
113
114 let mut components = context.create_components();
115 components.assign(Component::Year, target_date.year());
116 components.assign(Component::Month, target_date.month() as i32);
117 components.assign(Component::Day, target_date.day() as i32);
118
119 if duration.has_time_component() {
120 components.assign(Component::Hour, target_date.hour() as i32);
121 components.assign(Component::Minute, target_date.minute() as i32);
122 components.assign(Component::Second, target_date.second() as i32);
123 }
124
125 results.push(context.create_result(match_start, match_end, components, None));
126 }
127
128 start = match_end;
129 }
130
131 start = 0;
133 while start < context.text.len() {
134 let search_text = &context.text[start..];
135 let captures = match RELATIVE_PATTERN.captures(search_text) {
136 Ok(Some(caps)) => caps,
137 Ok(None) => break,
138 Err(_) => break,
139 };
140
141 let full_match = match captures.get(0) {
142 Some(m) => m,
143 None => break,
144 };
145
146 let match_start = start + full_match.start();
147 let match_end = start + full_match.end();
148
149 let overlaps = results.iter().any(|r| {
151 (match_start >= r.index && match_start < r.index + r.text.len())
152 || (r.index >= match_start && r.index < match_end)
153 });
154 if overlaps {
155 start = match_end;
156 continue;
157 }
158
159 let modifier = captures.name("modifier").map(|m| m.as_str().to_lowercase());
160 let num_str = captures.name("num").map(|m| m.as_str()).unwrap_or("1");
161 let unit_str = captures
162 .name("unit")
163 .map(|m| m.as_str())
164 .unwrap_or_default();
165
166 let num = parse_number_pattern(num_str);
167 if let Some(unit) = Self::parse_unit(unit_str) {
168 let is_past = modifier.as_ref().is_some_and(|m| {
170 m.starts_with("прошл") || m.starts_with("последн") || m.starts_with("предыдущ")
171 });
172
173 let multiplier = if is_past { -1.0 } else { 1.0 };
174 let adjusted_num = num * multiplier;
175
176 let mut duration = Duration::new();
177 match unit {
178 TimeUnit::Second => duration.second = Some(adjusted_num),
179 TimeUnit::Minute => duration.minute = Some(adjusted_num),
180 TimeUnit::Hour => duration.hour = Some(adjusted_num),
181 TimeUnit::Day => duration.day = Some(adjusted_num),
182 TimeUnit::Week => duration.week = Some(adjusted_num),
183 TimeUnit::Month => duration.month = Some(adjusted_num),
184 TimeUnit::Year => duration.year = Some(adjusted_num),
185 _ => {}
186 }
187
188 let target_date = add_duration(ref_date, &duration);
189
190 let mut components = context.create_components();
191 components.assign(Component::Year, target_date.year());
192 components.assign(Component::Month, target_date.month() as i32);
193 components.assign(Component::Day, target_date.day() as i32);
194
195 if duration.has_time_component() {
196 components.assign(Component::Hour, target_date.hour() as i32);
197 components.assign(Component::Minute, target_date.minute() as i32);
198 components.assign(Component::Second, target_date.second() as i32);
199 } else {
200 components.imply(Component::Hour, ref_date.hour() as i32);
201 components.imply(Component::Minute, ref_date.minute() as i32);
202 }
203
204 results.push(context.create_result(match_start, match_end, components, None));
205 }
206
207 start = match_end;
208 }
209
210 start = 0;
212 while start < context.text.len() {
213 let search_text = &context.text[start..];
214 let captures = match THIS_WEEK_PATTERN.captures(search_text) {
215 Ok(Some(caps)) => caps,
216 Ok(None) => break,
217 Err(_) => break,
218 };
219
220 let full_match = match captures.get(0) {
221 Some(m) => m,
222 None => break,
223 };
224
225 let match_start = start + full_match.start();
226 let match_end = start + full_match.end();
227
228 let overlaps = results.iter().any(|r| {
230 (match_start >= r.index && match_start < r.index + r.text.len())
231 || (r.index >= match_start && r.index < match_end)
232 });
233 if overlaps {
234 start = match_end;
235 continue;
236 }
237
238 let modifier = captures
239 .name("modifier")
240 .map(|m| m.as_str().to_lowercase())
241 .unwrap_or_default();
242 let unit_str = captures
243 .name("unit")
244 .map(|m| m.as_str())
245 .unwrap_or_default();
246
247 let offset = if modifier.starts_with("это") {
249 0 } else if modifier.starts_with("следующ") || modifier.starts_with("будущ") {
251 1 } else if modifier.starts_with("прошл") {
253 -1 } else {
255 0
256 };
257
258 let unit = if unit_str.starts_with("недел") {
260 Some(TimeUnit::Week)
261 } else if unit_str.starts_with("месяц") {
262 Some(TimeUnit::Month)
263 } else if unit_str.starts_with("год") {
264 Some(TimeUnit::Year)
265 } else {
266 None
267 };
268
269 if let Some(time_unit) = unit {
270 let mut duration = Duration::new();
271 match time_unit {
272 TimeUnit::Week => duration.week = Some(offset as f64),
273 TimeUnit::Month => duration.month = Some(offset as f64),
274 TimeUnit::Year => duration.year = Some(offset as f64),
275 _ => {}
276 }
277
278 let target_date = add_duration(ref_date, &duration);
279
280 let mut components = context.create_components();
281 components.assign(Component::Year, target_date.year());
282 components.assign(Component::Month, target_date.month() as i32);
283 components.assign(Component::Day, target_date.day() as i32);
284 components.imply(Component::Hour, ref_date.hour() as i32);
285 components.imply(Component::Minute, ref_date.minute() as i32);
286
287 results.push(context.create_result(match_start, match_end, components, None));
288 }
289
290 start = match_end;
291 }
292
293 Ok(results)
294 }
295}