whichtime_sys/parsers/zh/
casual_date.rs1use crate::components::Component;
13use crate::context::ParsingContext;
14use crate::dictionaries::zh::{NUMBER_MAP, parse_number_pattern};
15use crate::error::Result;
16use crate::parsers::Parser;
17use crate::results::ParsedResult;
18use crate::types::Meridiem;
19use chrono::{Datelike, Duration, Timelike};
20use fancy_regex::Regex;
21use std::sync::LazyLock;
22
23static PATTERN: LazyLock<Regex> = LazyLock::new(|| {
25 Regex::new(
26 r"(?P<date>今天|今日|今晚|今夜|明天|明日|昨天|昨日|后天|後天|前天|现在|現在|而家|聽日|尋日|琴日)(?P<time_part>早上|早晨|上午|中午|正午|下午|傍晚|晚上|晚间|晚間|夜里|夜裡|夜晚|凌晨|午夜|半夜)?(?:(?P<hour>[0-9一二三四五六七八九十零〇两兩]+)(?:点|點))?"
27 ).unwrap()
28});
29
30static TIME_ONLY_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
32 Regex::new(
33 r"(?P<time_only>早上|早晨|上午|中午|正午|下午|傍晚|晚上|晚间|晚間|夜里|夜裡|夜晚|凌晨|午夜|半夜)"
34 ).unwrap()
35});
36
37pub struct ZHCasualDateParser;
39
40impl ZHCasualDateParser {
41 pub fn new() -> Self {
42 Self
43 }
44
45 fn get_time_period_hour(period: &str) -> Option<(i32, Option<Meridiem>)> {
46 match period {
47 "早上" | "早晨" | "上午" => Some((6, Some(Meridiem::AM))),
48 "中午" | "正午" => Some((12, Some(Meridiem::PM))),
49 "下午" => Some((15, Some(Meridiem::PM))),
50 "傍晚" => Some((18, Some(Meridiem::PM))),
51 "晚上" | "晚间" | "晚間" => Some((22, Some(Meridiem::PM))),
52 "夜里" | "夜裡" | "夜晚" => Some((22, Some(Meridiem::PM))),
53 "凌晨" | "午夜" | "半夜" => Some((0, Some(Meridiem::AM))),
54 _ => None,
55 }
56 }
57
58 fn parse_hour(s: &str) -> i32 {
59 if let Some(&val) = NUMBER_MAP.get(s) {
61 return val as i32;
62 }
63 parse_number_pattern(s) as i32
65 }
66}
67
68impl Parser for ZHCasualDateParser {
69 fn name(&self) -> &'static str {
70 "ZHCasualDateParser"
71 }
72
73 fn should_apply(&self, _context: &ParsingContext) -> bool {
74 true
75 }
76
77 fn parse(&self, context: &ParsingContext) -> Result<Vec<ParsedResult>> {
78 let mut results = Vec::new();
79 let ref_date = context.reference.instant;
80
81 let mut start = 0;
82 while start < context.text.len() {
83 let search_text = &context.text[start..];
84
85 if let Ok(Some(caps)) = PATTERN.captures(search_text) {
87 let full_match = caps.get(0).unwrap();
88 let match_start = start + full_match.start();
89 let match_end = start + full_match.end();
90
91 let date_keyword = caps.name("date").map(|m| m.as_str()).unwrap_or_default();
92
93 let time_part = caps.name("time_part").map(|m| m.as_str());
94 let hour_str = caps.name("hour").map(|m| m.as_str());
95
96 let mut components = context.create_components();
97 let target_date;
98
99 match date_keyword {
101 "今天" | "今日" => {
102 components.assign(Component::Year, ref_date.year());
103 components.assign(Component::Month, ref_date.month() as i32);
104 components.assign(Component::Day, ref_date.day() as i32);
105 }
106 "明天" | "明日" | "聽日" => {
107 target_date = ref_date + Duration::days(1);
108 components.assign(Component::Year, target_date.year());
109 components.assign(Component::Month, target_date.month() as i32);
110 components.assign(Component::Day, target_date.day() as i32);
111 }
112 "昨天" | "昨日" | "尋日" | "琴日" => {
113 target_date = ref_date - Duration::days(1);
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 }
118 "后天" | "後天" => {
119 target_date = ref_date + Duration::days(2);
120 components.assign(Component::Year, target_date.year());
121 components.assign(Component::Month, target_date.month() as i32);
122 components.assign(Component::Day, target_date.day() as i32);
123 }
124 "前天" => {
125 target_date = ref_date - Duration::days(2);
126 components.assign(Component::Year, target_date.year());
127 components.assign(Component::Month, target_date.month() as i32);
128 components.assign(Component::Day, target_date.day() as i32);
129 }
130 "今晚" | "今夜" => {
131 components.assign(Component::Year, ref_date.year());
132 components.assign(Component::Month, ref_date.month() as i32);
133 components.assign(Component::Day, ref_date.day() as i32);
134 components.imply(Component::Hour, 22);
135 components.assign(Component::Meridiem, Meridiem::PM as i32);
136 }
137 "现在" | "現在" | "而家" => {
138 components.assign(Component::Year, ref_date.year());
139 components.assign(Component::Month, ref_date.month() as i32);
140 components.assign(Component::Day, ref_date.day() as i32);
141 components.assign(Component::Hour, ref_date.hour() as i32);
142 components.assign(Component::Minute, ref_date.minute() as i32);
143 components.assign(Component::Second, ref_date.second() as i32);
144 }
145 _ => {
146 start = match_end;
147 continue;
148 }
149 }
150
151 if let Some(period) = time_part
153 && let Some((hour, meridiem)) = Self::get_time_period_hour(period)
154 {
155 if let Some(h_str) = hour_str {
157 let mut h = Self::parse_hour(h_str);
158 if let Some(Meridiem::PM) = meridiem
160 && h < 12
161 {
162 h += 12;
163 }
164 components.assign(Component::Hour, h);
165 } else {
166 components.imply(Component::Hour, hour);
167 }
168 if let Some(m) = meridiem {
169 components.assign(Component::Meridiem, m as i32);
170 }
171 }
172
173 results.push(context.create_result(match_start, match_end, components, None));
174 start = match_end;
175 continue;
176 }
177
178 if let Ok(Some(caps)) = TIME_ONLY_PATTERN.captures(search_text) {
180 let full_match = caps.get(0).unwrap();
181 let match_start = start + full_match.start();
182 let match_end = start + full_match.end();
183
184 let time_only = caps.name("time_only").map(|m| m.as_str()).unwrap_or("");
185
186 if let Some((hour, meridiem)) = Self::get_time_period_hour(time_only) {
187 let mut components = context.create_components();
188 components.assign(Component::Year, ref_date.year());
189 components.assign(Component::Month, ref_date.month() as i32);
190 components.assign(Component::Day, ref_date.day() as i32);
191 components.imply(Component::Hour, hour);
192 if let Some(m) = meridiem {
193 components.assign(Component::Meridiem, m as i32);
194 }
195
196 results.push(context.create_result(match_start, match_end, components, None));
197 start = match_end;
198 continue;
199 }
200 }
201
202 if let Some(c) = search_text.chars().next() {
204 start += c.len_utf8();
205 } else {
206 break;
207 }
208 }
209
210 Ok(results)
211 }
212}
213
214impl Default for ZHCasualDateParser {
215 fn default() -> Self {
216 Self::new()
217 }
218}