whichtime_sys/parsers/ru/
casual_date.rs1use crate::components::Component;
9use crate::context::ParsingContext;
10use crate::error::Result;
11use crate::parsers::Parser;
12use crate::results::ParsedResult;
13use crate::types::Meridiem;
14use chrono::{Datelike, Duration, Timelike};
15use fancy_regex::Regex;
16use std::sync::LazyLock;
17
18static PATTERN: LazyLock<Regex> = LazyLock::new(|| {
19 Regex::new(
20 r"(?i)(?<![a-zA-Zа-яА-Я])(?:(сейчас|сегодня|завтра|послезавтра|вчера|позавчера)(?:\s+(утром|днем|днём|вечером|ночью))?|(?P<time_only>утром|днем|днём|вечером|ночью))(?:\s+(?:в|к)\s+)?(?:\s*(?P<noon>полдень|полудень|полночь))?(?:\s+(\d{1,2})(?::(\d{1,2}))?(?:\s*ч(?:\.|асов)?)?)?(?=\W|$)"
21 ).unwrap()
22});
23
24const DATE_GROUP: usize = 1;
25const TIME_PART_GROUP: usize = 2;
26const TIME_ONLY_GROUP: usize = 3;
27const NOON_GROUP: usize = 4;
28const HOUR_GROUP: usize = 5;
29const MINUTE_GROUP: usize = 6;
30
31pub struct RUCasualDateParser;
33
34impl RUCasualDateParser {
35 pub fn new() -> Self {
36 Self
37 }
38
39 fn assign_time_part(components: &mut crate::components::FastComponents, time_part: &str) {
40 match time_part {
41 "утром" => {
42 components.imply(Component::Hour, 6);
43 components.imply(Component::Minute, 0);
44 components.imply(Component::Second, 0);
45 components.assign(Component::Meridiem, Meridiem::AM as i32);
46 }
47 "днем" | "днём" => {
48 components.imply(Component::Hour, 14);
49 components.imply(Component::Minute, 0);
50 components.imply(Component::Second, 0);
51 components.assign(Component::Meridiem, Meridiem::PM as i32);
52 }
53 "вечером" => {
54 components.imply(Component::Hour, 20);
55 components.imply(Component::Minute, 0);
56 components.imply(Component::Second, 0);
57 components.assign(Component::Meridiem, Meridiem::PM as i32);
58 }
59 "ночью" => {
60 components.imply(Component::Hour, 23);
61 components.imply(Component::Minute, 0);
62 components.imply(Component::Second, 0);
63 components.assign(Component::Meridiem, Meridiem::PM as i32);
64 }
65 _ => {}
66 }
67 }
68}
69
70impl Parser for RUCasualDateParser {
71 fn name(&self) -> &'static str {
72 "RUCasualDateParser"
73 }
74
75 fn should_apply(&self, _context: &ParsingContext) -> bool {
76 true
77 }
78
79 fn parse(&self, context: &ParsingContext) -> Result<Vec<ParsedResult>> {
80 let mut results = Vec::new();
81 let ref_date = context.reference.instant;
82
83 let mut start = 0;
84 while start < context.text.len() {
85 let search_text = &context.text[start..];
86 let captures = match PATTERN.captures(search_text) {
87 Ok(Some(caps)) => caps,
88 Ok(None) => break,
89 Err(_) => break,
90 };
91
92 let full_match = match captures.get(0) {
93 Some(m) => m,
94 None => break,
95 };
96
97 let match_start = start + full_match.start();
98 let match_end = start + full_match.end();
99
100 let date_keyword = captures.get(DATE_GROUP).map(|m| m.as_str().to_lowercase());
101
102 let time_part_opt = captures
103 .get(TIME_PART_GROUP)
104 .map(|m| m.as_str().to_lowercase());
105
106 let time_only = captures
107 .get(TIME_ONLY_GROUP)
108 .map(|m| m.as_str().to_lowercase());
109
110 let time_part = time_part_opt.or(time_only);
111
112 let noon_part = captures.get(NOON_GROUP).map(|m| m.as_str().to_lowercase());
113
114 let explicit_hour: Option<i32> = captures
115 .get(HOUR_GROUP)
116 .and_then(|m| m.as_str().parse().ok());
117
118 let explicit_minute: Option<i32> = captures
119 .get(MINUTE_GROUP)
120 .and_then(|m| m.as_str().parse().ok());
121
122 let mut components = context.create_components();
123 let mut target_date = ref_date;
124
125 if let Some(kw) = date_keyword {
126 match kw.as_str() {
127 "сегодня" => {}
128 "завтра" => {
129 target_date = ref_date + Duration::days(1);
130 }
131 "послезавтра" => {
132 target_date = ref_date + Duration::days(2);
133 }
134 "вчера" => {
135 target_date = ref_date - Duration::days(1);
136 }
137 "позавчера" => {
138 target_date = ref_date - Duration::days(2);
139 }
140 "сейчас" => {
141 components.assign(Component::Hour, ref_date.hour() as i32);
142 components.assign(Component::Minute, ref_date.minute() as i32);
143 components.assign(Component::Second, ref_date.second() as i32);
144 }
145 _ => {}
146 }
147 }
148
149 components.assign(Component::Year, target_date.year());
150 components.assign(Component::Month, target_date.month() as i32);
151 components.assign(Component::Day, target_date.day() as i32);
152
153 if let Some(ref tp) = time_part {
155 Self::assign_time_part(&mut components, tp);
156 }
157
158 if let Some(hour) = explicit_hour {
160 let mut adjusted_hour = hour;
161
162 if let Some(ref tp) = time_part {
164 match tp.as_str() {
165 "вечером" | "днем" | "днём" => {
166 if adjusted_hour < 12 {
169 adjusted_hour += 12;
170 }
171 }
172 "ночью" => {
173 if adjusted_hour < 6 {
174 } else if adjusted_hour < 12 {
176 adjusted_hour += 12;
178 }
179 }
180 _ => {}
181 }
182 } else if adjusted_hour < 12 {
183 }
185
186 components.assign(Component::Hour, adjusted_hour);
187 components.assign(Component::Minute, explicit_minute.unwrap_or(0));
188 }
189
190 if let Some(noon) = noon_part {
192 if noon.contains("полдень") || noon.contains("полудень") {
193 components.assign(Component::Hour, 12);
194 components.assign(Component::Minute, 0);
195 components.assign(Component::Meridiem, Meridiem::PM as i32);
196 } else if noon.contains("полночь") {
197 components.assign(Component::Hour, 0);
204 components.assign(Component::Minute, 0);
205 components.assign(Component::Meridiem, Meridiem::AM as i32);
206 }
207 }
208
209 results.push(context.create_result(match_start, match_end, components, None));
210
211 start = match_end;
212 }
213
214 Ok(results)
215 }
216}
217
218impl Default for RUCasualDateParser {
219 fn default() -> Self {
220 Self::new()
221 }
222}