whichtime_sys/parsers/en/
relative.rs1use crate::components::Component;
4use crate::context::ParsingContext;
5use crate::dictionaries::RelativeModifier;
6use crate::dictionaries::en::{get_relative_modifier, get_time_unit, parse_number_pattern};
7use crate::error::Result;
8use crate::parsers::Parser;
9use crate::results::ParsedResult;
10use crate::scanner::TokenType;
11use crate::types::{Duration, TimeUnit, add_duration};
12use chrono::Datelike;
13use regex::Regex;
14use std::sync::LazyLock;
15
16static PATTERN: LazyLock<Regex> = LazyLock::new(|| {
17 Regex::new(
18 r"(?i)\b(this|next|last|past|previous)\s*(week|month|year|quarter|hour|minute|day)\b",
19 )
20 .unwrap()
21});
22
23static NUMBERED_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
25 Regex::new(
26 r"(?i)\b(next|last|past)\s+(\d+|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)\s*(weeks?|months?|years?|quarters?|hours?|minutes?|days?)\b"
27 ).unwrap()
28});
29
30pub struct RelativeDateParser;
32
33impl Parser for RelativeDateParser {
34 fn name(&self) -> &'static str {
35 "RelativeDateParser"
36 }
37
38 fn should_apply(&self, context: &ParsingContext) -> bool {
39 context.has_token_type(TokenType::RelativeModifier)
40 && context.has_token_type(TokenType::TimeUnit)
41 }
42
43 fn parse(&self, context: &ParsingContext) -> Result<Vec<ParsedResult>> {
44 let mut results = Vec::new();
45 let ref_date = context.reference.instant;
46
47 for mat in NUMBERED_PATTERN.find_iter(context.text) {
49 let matched_text = mat.as_str();
50 let index = mat.start();
51
52 let Some(caps) = NUMBERED_PATTERN.captures(matched_text) else {
53 continue;
54 };
55
56 let modifier_str = caps
57 .get(1)
58 .map(|m| m.as_str().to_lowercase())
59 .unwrap_or_default();
60 let num_str = caps.get(2).map(|m| m.as_str()).unwrap_or("1");
61 let unit_str = caps
62 .get(3)
63 .map(|m| m.as_str().to_lowercase())
64 .unwrap_or_default();
65
66 let Some(modifier) = get_relative_modifier(&modifier_str) else {
67 continue;
68 };
69 let num = parse_number_pattern(num_str);
70 let Some(unit) = get_time_unit(&unit_str) else {
71 continue;
72 };
73
74 let multiplier = match modifier {
75 RelativeModifier::Next => 1.0,
76 RelativeModifier::Last => -1.0,
77 RelativeModifier::This => 0.0,
78 };
79
80 let mut duration = Duration::new();
81 let value = num * multiplier;
82 match unit {
83 TimeUnit::Second => duration.second = Some(value),
84 TimeUnit::Minute => duration.minute = Some(value),
85 TimeUnit::Hour => duration.hour = Some(value),
86 TimeUnit::Day => duration.day = Some(value),
87 TimeUnit::Week => duration.week = Some(value),
88 TimeUnit::Month => duration.month = Some(value),
89 TimeUnit::Year => duration.year = Some(value),
90 TimeUnit::Quarter => duration.quarter = Some(value),
91 TimeUnit::Millisecond => duration.millisecond = Some(value),
92 }
93
94 let target_date = add_duration(ref_date, &duration);
95
96 let mut components = context.create_components();
97 components.assign(Component::Year, target_date.year());
98 components.assign(Component::Month, target_date.month() as i32);
99 components.assign(Component::Day, target_date.day() as i32);
100
101 if duration.has_time_component() {
102 use chrono::Timelike;
103 components.assign(Component::Hour, target_date.hour() as i32);
104 components.assign(Component::Minute, target_date.minute() as i32);
105 }
106
107 results.push(context.create_result(
108 index,
109 index + matched_text.len(),
110 components,
111 None,
112 ));
113 }
114
115 for mat in PATTERN.find_iter(context.text) {
117 let matched_text = mat.as_str();
118 let index = mat.start();
119
120 if results
122 .iter()
123 .any(|r| r.index <= index && r.end_index > index)
124 {
125 continue;
126 }
127
128 let Some(caps) = PATTERN.captures(matched_text) else {
129 continue;
130 };
131
132 let modifier_str = caps
133 .get(1)
134 .map(|m| m.as_str().to_lowercase())
135 .unwrap_or_default();
136 let unit_str = caps
137 .get(2)
138 .map(|m| m.as_str().to_lowercase())
139 .unwrap_or_default();
140
141 let Some(modifier) = get_relative_modifier(&modifier_str) else {
142 continue;
143 };
144 let Some(unit) = get_time_unit(&unit_str) else {
145 continue;
146 };
147
148 let mut duration = Duration::new();
149 let value = match modifier {
150 RelativeModifier::Next => 1.0,
151 RelativeModifier::Last => -1.0,
152 RelativeModifier::This => 0.0,
153 };
154
155 match unit {
156 TimeUnit::Hour => duration.hour = Some(value),
157 TimeUnit::Day => duration.day = Some(value),
158 TimeUnit::Week => duration.week = Some(value),
159 TimeUnit::Month => duration.month = Some(value),
160 TimeUnit::Year => duration.year = Some(value),
161 TimeUnit::Quarter => duration.quarter = Some(value),
162 _ => continue,
163 }
164
165 let target_date = add_duration(ref_date, &duration);
166
167 let mut components = context.create_components();
168
169 match unit {
171 TimeUnit::Week => {
172 components.assign(Component::Year, target_date.year());
173 components.assign(Component::Month, target_date.month() as i32);
174 components.assign(Component::Day, target_date.day() as i32);
175 }
176 TimeUnit::Month => {
177 components.assign(Component::Year, target_date.year());
178 components.assign(Component::Month, target_date.month() as i32);
179 components.assign(Component::Day, 1);
180 }
181 TimeUnit::Year => {
182 components.assign(Component::Year, target_date.year());
183 components.assign(Component::Month, 1);
184 components.assign(Component::Day, 1);
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 if duration.has_time_component() {
191 use chrono::Timelike;
192 components.assign(Component::Hour, target_date.hour() as i32);
193 components.assign(Component::Minute, target_date.minute() as i32);
194 }
195 }
196 }
197
198 results.push(context.create_result(
199 index,
200 index + matched_text.len(),
201 components,
202 None,
203 ));
204 }
205
206 Ok(results)
207 }
208}