whichtime_sys/parsers/en/
iso_format.rs1use crate::components::Component;
4use crate::context::ParsingContext;
5use crate::error::Result;
6use crate::parsers::Parser;
7use crate::results::ParsedResult;
8use regex::Regex;
9use std::sync::LazyLock;
10
11static PATTERN: LazyLock<Regex> = LazyLock::new(|| {
13 Regex::new(
14 r"(?i)(\d{4})-(\d{1,2})-(\d{1,2})(?:T(\d{1,2}):(\d{2})(?::(\d{2}))?(?:\.(\d{1,3}))?)?(?:(Z)|([+-])(\d{2}):?(\d{2}))?"
15 ).unwrap()
16});
17
18pub struct ISOFormatParser;
20
21impl Parser for ISOFormatParser {
22 fn name(&self) -> &'static str {
23 "ISOFormatParser"
24 }
25
26 fn should_apply(&self, context: &ParsingContext) -> bool {
27 let text = context.lower_text();
29 text.contains('-') && text.bytes().any(|b| b.is_ascii_digit())
30 }
31
32 fn parse(&self, context: &ParsingContext) -> Result<Vec<ParsedResult>> {
33 let mut results = Vec::new();
34
35 for mat in PATTERN.find_iter(context.text) {
36 let matched_text = mat.as_str();
37 let index = mat.start();
38
39 let Some(caps) = PATTERN.captures(matched_text) else {
41 continue;
42 };
43
44 let mut components = context.create_components();
45
46 if let Some(year_match) = caps.get(1)
48 && let Ok(year) = year_match.as_str().parse::<i32>()
49 {
50 components.assign(Component::Year, year);
51 }
52
53 if let Some(month_match) = caps.get(2)
55 && let Ok(month) = month_match.as_str().parse::<i32>()
56 {
57 if !(1..=12).contains(&month) {
58 continue;
59 }
60 components.assign(Component::Month, month);
61 }
62
63 if let Some(day_match) = caps.get(3)
65 && let Ok(day) = day_match.as_str().parse::<i32>()
66 {
67 if !(1..=31).contains(&day) {
68 continue;
69 }
70 components.assign(Component::Day, day);
71 }
72
73 if let Some(hour_match) = caps.get(4)
75 && let Ok(hour) = hour_match.as_str().parse::<i32>()
76 {
77 if !(0..=23).contains(&hour) {
78 continue;
79 }
80 components.assign(Component::Hour, hour);
81 }
82
83 if let Some(min_match) = caps.get(5)
85 && let Ok(min) = min_match.as_str().parse::<i32>()
86 {
87 components.assign(Component::Minute, min);
88 }
89
90 if let Some(sec_match) = caps.get(6)
92 && let Ok(sec) = sec_match.as_str().parse::<i32>()
93 {
94 components.assign(Component::Second, sec);
95 }
96
97 if let Some(ms_match) = caps.get(7)
99 && let Ok(ms) = ms_match.as_str().parse::<i32>()
100 {
101 components.assign(Component::Millisecond, ms);
102 }
103
104 if caps.get(8).is_some() {
106 components.assign(Component::TimezoneOffset, 0);
107 }
108 else if let (Some(sign), Some(tz_hour), Some(tz_min)) =
110 (caps.get(9), caps.get(10), caps.get(11))
111 {
112 let sign = if sign.as_str() == "-" { -1 } else { 1 };
113 let hours: i32 = tz_hour.as_str().parse().unwrap_or(0);
114 let mins: i32 = tz_min.as_str().parse().unwrap_or(0);
115 let offset = sign * (hours * 60 + mins);
116 components.assign(Component::TimezoneOffset, offset);
117 }
118
119 if !components.is_valid_date() {
121 continue;
122 }
123
124 results.push(context.create_result(
125 index,
126 index + matched_text.len(),
127 components,
128 None,
129 ));
130 }
131
132 Ok(results)
133 }
134}