whichtime_sys/parsers/uk/
weekday.rs1use crate::components::Component;
10use crate::context::ParsingContext;
11use crate::dictionaries::uk::get_weekday;
12use crate::error::Result;
13use crate::parsers::Parser;
14use crate::results::ParsedResult;
15use crate::types::Meridiem;
16use chrono::{Datelike, Duration, Weekday as ChronoWeekday};
17use fancy_regex::Regex;
18use std::sync::LazyLock;
19
20static PATTERN: LazyLock<Regex> = LazyLock::new(|| {
21 Regex::new(
22 r"(?i)(?<![а-яА-ЯіїєґІЇЄҐ'])(?:(?P<prep>[уву])\s+)?(?:(?P<modifier>минулий|минулу|минула|наступний|наступну|наступна|цей|цю|ця)\s+)?(?P<weekday>понеділок|понеділка|вівторок|вівторка|середа|середу|середи|четвер|четверга|п'ятниця|п'ятницю|п'ятниці|пятниця|пятницю|субота|суботу|суботи|неділя|неділю|неділі|пн|вт|ср|чт|пт|сб|нд)(?:\s+(?P<time>вранці|зранку|опівдні|вдень|ввечері|увечері|вночі))?(?![а-яА-ЯіїєґІЇЄҐ'])"
23 ).unwrap()
24});
25
26pub struct UKWeekdayParser;
28
29impl UKWeekdayParser {
30 pub fn new() -> Self {
31 Self
32 }
33
34 fn assign_time_part(components: &mut crate::components::FastComponents, time_part: &str) {
35 match time_part.to_lowercase().as_str() {
36 "вранці" | "зранку" => {
37 components.assign(Component::Hour, 6);
38 components.assign(Component::Minute, 0);
39 components.assign(Component::Meridiem, Meridiem::AM as i32);
40 }
41 "опівдні" => {
42 components.assign(Component::Hour, 12);
43 components.assign(Component::Minute, 0);
44 components.assign(Component::Meridiem, Meridiem::PM as i32);
45 }
46 "вдень" => {
47 components.assign(Component::Hour, 14);
48 components.assign(Component::Minute, 0);
49 components.assign(Component::Meridiem, Meridiem::PM as i32);
50 }
51 "ввечері" | "увечері" => {
52 components.assign(Component::Hour, 20);
53 components.assign(Component::Minute, 0);
54 components.assign(Component::Meridiem, Meridiem::PM as i32);
55 }
56 "вночі" => {
57 components.assign(Component::Hour, 2);
58 components.assign(Component::Minute, 0);
59 components.assign(Component::Meridiem, Meridiem::AM as i32);
60 }
61 _ => {}
62 }
63 }
64}
65
66impl Default for UKWeekdayParser {
67 fn default() -> Self {
68 Self::new()
69 }
70}
71
72impl Parser for UKWeekdayParser {
73 fn name(&self) -> &'static str {
74 "UKWeekdayParser"
75 }
76
77 fn should_apply(&self, _context: &ParsingContext) -> bool {
78 true
79 }
80
81 fn parse(&self, context: &ParsingContext) -> Result<Vec<ParsedResult>> {
82 let mut results = Vec::new();
83 let ref_date = context.reference.instant;
84
85 let mut start = 0;
86 while start < context.text.len() {
87 let search_text = &context.text[start..];
88 let captures = match PATTERN.captures(search_text) {
89 Ok(Some(caps)) => caps,
90 Ok(None) => break,
91 Err(_) => break,
92 };
93
94 let full_match = match captures.get(0) {
95 Some(m) => m,
96 None => break,
97 };
98
99 let match_start = start + full_match.start();
100 let match_end = start + full_match.end();
101
102 let modifier = captures.name("modifier").map(|m| m.as_str().to_lowercase());
103 let weekday_str = captures
104 .name("weekday")
105 .map(|m| m.as_str().to_lowercase())
106 .unwrap_or_default();
107 let time_part = captures.name("time").map(|m| m.as_str());
108
109 let clean_weekday = weekday_str.trim_end_matches('.');
111
112 let Some(weekday) = get_weekday(clean_weekday) else {
113 start = match_end;
114 continue;
115 };
116
117 let mut components = context.create_components();
118
119 let target_weekday = match weekday {
121 crate::types::Weekday::Sunday => ChronoWeekday::Sun,
122 crate::types::Weekday::Monday => ChronoWeekday::Mon,
123 crate::types::Weekday::Tuesday => ChronoWeekday::Tue,
124 crate::types::Weekday::Wednesday => ChronoWeekday::Wed,
125 crate::types::Weekday::Thursday => ChronoWeekday::Thu,
126 crate::types::Weekday::Friday => ChronoWeekday::Fri,
127 crate::types::Weekday::Saturday => ChronoWeekday::Sat,
128 };
129
130 let current_weekday = ref_date.weekday();
131 let current_day_num = current_weekday.num_days_from_sunday() as i64;
132 let target_day_num = target_weekday.num_days_from_sunday() as i64;
133
134 let mut days_diff = target_day_num - current_day_num;
136
137 let has_preposition = captures.name("prep").is_some();
139
140 let target_date = match modifier.as_deref() {
142 Some(m) if m.starts_with("наступн") => {
143 if days_diff <= 0 {
145 days_diff += 7;
146 }
147 ref_date + Duration::days(days_diff)
148 }
149 Some(m) if m.starts_with("минул") => {
150 if days_diff >= 0 {
152 days_diff -= 7;
153 }
154 ref_date + Duration::days(days_diff)
155 }
156 _ if has_preposition => {
157 let past_diff = if days_diff > 0 {
160 days_diff - 7
161 } else {
162 days_diff
163 };
164 let future_diff = if days_diff <= 0 {
165 days_diff + 7
166 } else {
167 days_diff
168 };
169
170 if past_diff.abs() < future_diff.abs() {
172 days_diff = past_diff;
173 } else {
174 days_diff = future_diff;
175 }
176 ref_date + Duration::days(days_diff)
177 }
178 _ => {
179 if days_diff > 0 {
181 days_diff -= 7;
182 }
183 ref_date + Duration::days(days_diff)
184 }
185 };
186
187 components.assign(Component::Year, target_date.year());
188 components.assign(Component::Month, target_date.month() as i32);
189 components.assign(Component::Day, target_date.day() as i32);
190 components.assign(Component::Weekday, weekday as i32);
191
192 if let Some(tp) = time_part {
194 Self::assign_time_part(&mut components, tp);
195 }
196
197 results.push(context.create_result(match_start, match_end, components, None));
198 start = match_end;
199 }
200
201 Ok(results)
202 }
203}