Skip to main content

whichtime_sys/parsers/zh/
time_unit_within.rs

1//! Chinese time unit within parser
2//!
3//! Handles Chinese "within X time" expressions like:
4//! - "五日内" (within 5 days)
5//! - "三天内" (within 3 days)
6//! - "两周内" (within 2 weeks)
7
8use crate::components::Component;
9use crate::context::ParsingContext;
10use crate::dictionaries::TimeUnit;
11use crate::dictionaries::zh::{get_time_unit, parse_number_pattern};
12use crate::error::Result;
13use crate::parsers::Parser;
14use crate::results::ParsedResult;
15use chrono::{Datelike, Duration, Timelike};
16use fancy_regex::Regex;
17use std::sync::LazyLock;
18
19// Pattern for "X units within" expressions in Chinese
20static PATTERN: LazyLock<Regex> = LazyLock::new(|| {
21    Regex::new(
22        r"(?P<number>[0-9一二三四五六七八九十两兩]+)(?P<unit>秒|秒钟|秒鐘|分|分钟|分鐘|小时|小時|钟头|鐘頭|天|日|周|週|星期|礼拜|禮拜|个月|個月|月|年)(?:之?内|之?內)"
23    ).unwrap()
24});
25
26/// Chinese time unit within parser
27pub struct ZHTimeUnitWithinParser;
28
29impl ZHTimeUnitWithinParser {
30    pub fn new() -> Self {
31        Self
32    }
33
34    fn parse_number(s: &str) -> i32 {
35        parse_number_pattern(s) as i32
36    }
37}
38
39impl Parser for ZHTimeUnitWithinParser {
40    fn name(&self) -> &'static str {
41        "ZHTimeUnitWithinParser"
42    }
43
44    fn should_apply(&self, context: &ParsingContext) -> bool {
45        context.text.contains('内') || context.text.contains('內')
46    }
47
48    fn parse(&self, context: &ParsingContext) -> Result<Vec<ParsedResult>> {
49        let mut results = Vec::new();
50        let ref_date = context.reference.instant;
51
52        let mut start = 0;
53        while start < context.text.len() {
54            let search_text = &context.text[start..];
55
56            if let Ok(Some(caps)) = PATTERN.captures(search_text) {
57                let full_match = caps.get(0).unwrap();
58                let match_start = start + full_match.start();
59                let match_end = start + full_match.end();
60
61                let number = caps
62                    .name("number")
63                    .map(|m| Self::parse_number(m.as_str()))
64                    .unwrap_or(1);
65                let unit_str = caps.name("unit").map(|m| m.as_str()).unwrap_or("");
66
67                let unit = get_time_unit(unit_str);
68
69                if let Some(time_unit) = unit {
70                    let target_date = match time_unit {
71                        TimeUnit::Second => ref_date + Duration::seconds(number as i64),
72                        TimeUnit::Minute => ref_date + Duration::minutes(number as i64),
73                        TimeUnit::Hour => ref_date + Duration::hours(number as i64),
74                        TimeUnit::Day => ref_date + Duration::days(number as i64),
75                        TimeUnit::Week => ref_date + Duration::weeks(number as i64),
76                        TimeUnit::Month => {
77                            let new_month = ref_date.month() as i32 + number;
78                            let years_to_add = (new_month - 1) / 12;
79                            let final_month = ((new_month - 1) % 12) + 1;
80                            ref_date
81                                .with_year(ref_date.year() + years_to_add)
82                                .and_then(|d| d.with_month(final_month as u32))
83                                .unwrap_or(ref_date)
84                        }
85                        TimeUnit::Year => ref_date
86                            .with_year(ref_date.year() + number)
87                            .unwrap_or(ref_date),
88                        _ => ref_date + Duration::days(number as i64),
89                    };
90
91                    let mut components = context.create_components();
92                    components.assign(Component::Year, target_date.year());
93                    components.assign(Component::Month, target_date.month() as i32);
94                    components.assign(Component::Day, target_date.day() as i32);
95
96                    // For time units smaller than a day, also set time
97                    match time_unit {
98                        TimeUnit::Second | TimeUnit::Minute | TimeUnit::Hour => {
99                            components.assign(Component::Hour, target_date.hour() as i32);
100                            components.assign(Component::Minute, target_date.minute() as i32);
101                            if time_unit == TimeUnit::Second {
102                                components.assign(Component::Second, target_date.second() as i32);
103                            }
104                        }
105                        _ => {}
106                    }
107
108                    results.push(context.create_result(match_start, match_end, components, None));
109                }
110
111                start = match_end;
112                continue;
113            }
114
115            // No match - advance
116            if let Some(c) = search_text.chars().next() {
117                start += c.len_utf8();
118            } else {
119                break;
120            }
121        }
122
123        Ok(results)
124    }
125}
126
127impl Default for ZHTimeUnitWithinParser {
128    fn default() -> Self {
129        Self::new()
130    }
131}