whichtime_sys/parsers/zh/
time_expression.rs1use crate::components::Component;
10use crate::context::ParsingContext;
11use crate::dictionaries::zh::{fullwidth_to_halfwidth, parse_number_pattern};
12use crate::error::Result;
13use crate::parsers::Parser;
14use crate::results::ParsedResult;
15use crate::types::Meridiem;
16use fancy_regex::Regex;
17use std::sync::LazyLock;
18
19static PATTERN: LazyLock<Regex> = LazyLock::new(|| {
21 Regex::new(
22 r"(?P<meridiem>上午|下午|凌晨|早上|晚上|中午)?(?P<hour>[0-90-9一二三四五六七八九十两兩]+)(?:点|點)(?:(?P<minute>[0-90-9一二三四五六七八九十]+)分)?(?P<half>半)?(?:(?P<second>[0-90-9一二三四五六七八九十]+)秒)?"
23 ).unwrap()
24});
25
26pub struct ZHTimeExpressionParser;
28
29impl ZHTimeExpressionParser {
30 pub fn new() -> Self {
31 Self
32 }
33
34 fn parse_number(s: &str) -> i32 {
35 let hankaku = fullwidth_to_halfwidth(s);
36 if let Ok(n) = hankaku.parse::<i32>() {
37 return n;
38 }
39 parse_number_pattern(s) as i32
40 }
41
42 fn get_meridiem_info(meridiem: &str) -> (Option<Meridiem>, bool) {
43 match meridiem {
44 "上午" | "凌晨" | "早上" => (Some(Meridiem::AM), true),
45 "下午" | "晚上" => (Some(Meridiem::PM), true),
46 "中午" => (Some(Meridiem::PM), false), _ => (None, false),
48 }
49 }
50}
51
52impl Parser for ZHTimeExpressionParser {
53 fn name(&self) -> &'static str {
54 "ZHTimeExpressionParser"
55 }
56
57 fn should_apply(&self, context: &ParsingContext) -> bool {
58 context.text.contains('点') || context.text.contains('點')
59 }
60
61 fn parse(&self, context: &ParsingContext) -> Result<Vec<ParsedResult>> {
62 let mut results = Vec::new();
63
64 let mut start = 0;
65 while start < context.text.len() {
66 let search_text = &context.text[start..];
67
68 if let Ok(Some(caps)) = PATTERN.captures(search_text) {
69 let full_match = caps.get(0).unwrap();
70 let match_start = start + full_match.start();
71 let match_end = start + full_match.end();
72
73 let mut hour = caps
74 .name("hour")
75 .map(|m| Self::parse_number(m.as_str()))
76 .unwrap_or(0);
77 let mut minute = caps
78 .name("minute")
79 .map(|m| Self::parse_number(m.as_str()))
80 .unwrap_or(0);
81 let second = caps
82 .name("second")
83 .map(|m| Self::parse_number(m.as_str()))
84 .unwrap_or(0);
85
86 if caps.name("half").is_some() {
88 minute = if minute == 0 { 30 } else { minute + 30 };
89 }
90
91 let meridiem_str = caps.name("meridiem").map(|m| m.as_str());
92
93 if hour > 24 || minute >= 60 || second >= 60 {
95 start = match_end;
96 continue;
97 }
98
99 let mut meridiem_val = None;
101 if let Some(m_str) = meridiem_str {
102 let (mer, should_adjust) = Self::get_meridiem_info(m_str);
103 meridiem_val = mer;
104
105 if should_adjust {
106 if let Some(Meridiem::PM) = mer {
107 if hour < 12 {
108 hour += 12;
109 }
110 } else if let Some(Meridiem::AM) = mer
111 && hour == 12
112 {
113 hour = 0;
114 }
115 }
116 }
117
118 let mut components = context.create_components();
119 components.assign(Component::Hour, hour);
120 components.assign(Component::Minute, minute);
121 if second > 0 {
122 components.assign(Component::Second, second);
123 }
124 if let Some(m) = meridiem_val {
125 components.assign(Component::Meridiem, m as i32);
126 }
127
128 results.push(context.create_result(match_start, match_end, components, None));
129 start = match_end;
130 continue;
131 }
132
133 if let Some(c) = search_text.chars().next() {
135 start += c.len_utf8();
136 } else {
137 break;
138 }
139 }
140
141 Ok(results)
142 }
143}
144
145impl Default for ZHTimeExpressionParser {
146 fn default() -> Self {
147 Self::new()
148 }
149}