Skip to main content

whichtime_sys/parsers/sv/
month_name.rs

1//! Swedish month name parser
2//!
3//! Handles Swedish date expressions with month names like:
4//! - "15 augusti 2012"
5//! - "15 aug 2012"
6//! - "den 15 augusti"
7//! - "15-16 augusti" (date ranges)
8//! - "15 till 16 augusti" (date ranges)
9
10use crate::components::Component;
11use crate::context::ParsingContext;
12use crate::dictionaries::sv::get_month;
13use crate::error::Result;
14use crate::parsers::Parser;
15use crate::results::ParsedResult;
16use chrono::Datelike;
17use fancy_regex::Regex;
18use std::sync::LazyLock;
19
20static PATTERN: LazyLock<Regex> = LazyLock::new(|| {
21    Regex::new(
22        r"(?ix)
23        (?:den\s+)?
24        (?P<day>\d{1,2})
25        (?:
26            \s*(?:-|–|till)\s*
27            (?P<end_day>\d{1,2})
28        )?
29        \s+
30        (?P<month>januari|februari|mars|april|maj|juni|juli|augusti|september|oktober|november|december|jan\.?|feb\.?|mar\.?|apr\.?|jun\.?|jul\.?|aug\.?|sep\.?|sept\.?|okt\.?|nov\.?|dec\.?)
31        (?:
32            \s+
33            (?P<year>\d{4}|\d{2})
34        )?
35        (?![a-zA-ZåäöÅÄÖ])"
36    ).unwrap()
37});
38
39/// Swedish month name parser
40pub struct SVMonthNameParser;
41
42impl SVMonthNameParser {
43    pub fn new() -> Self {
44        Self
45    }
46}
47
48impl Default for SVMonthNameParser {
49    fn default() -> Self {
50        Self::new()
51    }
52}
53
54impl Parser for SVMonthNameParser {
55    fn name(&self) -> &'static str {
56        "SVMonthNameParser"
57    }
58
59    fn should_apply(&self, _context: &ParsingContext) -> bool {
60        true
61    }
62
63    fn parse(&self, context: &ParsingContext) -> Result<Vec<ParsedResult>> {
64        let mut results = Vec::new();
65        let ref_date = context.reference.instant;
66
67        let mut start = 0;
68        while start < context.text.len() {
69            let search_text = &context.text[start..];
70            let captures = match PATTERN.captures(search_text) {
71                Ok(Some(caps)) => caps,
72                Ok(None) => break,
73                Err(_) => break,
74            };
75
76            let full_match = match captures.get(0) {
77                Some(m) => m,
78                None => break,
79            };
80
81            let match_start = start + full_match.start();
82            let match_end = start + full_match.end();
83
84            let day_str = captures.name("day").map(|m| m.as_str());
85            let month_str = captures
86                .name("month")
87                .map(|m| m.as_str().to_lowercase())
88                .unwrap_or_default();
89            let year_str = captures.name("year").map(|m| m.as_str());
90            let end_day_str = captures.name("end_day").map(|m| m.as_str());
91
92            // Parse day
93            let day: i32 = day_str.and_then(|d| d.parse().ok()).unwrap_or(1);
94
95            // Parse month - remove trailing dot if present
96            let clean_month = month_str.trim_end_matches('.');
97            let month = get_month(clean_month).unwrap_or(0);
98
99            if month == 0 || !(1..=31).contains(&day) {
100                start = match_end;
101                continue;
102            }
103
104            let mut components = context.create_components();
105
106            // Parse year
107            if let Some(y) = year_str {
108                let mut year: i32 = y.parse().unwrap_or(ref_date.year());
109                if year < 100 {
110                    year = if year > 50 { 1900 + year } else { 2000 + year };
111                }
112                components.assign(Component::Year, year);
113            } else {
114                components.imply(Component::Year, ref_date.year());
115            }
116
117            components.assign(Component::Month, month as i32);
118            components.assign(Component::Day, day);
119
120            if !components.is_valid_date() {
121                start = match_end;
122                continue;
123            }
124
125            // Handle end date for ranges
126            let end_components = if let Some(end_day_text) = end_day_str {
127                let end_day: i32 = end_day_text.parse().unwrap_or(0);
128                if end_day > 0 && end_day <= 31 {
129                    let mut end_comp = context.create_components();
130                    if let Some(start_year) = components.get(Component::Year) {
131                        if year_str.is_some() {
132                            end_comp.assign(Component::Year, start_year);
133                        } else {
134                            end_comp.imply(Component::Year, start_year);
135                        }
136                    }
137                    end_comp.assign(Component::Month, month as i32);
138                    end_comp.assign(Component::Day, end_day);
139
140                    if end_comp.is_valid_date() {
141                        Some(end_comp)
142                    } else {
143                        None
144                    }
145                } else {
146                    None
147                }
148            } else {
149                None
150            };
151
152            results.push(context.create_result(match_start, match_end, components, end_components));
153            start = match_end;
154        }
155
156        Ok(results)
157    }
158}