Skip to main content

whichtime_sys/parsers/es/
casual_time.rs

1//! Spanish casual time parser
2//!
3//! Handles Spanish casual time expressions like:
4//! - "mediodía", "medianoche"
5//! - "esta mañana", "esta tarde", "esta noche"
6//! - "el mediodía", "la medianoche"
7
8use crate::components::Component;
9use crate::context::ParsingContext;
10use crate::error::Result;
11use crate::parsers::Parser;
12use crate::results::ParsedResult;
13use crate::scanner::TokenType;
14use crate::types::Meridiem;
15use chrono::{Datelike, Duration, Timelike};
16use fancy_regex::Regex;
17use std::sync::LazyLock;
18
19static PATTERN: LazyLock<Regex> = LazyLock::new(|| {
20    Regex::new(
21        r"(?i)(?:el\s+|la\s+)?(?:esta\s+)?(mediodía|mediodia|medianoche|mañana|manana|tarde|noche)(?=\W|$)"
22    ).unwrap()
23});
24
25/// Spanish casual time parser
26pub struct ESCasualTimeParser;
27
28impl ESCasualTimeParser {
29    pub fn new() -> Self {
30        Self
31    }
32}
33
34impl Parser for ESCasualTimeParser {
35    fn name(&self) -> &'static str {
36        "ESCasualTimeParser"
37    }
38
39    fn should_apply(&self, context: &ParsingContext) -> bool {
40        context.has_token_type(TokenType::CasualTime)
41    }
42
43    fn parse(&self, context: &ParsingContext) -> Result<Vec<ParsedResult>> {
44        let mut results = Vec::new();
45        let ref_date = context.reference.instant;
46
47        let mut start = 0;
48        while start < context.text.len() {
49            let search_text = &context.text[start..];
50            let mat = match PATTERN.find(search_text) {
51                Ok(Some(m)) => m,
52                Ok(None) => break,
53                Err(_) => break,
54            };
55
56            let matched_text = mat.as_str();
57            let index = start + mat.start();
58
59            let caps = match PATTERN.captures(matched_text) {
60                Ok(Some(c)) => c,
61                Ok(None) => {
62                    start = index + 1;
63                    continue;
64                }
65                Err(_) => {
66                    start = index + 1;
67                    continue;
68                }
69            };
70
71            let time_word = caps
72                .get(1)
73                .map(|m| m.as_str().to_lowercase())
74                .unwrap_or_default();
75
76            let mut components = context.create_components();
77
78            // Determine target date (same day by default)
79            let mut target_date = ref_date;
80
81            match time_word.as_str() {
82                "mediodía" | "mediodia" => {
83                    components.assign(Component::Hour, 12);
84                    components.assign(Component::Minute, 0);
85                    components.assign(Component::Second, 0);
86                    components.assign(Component::Meridiem, Meridiem::PM as i32);
87                }
88                "medianoche" => {
89                    // Midnight is at 00:00 of the next day (if reference is before midnight)
90                    if ref_date.hour() >= 6 {
91                        target_date = ref_date + Duration::days(1);
92                    }
93                    components.assign(Component::Hour, 0);
94                    components.assign(Component::Minute, 0);
95                    components.assign(Component::Second, 0);
96                }
97                "mañana" | "manana" => {
98                    // "mañana" as time of day = morning (6:00)
99                    components.imply(Component::Hour, 6);
100                    components.imply(Component::Minute, 0);
101                    components.assign(Component::Meridiem, Meridiem::AM as i32);
102                }
103                "tarde" => {
104                    components.imply(Component::Hour, 15);
105                    components.imply(Component::Minute, 0);
106                    components.assign(Component::Meridiem, Meridiem::PM as i32);
107                }
108                "noche" => {
109                    components.imply(Component::Hour, 22);
110                    components.imply(Component::Minute, 0);
111                    components.assign(Component::Meridiem, Meridiem::PM as i32);
112                }
113                _ => continue,
114            }
115
116            // Set date components
117            components.assign(Component::Year, target_date.year());
118            components.assign(Component::Month, target_date.month() as i32);
119            components.assign(Component::Day, target_date.day() as i32);
120
121            results.push(context.create_result(
122                index,
123                index + matched_text.len(),
124                components,
125                None,
126            ));
127
128            start = index + matched_text.len();
129        }
130
131        Ok(results)
132    }
133}
134
135impl Default for ESCasualTimeParser {
136    fn default() -> Self {
137        Self::new()
138    }
139}