json_eval_rs/rlogic/evaluator/
date_ops.rs1use super::Evaluator;
2use serde_json::Value;
3use super::super::compiled::CompiledLogic;
4use super::helpers;
5use chrono::Datelike;
6
7impl Evaluator {
8 #[inline]
10 fn unwrap_array(val: Value) -> Value {
11 if let Value::Array(arr) = &val {
12 if arr.len() == 1 {
13 return arr[0].clone();
14 }
15 }
16 val
17 }
18
19 #[inline]
22 pub(super) fn parse_date(&self, date_str: &str) -> Option<chrono::NaiveDate> {
23 use chrono::NaiveDate;
24
25 let formats = [
27 "%Y-%m-%dT%H:%M:%S%.fZ", "%Y-%m-%dT%H:%M:%SZ", "%Y-%m-%dT%H:%M:%S%.f", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M:%S%z", "%Y-%m-%dT%H:%M:%S%#z", "%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M", "%Y-%m-%d", "%Y/%m/%d", "%Y.%m.%d", "%m/%d/%Y", "%m-%d-%Y", "%d/%m/%Y", "%d-%m-%Y", "%d.%m.%Y", ];
49
50 for format in &formats {
51 if let Ok(date) = NaiveDate::parse_from_str(date_str, format) {
52 return Some(date);
53 }
54 }
55
56 None
57 }
58
59 pub(super) fn extract_date_component(&self, expr: &CompiledLogic, component: &str, user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
61 let val = self.evaluate_with_context(expr, user_data, internal_context, depth + 1)?;
62 let val = Self::unwrap_array(val);
63
64 if let Value::String(date_str) = &val {
65 if let Some(d) = self.parse_date(date_str) {
66 let value = match component {
67 "year" => d.year() as f64,
68 "month" => d.month() as f64,
69 "day" => d.day() as f64,
70 _ => return Ok(Value::Null),
71 };
72 return Ok(self.f64_to_json(value));
73 }
74 }
75 Ok(Value::Null)
76 }
77
78 pub(super) fn eval_today(&self) -> Result<Value, String> {
80 let now = chrono::Utc::now();
81 Ok(Value::String(helpers::build_iso_date_string(now.date_naive())))
82 }
83
84 pub(super) fn eval_now(&self) -> Result<Value, String> {
86 let now = chrono::Utc::now();
87 Ok(Value::String(now.to_rfc3339_opts(chrono::SecondsFormat::Secs, true)))
88 }
89
90 pub(super) fn eval_days(&self, end_expr: &CompiledLogic, start_expr: &CompiledLogic, user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
92 let end_val = self.evaluate_with_context(end_expr, user_data, internal_context, depth + 1)?;
93 let start_val = self.evaluate_with_context(start_expr, user_data, internal_context, depth + 1)?;
94
95 let end_val = Self::unwrap_array(end_val);
96 let start_val = Self::unwrap_array(start_val);
97
98 if let (Value::String(end), Value::String(start)) = (&end_val, &start_val) {
99 if let (Some(e), Some(s)) = (self.parse_date(end), self.parse_date(start)) {
100 return Ok(self.f64_to_json((e - s).num_days() as f64));
101 }
102 }
103 Ok(Value::Null)
104 }
105
106 pub(super) fn eval_date(&self, year_expr: &CompiledLogic, month_expr: &CompiledLogic, day_expr: &CompiledLogic, user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
109 let year_val = self.evaluate_with_context(year_expr, user_data, internal_context, depth + 1)?;
110 let month_val = self.evaluate_with_context(month_expr, user_data, internal_context, depth + 1)?;
111 let day_val = self.evaluate_with_context(day_expr, user_data, internal_context, depth + 1)?;
112
113 let year = helpers::to_number(&year_val) as i32;
114 let month = helpers::to_number(&month_val) as i32;
115 let day = helpers::to_number(&day_val) as i32;
116
117 use chrono::{NaiveDate, Duration};
118
119 let mut normalized_year = year;
125 let mut normalized_month = month;
126
127 if normalized_month < 1 {
129 let months_back = (1 - normalized_month) / 12 + 1;
130 normalized_year -= months_back;
131 normalized_month += months_back * 12;
132 } else if normalized_month > 12 {
133 let months_forward = (normalized_month - 1) / 12;
134 normalized_year += months_forward;
135 normalized_month = ((normalized_month - 1) % 12) + 1;
136 }
137
138 if let Some(base_date) = NaiveDate::from_ymd_opt(normalized_year, normalized_month as u32, 1) {
140 if let Some(final_date) = base_date.checked_add_signed(Duration::days((day - 1) as i64)) {
143 Ok(Value::String(helpers::build_iso_date_string(final_date)))
144 } else {
145 Ok(Value::Null)
147 }
148 } else {
149 Ok(Value::Null)
151 }
152 }
153
154 pub(super) fn eval_year_frac(&self, start_expr: &CompiledLogic, end_expr: &CompiledLogic, basis_expr: &Option<Box<CompiledLogic>>, user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
156 let start_val = self.evaluate_with_context(start_expr, user_data, internal_context, depth + 1)?;
157 let end_val = self.evaluate_with_context(end_expr, user_data, internal_context, depth + 1)?;
158
159 let start_val = Self::unwrap_array(start_val);
160 let end_val = Self::unwrap_array(end_val);
161
162 let basis = if let Some(b_expr) = basis_expr {
163 let b_val = self.evaluate_with_context(b_expr, user_data, internal_context, depth + 1)?;
164 helpers::to_number(&b_val) as i32
165 } else { 0 };
166
167 if let (Value::String(start_str), Value::String(end_str)) = (&start_val, &end_val) {
168 if let (Some(start), Some(end)) = (self.parse_date(start_str), self.parse_date(end_str)) {
169 let days = (end - start).num_days() as f64;
170 let result = match basis {
171 0 => days / 360.0,
172 1 => days / 365.25,
173 2 => days / 360.0,
174 3 => days / 365.0,
175 4 => days / 360.0,
176 _ => days / 365.0,
177 };
178 return Ok(self.f64_to_json(result));
179 }
180 }
181 Ok(Value::Null)
182 }
183
184 pub(super) fn eval_date_dif(&self, start_expr: &CompiledLogic, end_expr: &CompiledLogic, unit_expr: &CompiledLogic, user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
186 let start_val = self.evaluate_with_context(start_expr, user_data, internal_context, depth + 1)?;
187 let end_val = self.evaluate_with_context(end_expr, user_data, internal_context, depth + 1)?;
188 let unit_val = self.evaluate_with_context(unit_expr, user_data, internal_context, depth + 1)?;
189
190 let start_val = Self::unwrap_array(start_val);
191 let end_val = Self::unwrap_array(end_val);
192 let unit_val = Self::unwrap_array(unit_val);
193
194 if let (Value::String(start_str), Value::String(end_str), Value::String(unit)) =
195 (&start_val, &end_val, &unit_val) {
196 if let (Some(start), Some(end)) = (self.parse_date(start_str), self.parse_date(end_str)) {
197 let result = match unit.to_uppercase().as_str() {
198 "D" => (end - start).num_days() as f64,
199 "M" => {
200 let years = end.year() - start.year();
201 let months = end.month() as i32 - start.month() as i32;
202 let mut total_months = years * 12 + months;
203 if end.day() < start.day() {
204 total_months -= 1;
205 }
206 total_months as f64
207 }
208 "Y" => {
209 let mut years = end.year() - start.year();
210 if end.month() < start.month() ||
211 (end.month() == start.month() && end.day() < start.day()) {
212 years -= 1;
213 }
214 years as f64
215 }
216 "MD" => {
217 if start.day() <= end.day() {
218 (end.day() - start.day()) as f64
219 } else {
220 let days_in_month = 30u32; (days_in_month as i32 - (start.day() as i32 - end.day() as i32)) as f64
222 }
223 }
224 "YM" => {
225 let months = end.month() as i32 - start.month() as i32;
226 let mut result = if months < 0 { months + 12 } else { months };
227 if end.day() < start.day() {
228 result -= 1;
229 if result < 0 {
230 result += 12;
231 }
232 }
233 result as f64
234 }
235 "YD" => {
236 let mut temp_start = start.with_year(end.year()).unwrap_or(start);
237 if temp_start > end {
238 temp_start = start.with_year(end.year() - 1).unwrap_or(start);
239 }
240 (end - temp_start).num_days() as f64
241 }
242 _ => return Ok(Value::Null),
243 };
244
245 Ok(self.f64_to_json(result))
246 } else {
247 Ok(Value::Null)
248 }
249 } else {
250 Ok(Value::Null)
251 }
252 }
253
254 pub(super) fn eval_date_format(
257 &self,
258 date_expr: &CompiledLogic,
259 format_expr: &Option<Box<CompiledLogic>>,
260 user_data: &Value,
261 internal_context: &Value,
262 depth: usize,
263 ) -> Result<Value, String> {
264 let date_val = self.evaluate_with_context(date_expr, user_data, internal_context, depth + 1)?;
265 let date_val = Self::unwrap_array(date_val);
266
267 let date = if let Value::String(date_str) = &date_val {
269 self.parse_date(date_str)
270 } else {
271 None
272 };
273
274 if date.is_none() {
275 return Ok(Value::Null);
276 }
277 let date = date.unwrap();
278
279 let format_str = if let Some(fmt_expr) = format_expr {
281 let fmt_val = self.evaluate_with_context(fmt_expr, user_data, internal_context, depth + 1)?;
282 super::helpers::to_string(&fmt_val)
283 } else {
284 "iso".to_string()
285 };
286
287 let formatted = match format_str.to_lowercase().as_str() {
289 "short" => date.format("%m/%d/%Y").to_string(), "long" => date.format("%B %d, %Y").to_string(), "iso" => date.format("%Y-%m-%d").to_string(), "us" => date.format("%m/%d/%Y").to_string(), "eu" => date.format("%d/%m/%Y").to_string(), "full" => date.format("%A, %B %d, %Y").to_string(), "monthday" => date.format("%B %d").to_string(), "yearmonth" => date.format("%Y-%m").to_string(), "ddmmyyyy" => date.format("%d/%m/%Y").to_string(), "mmddyyyy" => date.format("%m/%d/%Y").to_string(), "yyyymmdd" => date.format("%Y-%m-%d").to_string(), "dd-mm-yyyy" => date.format("%d-%m-%Y").to_string(), "mm-dd-yyyy" => date.format("%m-%d-%Y").to_string(), "yyyy-mm-dd" => date.format("%Y-%m-%d").to_string(), "dd.mm.yyyy" => date.format("%d.%m.%Y").to_string(), _ => {
305 date.format(&format_str).to_string()
307 }
308 };
309
310 Ok(Value::String(formatted))
311 }
312}