1use crate::error::Error;
2use crate::parsing::ast::*;
3use crate::Source;
4
5use chrono::{Datelike, Timelike};
6use rust_decimal::Decimal;
7use std::str::FromStr;
8
9pub(crate) fn parse_duration_from_string(value_str: &str, source: &Source) -> Result<Value, Error> {
12 let trimmed = value_str.trim();
13 let mut parts: Vec<&str> = trimmed.split_whitespace().collect();
14 if parts.len() < 2 {
15 return Err(Error::validation(
16 format!(
17 "Invalid duration: '{}'. Expected format: <number> <unit> (e.g. 10 hours, 2 weeks)",
18 value_str
19 ),
20 Some(source.clone()),
21 None::<String>,
22 ));
23 }
24 let unit_str = parts.pop().unwrap();
25 let number_str = parts.join(" ").replace(['_', ','], "");
26 let digit_count = number_str.chars().filter(|c| c.is_ascii_digit()).count();
27 if digit_count > crate::limits::MAX_NUMBER_DIGITS {
28 return Err(Error::validation(
29 format!(
30 "Number has too many digits (max {})",
31 crate::limits::MAX_NUMBER_DIGITS
32 ),
33 Some(source.clone()),
34 None::<String>,
35 ));
36 }
37 let n = Decimal::from_str(&number_str).map_err(|_| {
38 Error::validation(
39 format!("Invalid duration number: '{}'", number_str),
40 Some(source.clone()),
41 None::<String>,
42 )
43 })?;
44 let unit_lower = unit_str.to_lowercase();
45 let unit = match unit_lower.as_str() {
46 "year" | "years" => DurationUnit::Year,
47 "month" | "months" => DurationUnit::Month,
48 "week" | "weeks" => DurationUnit::Week,
49 "day" | "days" => DurationUnit::Day,
50 "hour" | "hours" => DurationUnit::Hour,
51 "minute" | "minutes" => DurationUnit::Minute,
52 "second" | "seconds" => DurationUnit::Second,
53 "millisecond" | "milliseconds" => DurationUnit::Millisecond,
54 "microsecond" | "microseconds" => DurationUnit::Microsecond,
55 _ => {
56 return Err(Error::validation(
57 format!(
58 "Unknown duration unit: '{}'. Expected one of: years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds",
59 unit_str
60 ),
61 Some(source.clone()),
62 None::<String>,
63 ));
64 }
65 };
66 Ok(Value::Duration(n, unit))
67}
68
69pub(crate) fn parse_number_unit_string(s: &str) -> Result<(Decimal, String), String> {
73 let trimmed = s.trim();
74 let mut parts = trimmed.split_whitespace();
75 let number_part = parts.next().ok_or_else(|| {
76 if trimmed.is_empty() {
77 "Scale value cannot be empty. Use a number followed by a unit (e.g. '10 eur')."
78 .to_string()
79 } else {
80 format!(
81 "Invalid scale value: '{}'. Scale value must be a number followed by a unit (e.g. '10 eur').",
82 s
83 )
84 }
85 })?;
86 let unit_part = parts.next().ok_or_else(|| {
87 format!(
88 "Scale value must include a unit (e.g. '{} eur').",
89 number_part
90 )
91 })?;
92 let clean = number_part.replace(['_', ','], "");
93 let digit_count = clean.chars().filter(|c| c.is_ascii_digit()).count();
94 if digit_count > crate::limits::MAX_NUMBER_DIGITS {
95 return Err(format!(
96 "Number has too many digits (max {})",
97 crate::limits::MAX_NUMBER_DIGITS
98 ));
99 }
100 let n = Decimal::from_str(&clean).map_err(|_| format!("Invalid scale: '{}'", s))?;
101 Ok((n, unit_part.to_string()))
102}
103
104pub(crate) fn parse_datetime_str(s: &str) -> Option<DateTimeValue> {
105 if let Ok(dt) = s.parse::<chrono::DateTime<chrono::FixedOffset>>() {
106 let offset = dt.offset().local_minus_utc();
107 let microsecond = dt.nanosecond() / 1000 % 1_000_000;
108 return Some(DateTimeValue {
109 year: dt.year(),
110 month: dt.month(),
111 day: dt.day(),
112 hour: dt.hour(),
113 minute: dt.minute(),
114 second: dt.second(),
115 microsecond,
116 timezone: Some(TimezoneValue {
117 offset_hours: (offset / 3600) as i8,
118 offset_minutes: ((offset % 3600) / 60) as u8,
119 }),
120 });
121 }
122 if let Ok(dt) = s.parse::<chrono::NaiveDateTime>() {
123 let microsecond = dt.nanosecond() / 1000 % 1_000_000;
124 return Some(DateTimeValue {
125 year: dt.year(),
126 month: dt.month(),
127 day: dt.day(),
128 hour: dt.hour(),
129 minute: dt.minute(),
130 second: dt.second(),
131 microsecond,
132 timezone: None,
133 });
134 }
135 if let Ok(d) = s.parse::<chrono::NaiveDate>() {
136 return Some(DateTimeValue {
137 year: d.year(),
138 month: d.month(),
139 day: d.day(),
140 hour: 0,
141 minute: 0,
142 second: 0,
143 microsecond: 0,
144 timezone: None,
145 });
146 }
147 None
148}
149
150pub fn parse_date_string(s: &str) -> Result<DateTimeValue, String> {
152 if let Some(dtv) = parse_datetime_str(s) {
153 return Ok(dtv);
154 }
155 if let Some(dtv) = DateTimeValue::parse(s) {
156 return Ok(dtv);
157 }
158 Err(format!("Invalid date format: '{}'", s))
159}
160
161pub fn parse_time_string(s: &str) -> Result<TimeValue, String> {
163 if let Ok(t) = s.parse::<chrono::DateTime<chrono::FixedOffset>>() {
164 let offset = t.offset().local_minus_utc();
165 return Ok(TimeValue {
166 hour: t.hour() as u8,
167 minute: t.minute() as u8,
168 second: t.second() as u8,
169 timezone: Some(TimezoneValue {
170 offset_hours: (offset / 3600) as i8,
171 offset_minutes: ((offset % 3600) / 60) as u8,
172 }),
173 });
174 }
175
176 if let Ok(t) = s.parse::<chrono::NaiveTime>() {
177 return Ok(TimeValue {
178 hour: t.hour() as u8,
179 minute: t.minute() as u8,
180 second: t.second() as u8,
181 timezone: None,
182 });
183 }
184
185 Err(format!("Invalid time format: '{}'", s))
186}