Skip to main content

whichtime_sys/parsers/it/
casual_date.rs

1//! Italian casual date parser
2//!
3//! Handles Italian casual date expressions like:
4//! - "ora", "oggi", "domani", "ieri"
5//! - "stamattina", "stasera"
6//! - Combined: "domani alle 15:00"
7
8use crate::components::Component;
9use crate::context::ParsingContext;
10use crate::error::Result;
11use crate::parsers::Parser;
12use crate::results::ParsedResult;
13use crate::types::Meridiem;
14use chrono::{Datelike, Duration, Timelike};
15use fancy_regex::Regex;
16use std::sync::LazyLock;
17
18static PATTERN: LazyLock<Regex> = LazyLock::new(|| {
19    Regex::new(
20        r"(?i)(?<![a-zA-ZàèéìòùÀÈÉÌÒÙ])(adesso|ora|oggi|domani|ieri|dopodomani|altroieri|l'altro\s+ieri|stamattina|stasera|stanotte)(?:\s+(mattina|pomeriggio|sera|notte))?(?:\s+(?:alle?\s+)?(\d{1,2})(?:[:\.](\d{2}))?)?(?=\W|$)"
21    ).unwrap()
22});
23
24const DATE_GROUP: usize = 1;
25const TIME_PERIOD_GROUP: usize = 2;
26const HOUR_GROUP: usize = 3;
27const MINUTE_GROUP: usize = 4;
28
29/// Italian casual date parser
30pub struct ITCasualDateParser;
31
32impl ITCasualDateParser {
33    pub fn new() -> Self {
34        Self
35    }
36}
37
38impl Parser for ITCasualDateParser {
39    fn name(&self) -> &'static str {
40        "ITCasualDateParser"
41    }
42
43    fn should_apply(&self, _context: &ParsingContext) -> bool {
44        true
45    }
46
47    fn parse(&self, context: &ParsingContext) -> Result<Vec<ParsedResult>> {
48        let mut results = Vec::new();
49        let ref_date = context.reference.instant;
50
51        let mut start = 0;
52        while start < context.text.len() {
53            let search_text = &context.text[start..];
54            let captures = match PATTERN.captures(search_text) {
55                Ok(Some(caps)) => caps,
56                Ok(None) => break,
57                Err(_) => break,
58            };
59
60            let full_match = match captures.get(0) {
61                Some(m) => m,
62                None => break,
63            };
64
65            let match_start = start + full_match.start();
66            let match_end = start + full_match.end();
67
68            let date_keyword = captures
69                .get(DATE_GROUP)
70                .map(|m| m.as_str().to_lowercase())
71                .unwrap_or_default();
72
73            let time_period = captures
74                .get(TIME_PERIOD_GROUP)
75                .map(|m| m.as_str().to_lowercase());
76
77            let mut components = context.create_components();
78            let target_date;
79
80            // Process date keyword
81            match date_keyword.as_str() {
82                "adesso" | "ora" => {
83                    components.assign(Component::Year, ref_date.year());
84                    components.assign(Component::Month, ref_date.month() as i32);
85                    components.assign(Component::Day, ref_date.day() as i32);
86                    components.assign(Component::Hour, ref_date.hour() as i32);
87                    components.assign(Component::Minute, ref_date.minute() as i32);
88                    components.assign(Component::Second, ref_date.second() as i32);
89                }
90                "oggi" => {
91                    components.assign(Component::Year, ref_date.year());
92                    components.assign(Component::Month, ref_date.month() as i32);
93                    components.assign(Component::Day, ref_date.day() as i32);
94                }
95                "domani" => {
96                    target_date = ref_date + Duration::days(1);
97                    components.assign(Component::Year, target_date.year());
98                    components.assign(Component::Month, target_date.month() as i32);
99                    components.assign(Component::Day, target_date.day() as i32);
100                }
101                "ieri" => {
102                    target_date = ref_date - Duration::days(1);
103                    components.assign(Component::Year, target_date.year());
104                    components.assign(Component::Month, target_date.month() as i32);
105                    components.assign(Component::Day, target_date.day() as i32);
106                }
107                "dopodomani" => {
108                    target_date = ref_date + Duration::days(2);
109                    components.assign(Component::Year, target_date.year());
110                    components.assign(Component::Month, target_date.month() as i32);
111                    components.assign(Component::Day, target_date.day() as i32);
112                }
113                "altroieri" | "l'altro ieri" => {
114                    target_date = ref_date - Duration::days(2);
115                    components.assign(Component::Year, target_date.year());
116                    components.assign(Component::Month, target_date.month() as i32);
117                    components.assign(Component::Day, target_date.day() as i32);
118                }
119                "stamattina" => {
120                    components.assign(Component::Year, ref_date.year());
121                    components.assign(Component::Month, ref_date.month() as i32);
122                    components.assign(Component::Day, ref_date.day() as i32);
123                    components.imply(Component::Hour, 6);
124                    components.assign(Component::Meridiem, Meridiem::AM as i32);
125                }
126                "stasera" | "stanotte" => {
127                    components.assign(Component::Year, ref_date.year());
128                    components.assign(Component::Month, ref_date.month() as i32);
129                    components.assign(Component::Day, ref_date.day() as i32);
130                    components.imply(Component::Hour, 22);
131                    components.assign(Component::Meridiem, Meridiem::PM as i32);
132                }
133                _ => {
134                    start = match_end;
135                    continue;
136                }
137            }
138
139            // Apply time period modifier if present
140            if let Some(ref period) = time_period {
141                match period.as_str() {
142                    "mattina" => {
143                        components.imply(Component::Hour, 8);
144                        components.assign(Component::Meridiem, Meridiem::AM as i32);
145                    }
146                    "pomeriggio" => {
147                        components.imply(Component::Hour, 14);
148                        components.assign(Component::Meridiem, Meridiem::PM as i32);
149                    }
150                    "sera" => {
151                        components.imply(Component::Hour, 18);
152                        components.assign(Component::Meridiem, Meridiem::PM as i32);
153                    }
154                    "notte" => {
155                        components.imply(Component::Hour, 22);
156                        components.assign(Component::Meridiem, Meridiem::PM as i32);
157                    }
158                    _ => {}
159                }
160            }
161
162            // Handle explicit time: "alle 15:00"
163            if let Some(hour_match) = captures.get(HOUR_GROUP) {
164                let hour: i32 = hour_match.as_str().parse().unwrap_or(0);
165                let minute: i32 = captures
166                    .get(MINUTE_GROUP)
167                    .map(|m| m.as_str().parse().unwrap_or(0))
168                    .unwrap_or(0);
169
170                components.assign(Component::Hour, hour);
171                components.assign(Component::Minute, minute);
172
173                if hour >= 12 {
174                    components.assign(Component::Meridiem, Meridiem::PM as i32);
175                } else {
176                    components.assign(Component::Meridiem, Meridiem::AM as i32);
177                }
178            }
179
180            results.push(context.create_result(match_start, match_end, components, None));
181
182            start = match_end;
183        }
184
185        Ok(results)
186    }
187}
188
189impl Default for ITCasualDateParser {
190    fn default() -> Self {
191        Self::new()
192    }
193}