whichtime_sys/parsers/fr/
time_unit_relative.rs1use crate::components::Component;
10use crate::context::ParsingContext;
11use crate::error::Result;
12use crate::parsers::Parser;
13use crate::results::ParsedResult;
14use chrono::{Datelike, Duration, Timelike};
15use fancy_regex::Regex;
16use std::sync::LazyLock;
17
18static PATTERN: LazyLock<Regex> = LazyLock::new(|| {
20 Regex::new(
21 r"(?i)(?:l[ae]s?)\s+(?:(\d+|deux|trois|quatre|cinq|six|sept|huit|neuf|dix|une?)\s+)?(?:(prochaine?s?|dernière?s?|dernier|passée?s?|passe)\s+)?(semaines?|mois|ans?|années?|annees?)(?:\s+(prochaine?|dernière?|dernier|passée?|passe))?\b"
22 ).unwrap()
23});
24
25pub struct FRTimeUnitRelativeParser;
27
28impl FRTimeUnitRelativeParser {
29 pub fn new() -> Self {
30 Self
31 }
32
33 fn parse_number(s: &str) -> i32 {
34 match s.to_lowercase().as_str() {
35 "un" | "une" => 1,
36 "deux" => 2,
37 "trois" => 3,
38 "quatre" => 4,
39 "cinq" => 5,
40 "six" => 6,
41 "sept" => 7,
42 "huit" => 8,
43 "neuf" => 9,
44 "dix" => 10,
45 _ => s.parse().unwrap_or(1),
46 }
47 }
48}
49
50impl Parser for FRTimeUnitRelativeParser {
51 fn name(&self) -> &'static str {
52 "FRTimeUnitRelativeParser"
53 }
54
55 fn should_apply(&self, _context: &ParsingContext) -> bool {
56 true
57 }
58
59 fn parse(&self, context: &ParsingContext) -> Result<Vec<ParsedResult>> {
60 let mut results = Vec::new();
61 let ref_date = context.reference.instant;
62
63 let mut start = 0;
64 while start < context.text.len() {
65 let search_text = &context.text[start..];
66 let mat = match PATTERN.find(search_text) {
67 Ok(Some(m)) => m,
68 Ok(None) => break,
69 Err(_) => break,
70 };
71
72 let matched_text = mat.as_str();
73 let index = start + mat.start();
74
75 let caps = match PATTERN.captures(matched_text) {
76 Ok(Some(c)) => c,
77 Ok(None) => {
78 start = index + 1;
79 continue;
80 }
81 Err(_) => {
82 start = index + 1;
83 continue;
84 }
85 };
86
87 let num = caps
89 .get(1)
90 .map(|m| Self::parse_number(m.as_str()))
91 .unwrap_or(1);
92
93 let modifier_before = caps.get(2).map(|m| m.as_str().to_lowercase());
95 let modifier_after = caps.get(4).map(|m| m.as_str().to_lowercase());
96 let modifier = modifier_before.or(modifier_after).unwrap_or_default();
97
98 let unit = caps
100 .get(3)
101 .map(|m| m.as_str().to_lowercase())
102 .unwrap_or_default();
103
104 let is_future = modifier.contains("prochain");
106 let multiplier = if is_future { 1 } else { -1 };
107 let amount = num * multiplier;
108
109 let target_date = match unit.as_str() {
111 "semaine" | "semaines" => ref_date + Duration::weeks(amount as i64),
112 "mois" => {
113 let new_month = ref_date.month() as i32 + amount;
114 let (year_offset, month) = if new_month <= 0 {
115 ((new_month - 12) / 12, ((new_month - 1) % 12 + 13) as u32)
116 } else if new_month > 12 {
117 ((new_month - 1) / 12, ((new_month - 1) % 12 + 1) as u32)
118 } else {
119 (0, new_month as u32)
120 };
121 let new_year = ref_date.year() + year_offset;
122 ref_date
123 .with_year(new_year)
124 .and_then(|d| d.with_month(month))
125 .unwrap_or(ref_date)
126 }
127 "an" | "année" | "annee" | "ans" | "années" | "annees" => {
128 let new_year = ref_date.year() + amount;
129 ref_date.with_year(new_year).unwrap_or(ref_date)
130 }
131 _ => {
132 start = index + matched_text.len();
133 continue;
134 }
135 };
136
137 let mut components = context.create_components();
138 components.assign(Component::Year, target_date.year());
139 components.assign(Component::Month, target_date.month() as i32);
140 components.assign(Component::Day, target_date.day() as i32);
141
142 components.imply(Component::Hour, ref_date.hour() as i32);
144 components.imply(Component::Minute, ref_date.minute() as i32);
145 components.imply(Component::Second, ref_date.second() as i32);
146
147 results.push(context.create_result(
148 index,
149 index + matched_text.len(),
150 components,
151 None,
152 ));
153
154 start = index + matched_text.len();
155 }
156
157 Ok(results)
158 }
159}
160
161impl Default for FRTimeUnitRelativeParser {
162 fn default() -> Self {
163 Self::new()
164 }
165}