Skip to main content

whichtime_sys/parsers/zh/
weekday.rs

1//! Chinese weekday parser
2//!
3//! Handles Chinese weekday expressions like:
4//! - "星期四" (Thursday)
5//! - "周一" (Monday)
6//! - "下周三" (next Wednesday)
7//! - "上週五" (last Friday)
8
9use crate::components::Component;
10use crate::context::ParsingContext;
11use crate::dictionaries::zh::get_weekday;
12use crate::error::Result;
13use crate::parsers::Parser;
14use crate::results::ParsedResult;
15use crate::types::Weekday;
16use chrono::{Datelike, Duration};
17use fancy_regex::Regex;
18use std::sync::LazyLock;
19
20// Pattern for Chinese weekday
21static PATTERN: LazyLock<Regex> = LazyLock::new(|| {
22    Regex::new(
23        r"(?P<modifier>这|這|这个|這個|本|今|下|下个|下個|上|上个|上個)?(?P<weekday>星期日|星期天|周日|週日|礼拜日|禮拜日|礼拜天|禮拜天|星期一|周一|週一|礼拜一|禮拜一|星期二|周二|週二|礼拜二|禮拜二|星期三|周三|週三|礼拜三|禮拜三|星期四|周四|週四|礼拜四|禮拜四|星期五|周五|週五|礼拜五|禮拜五|星期六|周六|週六|礼拜六|禮拜六)"
24    ).unwrap()
25});
26
27/// Chinese weekday parser
28pub struct ZHWeekdayParser;
29
30impl ZHWeekdayParser {
31    pub fn new() -> Self {
32        Self
33    }
34}
35
36impl Parser for ZHWeekdayParser {
37    fn name(&self) -> &'static str {
38        "ZHWeekdayParser"
39    }
40
41    fn should_apply(&self, context: &ParsingContext) -> bool {
42        context.text.contains("星期")
43            || context.text.contains("周")
44            || context.text.contains("週")
45            || context.text.contains("礼拜")
46            || context.text.contains("禮拜")
47    }
48
49    fn parse(&self, context: &ParsingContext) -> Result<Vec<ParsedResult>> {
50        let mut results = Vec::new();
51        let ref_date = context.reference.instant;
52
53        let mut start = 0;
54        while start < context.text.len() {
55            let search_text = &context.text[start..];
56
57            if let Ok(Some(caps)) = PATTERN.captures(search_text) {
58                let full_match = caps.get(0).unwrap();
59                let match_start = start + full_match.start();
60                let match_end = start + full_match.end();
61
62                let modifier = caps.name("modifier").map(|m| m.as_str());
63                let weekday_str = caps.name("weekday").map(|m| m.as_str()).unwrap_or("");
64
65                if let Some(weekday) = get_weekday(weekday_str) {
66                    let target_weekday = match weekday {
67                        Weekday::Sunday => chrono::Weekday::Sun,
68                        Weekday::Monday => chrono::Weekday::Mon,
69                        Weekday::Tuesday => chrono::Weekday::Tue,
70                        Weekday::Wednesday => chrono::Weekday::Wed,
71                        Weekday::Thursday => chrono::Weekday::Thu,
72                        Weekday::Friday => chrono::Weekday::Fri,
73                        Weekday::Saturday => chrono::Weekday::Sat,
74                    };
75
76                    let current_weekday = ref_date.weekday();
77                    let current_num = current_weekday.num_days_from_sunday() as i64;
78                    let target_num = target_weekday.num_days_from_sunday() as i64;
79
80                    // Calculate days offset based on modifier
81                    let days_offset = match modifier {
82                        Some("下") | Some("下个") | Some("下個") => {
83                            // Next week
84                            let diff = target_num - current_num;
85                            (if diff <= 0 { diff + 7 } else { diff }) + 7
86                        }
87                        Some("上") | Some("上个") | Some("上個") => {
88                            // Last week
89                            let diff = target_num - current_num;
90                            (if diff >= 0 { diff - 7 } else { diff }) - 7
91                        }
92                        Some("这") | Some("這") | Some("这个") | Some("這個") | Some("本")
93                        | Some("今") => {
94                            // This week (closest)
95
96                            target_num - current_num
97                        }
98                        None | Some(_) => {
99                            // No modifier - find closest occurrence (prefer past for weekdays before today)
100                            let diff = target_num - current_num;
101                            if diff > 0 {
102                                diff - 7 // Past occurrence
103                            } else if diff < 0 {
104                                diff // Past occurrence
105                            } else {
106                                0 // Today
107                            }
108                        }
109                    };
110
111                    let target_date = ref_date + Duration::days(days_offset);
112
113                    let mut components = context.create_components();
114                    components.assign(Component::Year, target_date.year());
115                    components.assign(Component::Month, target_date.month() as i32);
116                    components.assign(Component::Day, target_date.day() as i32);
117                    components.assign(Component::Weekday, weekday as i32);
118
119                    results.push(context.create_result(match_start, match_end, components, None));
120                }
121
122                start = match_end;
123                continue;
124            }
125
126            // No match - advance
127            if let Some(c) = search_text.chars().next() {
128                start += c.len_utf8();
129            } else {
130                break;
131            }
132        }
133
134        Ok(results)
135    }
136}
137
138impl Default for ZHWeekdayParser {
139    fn default() -> Self {
140        Self::new()
141    }
142}