Skip to main content

whichtime_sys/parsers/ja/
casual_date.rs

1//! Japanese casual date parser
2//!
3//! Handles Japanese casual date expressions like:
4//! - "今日", "きょう", "本日" (today)
5//! - "明日", "あした" (tomorrow)
6//! - "昨日", "きのう" (yesterday)
7//! - "今朝" (this morning)
8//! - "今晩", "今夜" (tonight)
9//! - Combined: "明日の正午", "来週の金曜日の午後3時"
10
11use crate::components::Component;
12use crate::context::ParsingContext;
13use crate::error::Result;
14use crate::parsers::Parser;
15use crate::results::ParsedResult;
16use crate::types::Meridiem;
17use chrono::{Datelike, Duration};
18use fancy_regex::Regex;
19use std::sync::LazyLock;
20
21static PATTERN: LazyLock<Regex> = LazyLock::new(|| {
22    Regex::new(
23        r"(今日|きょう|本日|ほんじつ|明日|あした|あす|昨日|きのう|さくじつ|明後日|あさって|一昨日|おととい|今朝|けさ|今夜|こんや|今晩|こんばん|今夕|こんゆう)(?:の(正午|昼|朝|午前|午後|夕方|夜|真夜中))?"
24    ).unwrap()
25});
26
27const DATE_GROUP: usize = 1;
28const TIME_PERIOD_GROUP: usize = 2;
29
30/// Japanese casual date parser
31pub struct JACasualDateParser;
32
33impl JACasualDateParser {
34    pub fn new() -> Self {
35        Self
36    }
37}
38
39impl Parser for JACasualDateParser {
40    fn name(&self) -> &'static str {
41        "JACasualDateParser"
42    }
43
44    fn should_apply(&self, _context: &ParsingContext) -> bool {
45        true
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            let captures = match PATTERN.captures(search_text) {
56                Ok(Some(caps)) => caps,
57                Ok(None) => break,
58                Err(_) => break,
59            };
60
61            let full_match = match captures.get(0) {
62                Some(m) => m,
63                None => break,
64            };
65
66            let match_start = start + full_match.start();
67            let match_end = start + full_match.end();
68
69            let date_keyword = captures
70                .get(DATE_GROUP)
71                .map(|m| m.as_str())
72                .unwrap_or_default();
73
74            let time_period = captures.get(TIME_PERIOD_GROUP).map(|m| m.as_str());
75
76            let mut components = context.create_components();
77            let target_date;
78
79            // Process date keyword
80            match date_keyword {
81                "今日" | "きょう" | "本日" | "ほんじつ" => {
82                    components.assign(Component::Year, ref_date.year());
83                    components.assign(Component::Month, ref_date.month() as i32);
84                    components.assign(Component::Day, ref_date.day() as i32);
85                }
86                "明日" | "あした" | "あす" => {
87                    target_date = ref_date + Duration::days(1);
88                    components.assign(Component::Year, target_date.year());
89                    components.assign(Component::Month, target_date.month() as i32);
90                    components.assign(Component::Day, target_date.day() as i32);
91                }
92                "昨日" | "きのう" | "さくじつ" => {
93                    target_date = ref_date - Duration::days(1);
94                    components.assign(Component::Year, target_date.year());
95                    components.assign(Component::Month, target_date.month() as i32);
96                    components.assign(Component::Day, target_date.day() as i32);
97                }
98                "明後日" | "あさって" => {
99                    target_date = ref_date + Duration::days(2);
100                    components.assign(Component::Year, target_date.year());
101                    components.assign(Component::Month, target_date.month() as i32);
102                    components.assign(Component::Day, target_date.day() as i32);
103                }
104                "一昨日" | "おととい" => {
105                    target_date = ref_date - Duration::days(2);
106                    components.assign(Component::Year, target_date.year());
107                    components.assign(Component::Month, target_date.month() as i32);
108                    components.assign(Component::Day, target_date.day() as i32);
109                }
110                "今朝" | "けさ" => {
111                    components.assign(Component::Year, ref_date.year());
112                    components.assign(Component::Month, ref_date.month() as i32);
113                    components.assign(Component::Day, ref_date.day() as i32);
114                    components.imply(Component::Hour, 6);
115                    components.assign(Component::Meridiem, Meridiem::AM as i32);
116                }
117                "今夜" | "こんや" | "今晩" | "こんばん" | "今夕" | "こんゆう" => {
118                    components.assign(Component::Year, ref_date.year());
119                    components.assign(Component::Month, ref_date.month() as i32);
120                    components.assign(Component::Day, ref_date.day() as i32);
121                    components.imply(Component::Hour, 22);
122                    components.assign(Component::Meridiem, Meridiem::PM as i32);
123                }
124                _ => {
125                    start = match_end;
126                    continue;
127                }
128            }
129
130            // Apply time period modifier if present
131            if let Some(period) = time_period {
132                match period {
133                    "正午" | "昼" => {
134                        components.assign(Component::Hour, 12);
135                        components.assign(Component::Minute, 0);
136                        components.assign(Component::Meridiem, Meridiem::PM as i32);
137                    }
138                    "朝" | "午前" => {
139                        components.imply(Component::Hour, 8);
140                        components.assign(Component::Meridiem, Meridiem::AM as i32);
141                    }
142                    "午後" => {
143                        components.imply(Component::Hour, 14);
144                        components.assign(Component::Meridiem, Meridiem::PM as i32);
145                    }
146                    "夕方" => {
147                        components.imply(Component::Hour, 17);
148                        components.assign(Component::Meridiem, Meridiem::PM as i32);
149                    }
150                    "夜" => {
151                        components.imply(Component::Hour, 20);
152                        components.assign(Component::Meridiem, Meridiem::PM as i32);
153                    }
154                    "真夜中" => {
155                        components.assign(Component::Hour, 0);
156                        components.assign(Component::Minute, 0);
157                    }
158                    _ => {}
159                }
160            }
161
162            results.push(context.create_result(match_start, match_end, components, None));
163
164            start = match_end;
165        }
166
167        Ok(results)
168    }
169}
170
171impl Default for JACasualDateParser {
172    fn default() -> Self {
173        Self::new()
174    }
175}