1use pest_derive::Parser;
2use thiserror::Error;
3
4#[derive(Parser)]
6#[grammar = "./grammar.pest"]
7pub struct DateParser;
8
9#[derive(Debug, Error)]
11pub enum ParseDateError {
12 #[error("Failed to parse date:\n{0}")]
14 ParseError(String),
15}
16
17pub mod date_parser {
19 use crate::{DateParser, ParseDateError, Rule};
20 use chrono::{DateTime, Datelike, Duration, Local, TimeZone, Weekday};
21 use chronoutil::delta::shift_months_opt;
22 use pest::iterators::Pair;
23 use pest::Parser;
24
25 pub fn from_string(string: &str) -> Result<DateTime<Local>, ParseDateError> {
37 let pairs = DateParser::parse(Rule::date_expression, string)
38 .map_err(|e| ParseDateError::ParseError(e.to_string()))?;
39
40 if let Some(pair) = pairs.clone().next() {
41 match pair.as_rule() {
42 Rule::date_expression => {
43 let datetime = process_date_expression(pair)?;
44 return Ok(datetime);
45 }
46 _ => {
47 return Err(ParseDateError::ParseError(
48 "Unexpected rule encountered".to_string(),
49 ));
50 }
51 }
52 }
53
54 Err(ParseDateError::ParseError(
55 "No valid date expression found".to_string(),
56 ))
57 }
58
59 pub fn process_date_expression(
60 pair: Pair<'_, Rule>,
61 ) -> Result<DateTime<Local>, ParseDateError> {
62 let datetime = Local::now();
63 for inner_pair in pair.into_inner() {
64 match inner_pair.as_rule() {
65 Rule::relative_date => {
66 let parsed = process_relative_date(inner_pair)?;
67 return Ok(parsed);
68 }
69 Rule::relative_term => {
70 let parsed = process_relative_term(inner_pair)?;
71 return Ok(parsed);
72 }
73 Rule::specific_time => {
74 let parsed = process_specific_time(inner_pair, datetime)?;
75 return Ok(parsed);
76 }
77 Rule::specific_day => {
78 if let Some(inner) = inner_pair.into_inner().next() {
79 let parsed = process_specific_day(inner.as_rule(), datetime)?;
80 return Ok(parsed);
81 }
82 }
83 Rule::specific_day_and_time => {
84 let parsed = process_specific_day_and_time(inner_pair)?;
85 return Ok(parsed);
86 }
87 Rule::relative_day_and_specific_time => {
88 let parsed = process_relative_day_and_specific_time(inner_pair)?;
89 return Ok(parsed);
90 }
91 Rule::future_time => {
92 let parsed = process_future_time(inner_pair)?;
93 return Ok(parsed);
94 }
95 _ => {
96 return Err(ParseDateError::ParseError(
97 "Unexpected rule encountered".to_string(),
98 ));
99 }
100 }
101 }
102
103 Err(ParseDateError::ParseError(
104 "No date expression found".to_string(),
105 ))
106 }
107
108 pub fn process_future_time(pair: Pair<'_, Rule>) -> Result<DateTime<Local>, ParseDateError> {
109 let mut datetime = Local::now();
110 let mut duration = 0;
111 let mut unit: Option<Rule> = None;
112
113 for inner_pair in pair.into_inner() {
114 match inner_pair.as_rule() {
115 Rule::number => {
116 duration = inner_pair.as_str().trim().parse::<i32>().map_err(|_| {
117 ParseDateError::ParseError("Invalid duration value".to_string())
118 })?;
119 }
120 Rule::time_unit => {
121 unit = Some(inner_pair.as_rule());
122 }
123 _ => {
124 return Err(ParseDateError::ParseError("Unexpected rule".to_string()));
125 }
126 }
127 }
128
129 if let Some(unit) = unit {
130 datetime = match unit {
131 Rule::day_s => datetime + Duration::days(duration as i64),
132 Rule::week_s => datetime + Duration::weeks(duration as i64),
133 Rule::month_s => shift_months_opt(datetime, duration).ok_or_else(|| {
134 ParseDateError::ParseError("Invalid month adjustment".to_string())
135 })?,
136 Rule::year_s => {
137 datetime
138 .with_year(datetime.year() + duration)
139 .ok_or_else(|| {
140 ParseDateError::ParseError("Invalid year adjustment".to_string())
141 })?
142 }
143 _ => {
144 return Err(ParseDateError::ParseError("Invalid time unit".to_string()));
145 }
146 };
147 Ok(datetime)
148 } else {
149 Err(ParseDateError::ParseError(
150 "Time unit not provided".to_string(),
151 ))
152 }
153 }
154
155 pub fn process_specific_day_and_time(
156 pair: Pair<'_, Rule>,
157 ) -> Result<DateTime<Local>, ParseDateError> {
158 let mut datetime = Local::now();
159 for inner_pair in pair.into_inner() {
160 match inner_pair.as_rule() {
161 Rule::specific_day => {
162 datetime = process_specific_day(inner_pair.as_rule(), datetime)?;
163 }
164 Rule::specific_time => {
165 datetime = process_specific_time(inner_pair, datetime)?;
166 }
167 _ => {
168 return Err(ParseDateError::ParseError(format!(
169 "Unexpected rule in specific date and time: {:?}",
170 inner_pair.as_rule()
171 )));
172 }
173 }
174 }
175 Ok(datetime)
176 }
177
178 pub fn process_relative_day_and_specific_time(
179 pair: Pair<'_, Rule>,
180 ) -> Result<DateTime<Local>, ParseDateError> {
181 let mut datetime = Local::now();
182 for inner_pair in pair.into_inner() {
183 match inner_pair.as_rule() {
184 Rule::relative_date => {
185 datetime = process_relative_date(inner_pair)?;
186 }
187 Rule::relative_term => {
188 datetime = process_relative_term(inner_pair)?;
189 }
190 Rule::specific_time => {
191 datetime = process_specific_time(inner_pair, datetime)?;
192 }
193 _ => {}
194 }
195 }
196 Ok(datetime)
197 }
198
199 pub fn process_relative_date(pair: Pair<'_, Rule>) -> Result<DateTime<Local>, ParseDateError> {
200 let datetime = Local::now();
201 let inner_pairs: Vec<_> = pair.clone().into_inner().collect();
202
203 if inner_pairs.len() == 2 {
204 let first_pair = &inner_pairs[0];
205 let second_pair = &inner_pairs[1];
206
207 if first_pair.as_rule() == Rule::next_or_last
208 && second_pair.as_rule() == Rule::specific_day
209 {
210 let direction = first_pair.clone().into_inner().last().unwrap().as_rule();
211
212 if let Some(inner_pair) = second_pair.clone().into_inner().next() {
213 match process_weekday(inner_pair.as_rule()) {
214 Ok(target_weekday) => {
215 return shift_to_weekday(datetime, target_weekday, direction);
216 }
217 Err(e) => {
218 return Err(ParseDateError::ParseError(format!(
219 "Unrecognized relative date: {:?}",
220 e.to_string()
221 )));
222 }
223 }
224 }
225
226 Err(ParseDateError::ParseError(format!(
227 "Unrecognized relative date: {:?}",
228 second_pair.to_string()
229 )))
230 } else {
231 Err(ParseDateError::ParseError(
232 "Pair did not match expected structure for relative_date.".to_string(),
233 ))
234 }
235 } else {
236 Err(ParseDateError::ParseError(
237 "Unexpected number of inner pairs in relative_date.".to_string(),
238 ))
239 }
240 }
241
242 pub fn process_relative_term(pair: Pair<'_, Rule>) -> Result<DateTime<Local>, ParseDateError> {
243 let datetime = Local::now();
244
245 if let Some(inner_pair) = pair.clone().into_inner().next() {
246 match inner_pair.as_rule() {
247 Rule::tomorrow => {
248 return Ok(datetime + Duration::days(1));
249 }
250 Rule::today => {
251 return Ok(datetime);
252 }
253 Rule::yesterday => {
254 return Ok(datetime - Duration::days(1));
255 }
256 _ => {
257 return Err(ParseDateError::ParseError(format!(
258 "Unexpected relative term: {:?}",
259 pair
260 )));
261 }
262 }
263 }
264
265 Err(ParseDateError::ParseError(
266 "Invalid relative term".to_string(),
267 ))
268 }
269
270 pub fn process_specific_time(
271 pair: Pair<'_, Rule>,
272 datetime: DateTime<Local>,
273 ) -> Result<DateTime<Local>, ParseDateError> {
274 println!("some print");
275 let mut hour: u32 = 0;
276 let mut minute: u32 = 0;
277 let mut is_pm = false;
278
279 for inner_pair in pair.into_inner() {
281 match inner_pair.as_rule() {
282 Rule::hour => {
283 hour = inner_pair.as_str().parse::<u32>().map_err(|e| {
284 ParseDateError::ParseError(format!("Failed to parse hour: {}", e))
285 })?;
286
287 if hour > 23 {
288 return Err(ParseDateError::ParseError(format!(
289 "Invalid hour: {:?}",
290 hour
291 )));
292 }
293 }
294 Rule::minute => {
295 minute = inner_pair.as_str().parse::<u32>().map_err(|e| {
296 ParseDateError::ParseError(format!("Failed to parse minute: {}", e))
297 })?;
298 }
299 Rule::am_pm => {
300 if let Some(res) = process_is_pm(inner_pair) {
301 is_pm = res;
302 }
303 }
304 _ => {
305 return Err(ParseDateError::ParseError(
306 "Unexpected rule in specific_time".to_string(),
307 ));
308 }
309 }
310 }
311
312 if is_pm && hour < 12 {
313 hour += 12;
314 } else if !is_pm && hour == 12 {
315 println!("here");
316 hour = 0;
317 }
318
319 let modified_datetime = change_time(datetime, hour, minute)?;
320
321 Ok(modified_datetime)
322 }
323
324 pub fn process_specific_day(
325 rule: Rule,
326 datetime: DateTime<Local>,
327 ) -> Result<DateTime<Local>, ParseDateError> {
328 let target_weekday = process_weekday(rule)?;
329 let current_weekday = datetime.weekday();
330
331 let target_day_num = target_weekday.num_days_from_sunday();
332 let current_day_num = current_weekday.num_days_from_sunday();
333
334 let days_difference = if target_day_num >= current_day_num {
335 (target_day_num - current_day_num) as i64
336 } else {
337 -((current_day_num - target_day_num) as i64)
338 };
339
340 let target_date = datetime + Duration::days(days_difference);
341 Ok(target_date)
342 }
343
344 pub fn process_weekday(day: Rule) -> Result<Weekday, ParseDateError> {
345 match day {
346 Rule::monday => Ok(Weekday::Mon),
347 Rule::tuesday => Ok(Weekday::Tue),
348 Rule::wednesday => Ok(Weekday::Wed),
349 Rule::thursday => Ok(Weekday::Thu),
350 Rule::friday => Ok(Weekday::Fri),
351 Rule::saturday => Ok(Weekday::Sat),
352 Rule::sunday => Ok(Weekday::Sun),
353 _ => Err(ParseDateError::ParseError(format!(
354 "Invalid weekday: {:?}",
355 day
356 ))),
357 }
358 }
359
360 pub fn change_time(
361 datetime: DateTime<Local>,
362 hour: u32,
363 minute: u32,
364 ) -> Result<DateTime<Local>, ParseDateError> {
365 match Local.with_ymd_and_hms(
366 datetime.year(),
367 datetime.month(),
368 datetime.day(),
369 hour,
370 minute,
371 0,
372 ) {
373 chrono::LocalResult::Single(new_datetime) => Ok(new_datetime),
374 chrono::LocalResult::None => Err(ParseDateError::ParseError(
375 "Invalid date or time components".to_string(),
376 )),
377 chrono::LocalResult::Ambiguous(_, _) => Err(ParseDateError::ParseError(
378 "Ambiguous date and time".to_string(),
379 )),
380 }
381 }
382
383 pub fn shift_to_weekday(
384 now: DateTime<Local>,
385 target_weekday: Weekday,
386 direction: Rule,
387 ) -> Result<DateTime<Local>, ParseDateError> {
388 let current_weekday = now.weekday();
389
390 let num_from_curr = current_weekday.num_days_from_sunday() as i32;
391 let num_from_target = target_weekday.num_days_from_sunday() as i32;
392
393 let days_difference: i32 = match direction {
394 Rule::next => {
395 if num_from_target == 0 {
396 7 - num_from_curr + 7
397 } else {
398 7 - num_from_curr + num_from_target
399 }
400 }
401
402 Rule::last => {
403 if num_from_target == 0 {
404 -num_from_curr
405 } else {
406 -num_from_curr - 7 + num_from_target
407 }
408 }
409 Rule::this => {
410 let diff = (num_from_target as i64) - (num_from_curr as i64);
411 if diff >= 0 {
412 diff as i32
413 } else {
414 (diff + 7) as i32
415 }
416 }
417 _ => -100,
418 };
419
420 if days_difference < -7 {
421 return Err(ParseDateError::ParseError(format!(
422 "Expected last, this or next, got {:?}",
423 direction
424 )));
425 }
426
427 println!("days_difference {:?}", days_difference);
428
429 Ok(now + Duration::days(days_difference as i64))
430 }
431
432 pub fn process_is_pm(pair: Pair<'_, Rule>) -> Option<bool> {
433 if let Some(inner_pair) = pair.into_inner().next() {
434 if inner_pair.as_rule() == Rule::pm {
435 return Some(true);
436 } else if inner_pair.as_rule() == Rule::am {
437 return Some(false);
438 } else {
439 return None;
440 }
441 }
442 None
443 }
444}