whichtime_sys/parsers/en/
weekday.rs1use crate::components::Component;
4use crate::context::ParsingContext;
5use crate::dictionaries::RelativeModifier;
6use crate::dictionaries::en::{get_relative_modifier, get_weekday};
7use crate::error::Result;
8use crate::parsers::Parser;
9use crate::results::ParsedResult;
10use crate::scanner::TokenType;
11use chrono::{Datelike, Duration};
12use regex::Regex;
13use std::sync::LazyLock;
14
15static PATTERN: LazyLock<Regex> = LazyLock::new(|| {
16 Regex::new(
17 r"(?i)(?:^|\W)(?:(this|next|last|past|previous)\s+)?(sun(?:day)?|mon(?:day)?|tue(?:s(?:day)?)?|wed(?:nesday)?|thu(?:rs(?:day)?)?|fri(?:day)?|sat(?:urday)?)(?:\W|$)"
18 ).unwrap()
19});
20
21pub struct WeekdayParser;
23
24impl Parser for WeekdayParser {
25 fn name(&self) -> &'static str {
26 "WeekdayParser"
27 }
28
29 fn should_apply(&self, context: &ParsingContext) -> bool {
30 context.has_token_type(TokenType::Weekday)
31 }
32
33 fn parse(&self, context: &ParsingContext) -> Result<Vec<ParsedResult>> {
34 let mut results = Vec::new();
35 let ref_date = context.reference.instant;
36 let ref_weekday = ref_date.weekday().num_days_from_sunday();
37
38 for mat in PATTERN.find_iter(context.text) {
39 let matched_text = mat.as_str();
40 let index = mat.start();
41
42 let Some(caps) = PATTERN.captures(matched_text) else {
43 continue;
44 };
45
46 let modifier_str = caps.get(1).map(|m| m.as_str().to_lowercase());
47 let weekday_str = caps
48 .get(2)
49 .map(|m| m.as_str().to_lowercase())
50 .unwrap_or_default();
51
52 let Some(weekday) = get_weekday(&weekday_str) else {
53 continue;
54 };
55
56 let modifier = modifier_str.as_deref().and_then(get_relative_modifier);
57
58 let days_offset = match modifier {
60 Some(RelativeModifier::Next) => {
61 let diff = (weekday as i64) - (ref_weekday as i64);
62 if diff <= 0 { diff + 7 } else { diff }
63 }
64 Some(RelativeModifier::Last) => {
65 let diff = (weekday as i64) - (ref_weekday as i64);
66 if diff >= 0 { diff - 7 } else { diff }
67 }
68 Some(RelativeModifier::This) | None => {
69 let diff = (weekday as i64) - (ref_weekday as i64);
70 if diff == 0 {
71 0
72 } else if diff > 0 {
73 if diff <= 3 { diff } else { diff - 7 }
74 } else if diff >= -3 {
75 diff
76 } else {
77 diff + 7
78 }
79 }
80 };
81
82 let target_date = ref_date + Duration::days(days_offset);
83
84 let mut components = context.create_components();
85 components.assign(Component::Year, target_date.year());
86 components.assign(Component::Month, target_date.month() as i32);
87 components.assign(Component::Day, target_date.day() as i32);
88 components.assign(Component::Weekday, weekday as i32);
89
90 let actual_start = matched_text
92 .find(|c: char| c.is_alphanumeric())
93 .unwrap_or(0);
94 let actual_end = matched_text
95 .rfind(|c: char| c.is_alphanumeric())
96 .map(|i| i + matched_text[i..].chars().next().map_or(1, char::len_utf8))
97 .unwrap_or(matched_text.len());
98
99 results.push(context.create_result(
100 index + actual_start,
101 index + actual_end,
102 components,
103 None,
104 ));
105 }
106
107 Ok(results)
108 }
109}