whichtime_sys/parsers/nl/
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])(nu|vandaag|overmorgen|eergisteren|van(?:ochtend|middag|avond|nacht)|morgen(?:ochtend|middag|avond|nacht)|gisteren(?:ochtend|middag|avond|nacht)|deze\s+(?:ochtend|middag|namiddag|avond|nacht)|morgen|gisteren)(?:\s+(?:om\s+)?(\d{1,2})(?::(\d{1,2}))?(?:\s*uhr|\s*uur)?)?(?=\W|$)"
22 ).unwrap()
23});
24
25const DATE_GROUP: usize = 1;
26const HOUR_GROUP: usize = 2;
27const MINUTE_GROUP: usize = 3;
28
29pub struct NLCasualDateParser;
31
32impl NLCasualDateParser {
33 pub fn new() -> Self {
34 Self
35 }
36
37 fn assign_time(components: &mut crate::components::FastComponents, time_part: &str) {
38 match time_part {
39 "ochtend" | "vanochtend" => {
40 components.imply(Component::Hour, 6);
41 components.imply(Component::Minute, 0);
42 components.imply(Component::Second, 0);
43 components.assign(Component::Meridiem, Meridiem::AM as i32);
44 }
45 "middag" | "vanmiddag" => {
46 components.imply(Component::Hour, 12); components.imply(Component::Minute, 0);
48 components.imply(Component::Second, 0);
49 components.assign(Component::Meridiem, Meridiem::PM as i32);
50 }
51 "namiddag" => {
52 components.imply(Component::Hour, 15); components.imply(Component::Minute, 0);
54 components.imply(Component::Second, 0);
55 components.assign(Component::Meridiem, Meridiem::PM as i32);
56 }
57 "avond" | "vanavond" => {
58 components.imply(Component::Hour, 20);
59 components.imply(Component::Minute, 0);
60 components.imply(Component::Second, 0);
61 components.assign(Component::Meridiem, Meridiem::PM as i32);
62 }
63 "nacht" | "vannacht" => {
64 components.imply(Component::Hour, 22);
65 components.imply(Component::Minute, 0);
66 components.imply(Component::Second, 0);
67 components.assign(Component::Meridiem, Meridiem::PM as i32);
68 }
69 _ => {}
70 }
71 }
72}
73
74impl Parser for NLCasualDateParser {
75 fn name(&self) -> &'static str {
76 "NLCasualDateParser"
77 }
78
79 fn should_apply(&self, _context: &ParsingContext) -> bool {
80 true
81 }
82
83 fn parse(&self, context: &ParsingContext) -> Result<Vec<ParsedResult>> {
84 let mut results = Vec::new();
85 let ref_date = context.reference.instant;
86
87 let mut start = 0;
88 while start < context.text.len() {
89 let search_text = &context.text[start..];
90 let captures = match PATTERN.captures(search_text) {
91 Ok(Some(caps)) => caps,
92 Ok(None) => break,
93 Err(_) => break,
94 };
95
96 let full_match = match captures.get(0) {
97 Some(m) => m,
98 None => break,
99 };
100
101 let match_start = start + full_match.start();
102 let match_end = start + full_match.end();
103
104 let date_keyword = captures
105 .get(DATE_GROUP)
106 .map(|m| m.as_str().to_lowercase())
107 .unwrap_or_default();
108
109 let explicit_hour: Option<i32> = captures
110 .get(HOUR_GROUP)
111 .and_then(|m| m.as_str().parse().ok());
112
113 let explicit_minute: Option<i32> = captures
114 .get(MINUTE_GROUP)
115 .and_then(|m| m.as_str().parse().ok());
116
117 let mut components = context.create_components();
118 let mut target_date = ref_date;
119
120 let (day_offset, time_part) = if date_keyword == "nu" || date_keyword == "vandaag" {
122 (0, None)
123 } else if date_keyword == "morgen" {
124 (1, None)
125 } else if date_keyword == "overmorgen" {
126 (2, None)
127 } else if date_keyword == "gisteren" {
128 (-1, None)
129 } else if date_keyword == "eergisteren" {
130 (-2, None)
131 } else if let Some(time_part) = date_keyword.strip_prefix("van") {
132 let part = if date_keyword == "vannacht" {
134 "nacht"
135 } else {
136 time_part
137 };
138 (0, Some(part.to_string()))
139 } else if let Some(time_part) = date_keyword.strip_prefix("morgen")
140 && date_keyword.len() > 6
141 {
142 (1, Some(time_part.to_string()))
144 } else if let Some(time_part) = date_keyword.strip_prefix("gisteren")
145 && date_keyword.len() > 8
146 {
147 (-1, Some(time_part.to_string()))
149 } else if let Some(time_part) = date_keyword.strip_prefix("deze ") {
150 (0, Some(time_part.to_string()))
152 } else {
153 (0, None)
154 };
155
156 if day_offset != 0 {
158 target_date = ref_date + Duration::days(day_offset);
159 }
160
161 components.assign(Component::Year, target_date.year());
162 components.assign(Component::Month, target_date.month() as i32);
163 components.assign(Component::Day, target_date.day() as i32);
164
165 if date_keyword == "nu" {
166 components.assign(Component::Hour, ref_date.hour() as i32);
167 components.assign(Component::Minute, ref_date.minute() as i32);
168 components.assign(Component::Second, ref_date.second() as i32);
169 }
170
171 if let Some(ref tp) = time_part {
173 Self::assign_time(&mut components, tp);
174 }
175
176 if let Some(hour) = explicit_hour {
178 let adjusted_hour =
180 if let Some(ref tp) = time_part {
181 match tp.as_str() {
182 "avond" | "nacht" | "namiddag" | "vanavond" | "vannacht" => {
183 if hour < 12 { hour + 12 } else { hour }
184 }
185 "ochtend" | "vanochtend" => {
186 if hour == 12 {
187 0
188 } else {
189 hour
190 }
191 }
192 _ => hour,
193 }
194 } else {
195 hour
196 };
197 components.assign(Component::Hour, adjusted_hour);
198 components.assign(Component::Minute, explicit_minute.unwrap_or(0));
199 }
200
201 results.push(context.create_result(match_start, match_end, components, None));
202
203 start = match_end;
204 }
205
206 Ok(results)
207 }
208}
209
210impl Default for NLCasualDateParser {
211 fn default() -> Self {
212 Self::new()
213 }
214}