whichtime_sys/parsers/ja/
casual_time.rs1use crate::components::Component;
9use crate::context::ParsingContext;
10use crate::dictionaries::ja::{get_casual_time, get_relative_modifier};
11use crate::dictionaries::{CasualTimeType, RelativeModifier};
12use crate::error::Result;
13use crate::parsers::Parser;
14use crate::results::ParsedResult;
15use crate::scanner::TokenType;
16use crate::types::Meridiem;
17use chrono::{Datelike, Duration, Timelike};
18use fancy_regex::Regex;
19use std::sync::LazyLock;
20
21static PATTERN: LazyLock<Regex> = LazyLock::new(|| {
22 Regex::new(
23 r"(?:(?P<modifier>今|この|今週|次|来|来週|前|先|先週)(?:\s*の)?)?(?P<time>正午|昼|真夜中|夜中|朝|午前|午後|夕方|夜)",
24 )
25 .unwrap()
26});
27
28pub struct JACasualTimeParser;
29
30impl JACasualTimeParser {
31 pub fn new() -> Self {
32 Self
33 }
34
35 fn is_digit_like(ch: char) -> bool {
36 ch.is_ascii_digit()
37 || ('0'..='9').contains(&ch)
38 || matches!(
39 ch,
40 '〇' | '一' | '二' | '三' | '四' | '五' | '六' | '七' | '八' | '九' | '十'
41 )
42 }
43
44 fn has_trailing_number(text: &str, idx: usize) -> bool {
45 if idx >= text.len() {
46 return false;
47 }
48 let chars = text[idx..].chars();
49 for ch in chars {
50 if ch.is_whitespace() {
51 continue;
52 }
53 return Self::is_digit_like(ch);
54 }
55 false
56 }
57}
58
59impl Parser for JACasualTimeParser {
60 fn name(&self) -> &'static str {
61 "JACasualTimeParser"
62 }
63
64 fn should_apply(&self, context: &ParsingContext) -> bool {
65 context.has_token_type(TokenType::CasualTime)
66 }
67
68 fn parse(&self, context: &ParsingContext) -> Result<Vec<ParsedResult>> {
69 let mut results = Vec::new();
70 let ref_date = context.reference.instant;
71
72 let mut start = 0;
73 while start < context.text.len() {
74 let search_text = &context.text[start..];
75 let mat = match PATTERN.find(search_text) {
76 Ok(Some(m)) => m,
77 _ => break,
78 };
79
80 let match_start = start + mat.start();
81 let match_end = start + mat.end();
82
83 if Self::has_trailing_number(context.text, match_end) {
85 start = match_end;
86 continue;
87 }
88
89 let Some(caps) = PATTERN.captures(mat.as_str()).ok().flatten() else {
90 start = match_end;
91 continue;
92 };
93
94 let time_word = match caps.name("time") {
95 Some(m) => m.as_str(),
96 None => {
97 start = match_end;
98 continue;
99 }
100 };
101
102 let Some(time_type) = get_casual_time(time_word) else {
103 start = match_end;
104 continue;
105 };
106
107 let modifier = caps
108 .name("modifier")
109 .and_then(|m| get_relative_modifier(m.as_str()));
110
111 let mut target_date = ref_date;
112 match modifier {
113 Some(RelativeModifier::Last) => {
114 if !(ref_date.hour() <= 6 && matches!(time_type, CasualTimeType::Night)) {
115 target_date = ref_date - Duration::days(1);
116 }
117 }
118 Some(RelativeModifier::Next) => {
119 target_date = ref_date + Duration::days(1);
120 }
121 _ => {}
122 }
123
124 let mut components = context.create_components();
125 components.assign(Component::Year, target_date.year());
126 components.assign(Component::Month, target_date.month() as i32);
127 components.assign(Component::Day, target_date.day() as i32);
128
129 match time_type {
130 CasualTimeType::Noon => {
131 components.assign(Component::Hour, 12);
132 components.assign(Component::Minute, 0);
133 components.assign(Component::Second, 0);
134 components.assign(Component::Meridiem, Meridiem::PM as i32);
135 }
136 CasualTimeType::Midnight => {
137 if modifier.is_none() && ref_date.hour() >= 6 {
138 let next_day = ref_date + Duration::days(1);
139 components.assign(Component::Year, next_day.year());
140 components.assign(Component::Month, next_day.month() as i32);
141 components.assign(Component::Day, next_day.day() as i32);
142 }
143 components.assign(Component::Hour, 0);
144 components.assign(Component::Minute, 0);
145 components.assign(Component::Second, 0);
146 }
147 CasualTimeType::Morning => {
148 components.imply(Component::Hour, 6);
149 components.imply(Component::Minute, 0);
150 components.assign(Component::Meridiem, Meridiem::AM as i32);
151 }
152 CasualTimeType::Afternoon => {
153 components.imply(Component::Hour, 15);
154 components.imply(Component::Minute, 0);
155 components.assign(Component::Meridiem, Meridiem::PM as i32);
156 }
157 CasualTimeType::Evening => {
158 components.imply(Component::Hour, 20);
159 components.imply(Component::Minute, 0);
160 components.assign(Component::Meridiem, Meridiem::PM as i32);
161 }
162 CasualTimeType::Night => {
163 components.imply(Component::Hour, 22);
164 components.imply(Component::Minute, 0);
165 components.assign(Component::Meridiem, Meridiem::PM as i32);
166 }
167 }
168
169 let result = context.create_result(match_start, match_end, components, None);
170 results.push(result);
171
172 start = match_end;
173 }
174
175 Ok(results)
176 }
177}
178
179impl Default for JACasualTimeParser {
180 fn default() -> Self {
181 Self::new()
182 }
183}