Skip to main content

whichtime_sys/parsers/sv/
casual_date.rs

1//! Swedish casual date parser
2//!
3//! Handles Swedish casual date expressions like:
4//! - "idag", "imorgon", "igår"
5//! - "idag på kvällen", "imorgon vid middag"
6
7use crate::components::Component;
8use crate::context::ParsingContext;
9use crate::error::Result;
10use crate::parsers::Parser;
11use crate::results::ParsedResult;
12use crate::types::Meridiem;
13use chrono::{Datelike, Duration, Timelike};
14use fancy_regex::Regex;
15use std::sync::LazyLock;
16
17static PATTERN: LazyLock<Regex> = LazyLock::new(|| {
18    Regex::new(
19        r"(?i)(?<![a-zA-ZåäöÅÄÖ])(nu|idag|i\s*dag|ikväll|i\s*kväll|ikvall|i\s*kvall|imorgon|i\s*morgon|igår|i\s*går|igar|i\s*gar|övermorgon|i\s*övermorgon|overmorgon|i\s*overmorgon|förrgår|i\s*förrgår|forrgar|i\s*forrgar)(?:\s+(på|vid)\s+(morgonen|förmiddagen|formiddagen|middagen|eftermiddagen|kvällen|kvallen|natten|midnatt|middag))?(?![a-zA-ZåäöÅÄÖ])"
20    ).unwrap()
21});
22
23const DATE_GROUP: usize = 1;
24const TIME_GROUP: usize = 3;
25
26/// Swedish casual date parser
27pub struct SVCasualDateParser;
28
29impl SVCasualDateParser {
30    pub fn new() -> Self {
31        Self
32    }
33
34    fn assign_time_part(components: &mut crate::components::FastComponents, time_part: &str) {
35        match time_part.to_lowercase().as_str() {
36            "morgonen" => {
37                components.assign(Component::Hour, 6);
38                components.assign(Component::Minute, 0);
39                components.assign(Component::Meridiem, Meridiem::AM as i32);
40            }
41            "förmiddagen" | "formiddagen" => {
42                components.assign(Component::Hour, 9);
43                components.assign(Component::Minute, 0);
44                components.assign(Component::Meridiem, Meridiem::AM as i32);
45            }
46            "middagen" | "middag" => {
47                components.assign(Component::Hour, 12);
48                components.assign(Component::Minute, 0);
49                components.assign(Component::Meridiem, Meridiem::PM as i32);
50            }
51            "eftermiddagen" => {
52                components.assign(Component::Hour, 15);
53                components.assign(Component::Minute, 0);
54                components.assign(Component::Meridiem, Meridiem::PM as i32);
55            }
56            "kvällen" | "kvallen" => {
57                components.assign(Component::Hour, 20);
58                components.assign(Component::Minute, 0);
59                components.assign(Component::Meridiem, Meridiem::PM as i32);
60            }
61            "natten" => {
62                components.assign(Component::Hour, 2);
63                components.assign(Component::Minute, 0);
64                components.assign(Component::Meridiem, Meridiem::AM as i32);
65            }
66            "midnatt" => {
67                components.assign(Component::Hour, 0);
68                components.assign(Component::Minute, 0);
69                components.assign(Component::Second, 0);
70            }
71            _ => {}
72        }
73    }
74}
75
76impl Default for SVCasualDateParser {
77    fn default() -> Self {
78        Self::new()
79    }
80}
81
82impl Parser for SVCasualDateParser {
83    fn name(&self) -> &'static str {
84        "SVCasualDateParser"
85    }
86
87    fn should_apply(&self, _context: &ParsingContext) -> bool {
88        true
89    }
90
91    fn parse(&self, context: &ParsingContext) -> Result<Vec<ParsedResult>> {
92        let mut results = Vec::new();
93        let ref_date = context.reference.instant;
94
95        let mut start = 0;
96        while start < context.text.len() {
97            let search_text = &context.text[start..];
98            let captures = match PATTERN.captures(search_text) {
99                Ok(Some(caps)) => caps,
100                Ok(None) => break,
101                Err(_) => break,
102            };
103
104            let full_match = match captures.get(0) {
105                Some(m) => m,
106                None => break,
107            };
108
109            let match_start = start + full_match.start();
110            let match_end = start + full_match.end();
111
112            let date_keyword = captures
113                .get(DATE_GROUP)
114                .map(|m| m.as_str().to_lowercase().replace(" ", ""))
115                .unwrap_or_default();
116
117            let time_part = captures.get(TIME_GROUP).map(|m| m.as_str());
118
119            let mut components = context.create_components();
120            let mut target_date = ref_date;
121
122            match date_keyword.as_str() {
123                "nu" => {
124                    components.assign(Component::Hour, ref_date.hour() as i32);
125                    components.assign(Component::Minute, ref_date.minute() as i32);
126                    components.assign(Component::Second, ref_date.second() as i32);
127                }
128                "idag" => {}
129                "ikväll" | "ikvall" => {
130                    components.imply(Component::Hour, 20);
131                    components.imply(Component::Minute, 0);
132                }
133                "imorgon" => {
134                    target_date = ref_date + Duration::days(1);
135                }
136                "igår" | "igar" => {
137                    target_date = ref_date - Duration::days(1);
138                }
139                "övermorgon" | "overmorgon" | "iövermorgon" | "iovermorgon" => {
140                    target_date = ref_date + Duration::days(2);
141                }
142                "förrgår" | "forrgar" | "iförrgår" | "iforrgar" => {
143                    target_date = ref_date - Duration::days(2);
144                }
145                _ => {}
146            }
147
148            components.assign(Component::Year, target_date.year());
149            components.assign(Component::Month, target_date.month() as i32);
150            components.assign(Component::Day, target_date.day() as i32);
151
152            // Apply time part if present
153            if let Some(tp) = time_part {
154                Self::assign_time_part(&mut components, tp);
155            }
156
157            results.push(context.create_result(match_start, match_end, components, None));
158            start = match_end;
159        }
160
161        Ok(results)
162    }
163}