1use std::collections::HashSet;
4
5use chrono::{DateTime, Datelike, NaiveDateTime, TimeDelta, TimeZone, Utc, Weekday};
6use chrono_tz::Tz;
7use serde_json::Value;
8
9use crate::functions::{Function, custom_error, number_value};
10use crate::interpreter::SearchResult;
11use crate::registry::register_if_enabled;
12use crate::{Context, Runtime, arg, defn};
13
14pub fn register_filtered(runtime: &mut Runtime, enabled: &HashSet<&str>) {
16 register_if_enabled(runtime, "now", enabled, Box::new(NowFn::new()));
17 register_if_enabled(runtime, "now_millis", enabled, Box::new(NowMillisFn::new()));
18 register_if_enabled(runtime, "parse_date", enabled, Box::new(ParseDateFn::new()));
19 register_if_enabled(
20 runtime,
21 "format_date",
22 enabled,
23 Box::new(FormatDateFn::new()),
24 );
25 register_if_enabled(runtime, "date_add", enabled, Box::new(DateAddFn::new()));
26 register_if_enabled(runtime, "date_diff", enabled, Box::new(DateDiffFn::new()));
27 register_if_enabled(
28 runtime,
29 "timezone_convert",
30 enabled,
31 Box::new(TimezoneConvertFn::new()),
32 );
33 register_if_enabled(runtime, "is_weekend", enabled, Box::new(IsWeekendFn::new()));
34 register_if_enabled(runtime, "is_weekday", enabled, Box::new(IsWeekdayFn::new()));
35 register_if_enabled(
36 runtime,
37 "business_days_between",
38 enabled,
39 Box::new(BusinessDaysBetweenFn::new()),
40 );
41 register_if_enabled(
42 runtime,
43 "relative_time",
44 enabled,
45 Box::new(RelativeTimeFn::new()),
46 );
47 register_if_enabled(runtime, "quarter", enabled, Box::new(QuarterFn::new()));
48 register_if_enabled(runtime, "is_after", enabled, Box::new(IsAfterFn::new()));
49 register_if_enabled(runtime, "is_before", enabled, Box::new(IsBeforeFn::new()));
50 register_if_enabled(runtime, "is_between", enabled, Box::new(IsBetweenFn::new()));
51 register_if_enabled(runtime, "time_ago", enabled, Box::new(TimeAgoFn::new()));
52 register_if_enabled(runtime, "from_epoch", enabled, Box::new(FromEpochFn::new()));
53 register_if_enabled(
54 runtime,
55 "from_epoch_ms",
56 enabled,
57 Box::new(FromEpochMsFn::new()),
58 );
59 register_if_enabled(runtime, "to_epoch", enabled, Box::new(ToEpochFn::new()));
60 register_if_enabled(
61 runtime,
62 "to_epoch_ms",
63 enabled,
64 Box::new(ToEpochMsFn::new()),
65 );
66 register_if_enabled(
67 runtime,
68 "duration_since",
69 enabled,
70 Box::new(DurationSinceFn::new()),
71 );
72 register_if_enabled(
73 runtime,
74 "start_of_day",
75 enabled,
76 Box::new(StartOfDayFn::new()),
77 );
78 register_if_enabled(runtime, "end_of_day", enabled, Box::new(EndOfDayFn::new()));
79 register_if_enabled(
80 runtime,
81 "start_of_week",
82 enabled,
83 Box::new(StartOfWeekFn::new()),
84 );
85 register_if_enabled(
86 runtime,
87 "start_of_month",
88 enabled,
89 Box::new(StartOfMonthFn::new()),
90 );
91 register_if_enabled(
92 runtime,
93 "start_of_year",
94 enabled,
95 Box::new(StartOfYearFn::new()),
96 );
97 register_if_enabled(
98 runtime,
99 "is_same_day",
100 enabled,
101 Box::new(IsSameDayFn::new()),
102 );
103 register_if_enabled(runtime, "epoch_ms", enabled, Box::new(NowMillisFn::new()));
105 register_if_enabled(
106 runtime,
107 "parse_datetime",
108 enabled,
109 Box::new(ParseDatetimeFn::new()),
110 );
111 register_if_enabled(
112 runtime,
113 "parse_natural_date",
114 enabled,
115 Box::new(ParseNaturalDateFn::new()),
116 );
117}
118
119defn!(NowFn, vec![], None);
121
122impl Function for NowFn {
123 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
124 self.signature.validate(args, ctx)?;
125 let ts = Utc::now().timestamp();
126 Ok(number_value(ts as f64))
127 }
128}
129
130defn!(NowMillisFn, vec![], None);
132
133impl Function for NowMillisFn {
134 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
135 self.signature.validate(args, ctx)?;
136 let ts = Utc::now().timestamp_millis();
137 Ok(number_value(ts as f64))
138 }
139}
140
141defn!(ParseDateFn, vec![arg!(string)], Some(arg!(string)));
143
144impl Function for ParseDateFn {
145 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
146 self.signature.validate(args, ctx)?;
147
148 let s = args[0].as_str().unwrap();
149
150 if args.len() > 1 {
151 let format = args[1].as_str().unwrap();
153 match NaiveDateTime::parse_from_str(s, format) {
154 Ok(dt) => Ok(number_value(dt.and_utc().timestamp() as f64)),
155 Err(_) => Ok(Value::Null),
156 }
157 } else {
158 if let Ok(dt) = DateTime::parse_from_rfc3339(s) {
160 return Ok(number_value(dt.timestamp() as f64));
161 }
162 if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S") {
163 return Ok(number_value(dt.and_utc().timestamp() as f64));
164 }
165 if let Ok(dt) =
166 NaiveDateTime::parse_from_str(&format!("{}T00:00:00", s), "%Y-%m-%dT%H:%M:%S")
167 {
168 return Ok(number_value(dt.and_utc().timestamp() as f64));
169 }
170 Ok(Value::Null)
171 }
172 }
173}
174
175defn!(FormatDateFn, vec![arg!(number), arg!(string)], None);
177
178impl Function for FormatDateFn {
179 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
180 self.signature.validate(args, ctx)?;
181
182 let ts = args[0].as_f64().unwrap();
183 let format = args[1].as_str().unwrap();
184
185 let dt = Utc.timestamp_opt(ts as i64, 0);
186 match dt {
187 chrono::LocalResult::Single(dt) => Ok(Value::String(dt.format(format).to_string())),
188 _ => Ok(Value::Null),
189 }
190 }
191}
192
193defn!(
195 DateAddFn,
196 vec![arg!(number), arg!(number), arg!(string)],
197 None
198);
199
200impl Function for DateAddFn {
201 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
202 self.signature.validate(args, ctx)?;
203
204 let ts = args[0].as_f64().unwrap();
205 let amount = args[1].as_f64().unwrap();
206 let unit = args[2].as_str().unwrap();
207
208 let duration = match unit.to_lowercase().as_str() {
209 "seconds" | "second" | "s" => TimeDelta::seconds(amount as i64),
210 "minutes" | "minute" | "m" => TimeDelta::minutes(amount as i64),
211 "hours" | "hour" | "h" => TimeDelta::hours(amount as i64),
212 "days" | "day" | "d" => TimeDelta::days(amount as i64),
213 "weeks" | "week" | "w" => TimeDelta::weeks(amount as i64),
214 _ => return Err(custom_error(ctx, &format!("invalid time unit: {}", unit))),
215 };
216
217 let dt = Utc.timestamp_opt(ts as i64, 0);
218 match dt {
219 chrono::LocalResult::Single(dt) => {
220 let new_dt = dt + duration;
221 Ok(number_value(new_dt.timestamp() as f64))
222 }
223 _ => Ok(Value::Null),
224 }
225 }
226}
227
228defn!(
230 DateDiffFn,
231 vec![arg!(number), arg!(number), arg!(string)],
232 None
233);
234
235impl Function for DateDiffFn {
236 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
237 self.signature.validate(args, ctx)?;
238
239 let ts1 = args[0].as_f64().unwrap();
240 let ts2 = args[1].as_f64().unwrap();
241 let unit = args[2].as_str().unwrap();
242
243 let diff_seconds = (ts1 - ts2) as i64;
244
245 let result = match unit.to_lowercase().as_str() {
246 "seconds" | "second" | "s" => diff_seconds as f64,
247 "minutes" | "minute" | "m" => diff_seconds as f64 / 60.0,
248 "hours" | "hour" | "h" => diff_seconds as f64 / 3600.0,
249 "days" | "day" | "d" => diff_seconds as f64 / 86400.0,
250 "weeks" | "week" | "w" => diff_seconds as f64 / 604800.0,
251 _ => return Err(custom_error(ctx, &format!("invalid time unit: {}", unit))),
252 };
253
254 Ok(number_value(result))
255 }
256}
257
258defn!(
260 TimezoneConvertFn,
261 vec![arg!(string), arg!(string), arg!(string)],
262 None
263);
264
265impl Function for TimezoneConvertFn {
266 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
267 self.signature.validate(args, ctx)?;
268
269 let timestamp_str = args[0].as_str().unwrap();
270 let from_tz_str = args[1].as_str().unwrap();
271 let to_tz_str = args[2].as_str().unwrap();
272
273 let from_tz: Tz = from_tz_str
275 .parse()
276 .map_err(|_| custom_error(ctx, &format!("invalid timezone: {}", from_tz_str)))?;
277 let to_tz: Tz = to_tz_str
278 .parse()
279 .map_err(|_| custom_error(ctx, &format!("invalid timezone: {}", to_tz_str)))?;
280
281 let naive_dt =
283 if let Ok(dt) = NaiveDateTime::parse_from_str(timestamp_str, "%Y-%m-%dT%H:%M:%S") {
284 dt
285 } else if let Ok(dt) = NaiveDateTime::parse_from_str(
286 &format!("{}T00:00:00", timestamp_str),
287 "%Y-%m-%dT%H:%M:%S",
288 ) {
289 dt
290 } else {
291 return Err(custom_error(
292 ctx,
293 &format!("invalid timestamp format: {}", timestamp_str),
294 ));
295 };
296
297 let from_dt = from_tz
299 .from_local_datetime(&naive_dt)
300 .single()
301 .ok_or_else(|| custom_error(ctx, "ambiguous or invalid local time"))?;
302
303 let to_dt = from_dt.with_timezone(&to_tz);
305
306 Ok(Value::String(to_dt.format("%Y-%m-%dT%H:%M:%S").to_string()))
308 }
309}
310
311defn!(IsWeekendFn, vec![arg!(number)], None);
313
314impl Function for IsWeekendFn {
315 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
316 self.signature.validate(args, ctx)?;
317
318 let ts = args[0].as_f64().unwrap();
319 let dt = Utc.timestamp_opt(ts as i64, 0);
320
321 match dt {
322 chrono::LocalResult::Single(dt) => {
323 let weekday = dt.weekday();
324 let is_weekend = weekday == Weekday::Sat || weekday == Weekday::Sun;
325 Ok(Value::Bool(is_weekend))
326 }
327 _ => Ok(Value::Null),
328 }
329 }
330}
331
332defn!(IsWeekdayFn, vec![arg!(number)], None);
334
335impl Function for IsWeekdayFn {
336 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
337 self.signature.validate(args, ctx)?;
338
339 let ts = args[0].as_f64().unwrap();
340 let dt = Utc.timestamp_opt(ts as i64, 0);
341
342 match dt {
343 chrono::LocalResult::Single(dt) => {
344 let weekday = dt.weekday();
345 let is_weekday = weekday != Weekday::Sat && weekday != Weekday::Sun;
346 Ok(Value::Bool(is_weekday))
347 }
348 _ => Ok(Value::Null),
349 }
350 }
351}
352
353defn!(
355 BusinessDaysBetweenFn,
356 vec![arg!(number), arg!(number)],
357 None
358);
359
360impl Function for BusinessDaysBetweenFn {
361 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
362 self.signature.validate(args, ctx)?;
363
364 let ts1 = args[0].as_f64().unwrap() as i64;
365 let ts2 = args[1].as_f64().unwrap() as i64;
366
367 let dt1 = match Utc.timestamp_opt(ts1, 0) {
368 chrono::LocalResult::Single(dt) => dt,
369 _ => return Ok(Value::Null),
370 };
371 let dt2 = match Utc.timestamp_opt(ts2, 0) {
372 chrono::LocalResult::Single(dt) => dt,
373 _ => return Ok(Value::Null),
374 };
375
376 let (start, end) = if dt1 <= dt2 {
378 (dt1.date_naive(), dt2.date_naive())
379 } else {
380 (dt2.date_naive(), dt1.date_naive())
381 };
382
383 let mut count = 0i64;
384 let mut current = start;
385
386 while current < end {
387 let weekday = current.weekday();
388 if weekday != Weekday::Sat && weekday != Weekday::Sun {
389 count += 1;
390 }
391 current = current.succ_opt().unwrap_or(current);
392 }
393
394 let result = if ts1 > ts2 { -count } else { count };
396
397 Ok(number_value(result as f64))
398 }
399}
400
401defn!(RelativeTimeFn, vec![arg!(number)], None);
403
404impl Function for RelativeTimeFn {
405 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
406 self.signature.validate(args, ctx)?;
407
408 let ts = args[0].as_f64().unwrap() as i64;
409 let now = Utc::now().timestamp();
410 let diff = ts - now;
411
412 let (abs_diff, is_future) = if diff >= 0 {
413 (diff, true)
414 } else {
415 (-diff, false)
416 };
417
418 let (value, unit_singular, unit_plural) = if abs_diff < 60 {
420 (abs_diff, "second", "seconds")
421 } else if abs_diff < 3600 {
422 (abs_diff / 60, "minute", "minutes")
423 } else if abs_diff < 86400 {
424 (abs_diff / 3600, "hour", "hours")
425 } else if abs_diff < 2592000 {
426 (abs_diff / 86400, "day", "days")
427 } else if abs_diff < 31536000 {
428 (abs_diff / 2592000, "month", "months")
429 } else {
430 (abs_diff / 31536000, "year", "years")
431 };
432
433 let unit = if value == 1 {
434 unit_singular
435 } else {
436 unit_plural
437 };
438 let result = if is_future {
439 format!("in {} {}", value, unit)
440 } else {
441 format!("{} {} ago", value, unit)
442 };
443
444 Ok(Value::String(result))
445 }
446}
447
448defn!(QuarterFn, vec![arg!(number)], None);
450
451impl Function for QuarterFn {
452 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
453 self.signature.validate(args, ctx)?;
454
455 let ts = args[0].as_f64().unwrap();
456 let dt = Utc.timestamp_opt(ts as i64, 0);
457
458 match dt {
459 chrono::LocalResult::Single(dt) => {
460 let month = dt.month();
461 let quarter = ((month - 1) / 3) + 1;
462 Ok(number_value(quarter as f64))
463 }
464 _ => Ok(Value::Null),
465 }
466 }
467}
468
469fn parse_date_value(value: &Value) -> Option<i64> {
472 match value {
473 Value::Number(n) => n.as_f64().map(|f| f as i64),
474 Value::String(s) => {
475 if let Ok(dt) = DateTime::parse_from_rfc3339(s) {
477 return Some(dt.timestamp());
478 }
479 if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S") {
481 return Some(dt.and_utc().timestamp());
482 }
483 if let Ok(dt) =
485 NaiveDateTime::parse_from_str(&format!("{}T00:00:00", s), "%Y-%m-%dT%H:%M:%S")
486 {
487 return Some(dt.and_utc().timestamp());
488 }
489 None
490 }
491 _ => None,
492 }
493}
494
495defn!(IsAfterFn, vec![arg!(any), arg!(any)], None);
497
498impl Function for IsAfterFn {
499 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
500 self.signature.validate(args, ctx)?;
501
502 let ts1 = parse_date_value(&args[0]);
503 let ts2 = parse_date_value(&args[1]);
504
505 match (ts1, ts2) {
506 (Some(t1), Some(t2)) => Ok(Value::Bool(t1 > t2)),
507 _ => Ok(Value::Null),
508 }
509 }
510}
511
512defn!(IsBeforeFn, vec![arg!(any), arg!(any)], None);
514
515impl Function for IsBeforeFn {
516 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
517 self.signature.validate(args, ctx)?;
518
519 let ts1 = parse_date_value(&args[0]);
520 let ts2 = parse_date_value(&args[1]);
521
522 match (ts1, ts2) {
523 (Some(t1), Some(t2)) => Ok(Value::Bool(t1 < t2)),
524 _ => Ok(Value::Null),
525 }
526 }
527}
528
529defn!(IsBetweenFn, vec![arg!(any), arg!(any), arg!(any)], None);
531
532impl Function for IsBetweenFn {
533 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
534 self.signature.validate(args, ctx)?;
535
536 let ts = parse_date_value(&args[0]);
537 let start = parse_date_value(&args[1]);
538 let end = parse_date_value(&args[2]);
539
540 match (ts, start, end) {
541 (Some(t), Some(s), Some(e)) => Ok(Value::Bool(t >= s && t <= e)),
542 _ => Ok(Value::Null),
543 }
544 }
545}
546
547defn!(TimeAgoFn, vec![arg!(any)], None);
549
550impl Function for TimeAgoFn {
551 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
552 self.signature.validate(args, ctx)?;
553
554 let ts = match parse_date_value(&args[0]) {
555 Some(t) => t,
556 None => return Ok(Value::Null),
557 };
558
559 let now = Utc::now().timestamp();
560 let diff = now - ts;
561 let abs_diff = diff.abs();
562
563 let (value, unit_singular, unit_plural) = if abs_diff < 60 {
565 (abs_diff, "second", "seconds")
566 } else if abs_diff < 3600 {
567 (abs_diff / 60, "minute", "minutes")
568 } else if abs_diff < 86400 {
569 (abs_diff / 3600, "hour", "hours")
570 } else if abs_diff < 2592000 {
571 (abs_diff / 86400, "day", "days")
572 } else if abs_diff < 31536000 {
573 (abs_diff / 2592000, "month", "months")
574 } else {
575 (abs_diff / 31536000, "year", "years")
576 };
577
578 let unit = if value == 1 {
579 unit_singular
580 } else {
581 unit_plural
582 };
583
584 let result = if diff < 0 {
585 format!("in {} {}", value, unit)
586 } else {
587 format!("{} {} ago", value, unit)
588 };
589
590 Ok(Value::String(result))
591 }
592}
593
594defn!(FromEpochFn, vec![arg!(number)], None);
599
600impl Function for FromEpochFn {
601 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
602 self.signature.validate(args, ctx)?;
603
604 let epoch = args[0].as_f64().unwrap() as i64;
605
606 match DateTime::from_timestamp(epoch, 0) {
607 Some(dt) => Ok(Value::String(dt.format("%Y-%m-%dT%H:%M:%SZ").to_string())),
608 None => Ok(Value::Null),
609 }
610 }
611}
612
613defn!(FromEpochMsFn, vec![arg!(number)], None);
618
619impl Function for FromEpochMsFn {
620 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
621 self.signature.validate(args, ctx)?;
622
623 let epoch_ms = args[0].as_f64().unwrap() as i64;
624 let seconds = epoch_ms / 1000;
625 let nanos = ((epoch_ms % 1000) * 1_000_000) as u32;
626
627 match DateTime::from_timestamp(seconds, nanos) {
628 Some(dt) => Ok(Value::String(
629 dt.format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string(),
630 )),
631 None => Ok(Value::Null),
632 }
633 }
634}
635
636defn!(ToEpochFn, vec![arg!(any)], None);
641
642impl Function for ToEpochFn {
643 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
644 self.signature.validate(args, ctx)?;
645
646 match parse_date_value(&args[0]) {
647 Some(ts) => Ok(number_value(ts as f64)),
648 None => Ok(Value::Null),
649 }
650 }
651}
652
653defn!(ToEpochMsFn, vec![arg!(any)], None);
658
659impl Function for ToEpochMsFn {
660 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
661 self.signature.validate(args, ctx)?;
662
663 match parse_date_value(&args[0]) {
664 Some(ts) => {
665 let ts_ms = ts * 1000;
666 Ok(number_value(ts_ms as f64))
667 }
668 None => Ok(Value::Null),
669 }
670 }
671}
672
673defn!(DurationSinceFn, vec![arg!(any)], None);
678
679impl Function for DurationSinceFn {
680 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
681 self.signature.validate(args, ctx)?;
682
683 let ts = match parse_date_value(&args[0]) {
684 Some(t) => t,
685 None => return Ok(Value::Null),
686 };
687 let now = Utc::now().timestamp();
688 let diff = now - ts;
689
690 let is_future = diff < 0;
692 let abs_diff = diff.abs();
693
694 let days = abs_diff / 86400;
695 let hours = (abs_diff % 86400) / 3600;
696 let minutes = (abs_diff % 3600) / 60;
697 let seconds = abs_diff % 60;
698
699 let human = if days > 0 {
701 if days == 1 {
702 "1 day".to_string()
703 } else {
704 format!("{} days", days)
705 }
706 } else if hours > 0 {
707 if hours == 1 {
708 "1 hour".to_string()
709 } else {
710 format!("{} hours", hours)
711 }
712 } else if minutes > 0 {
713 if minutes == 1 {
714 "1 minute".to_string()
715 } else {
716 format!("{} minutes", minutes)
717 }
718 } else if seconds == 1 {
719 "1 second".to_string()
720 } else {
721 format!("{} seconds", seconds)
722 };
723
724 let human_with_direction = if is_future {
725 format!("in {}", human)
726 } else {
727 format!("{} ago", human)
728 };
729
730 let mut map = serde_json::Map::new();
732 map.insert(
733 "seconds".to_string(),
734 Value::Number(serde_json::Number::from(abs_diff)),
735 );
736 map.insert(
737 "minutes".to_string(),
738 Value::Number(serde_json::Number::from(abs_diff / 60)),
739 );
740 map.insert(
741 "hours".to_string(),
742 Value::Number(serde_json::Number::from(abs_diff / 3600)),
743 );
744 map.insert(
745 "days".to_string(),
746 Value::Number(serde_json::Number::from(abs_diff / 86400)),
747 );
748 map.insert("is_future".to_string(), Value::Bool(is_future));
749 map.insert("human".to_string(), Value::String(human_with_direction));
750
751 Ok(Value::Object(map))
752 }
753}
754
755defn!(StartOfDayFn, vec![arg!(any)], None);
760
761impl Function for StartOfDayFn {
762 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
763 self.signature.validate(args, ctx)?;
764
765 let ts = match parse_date_value(&args[0]) {
766 Some(t) => t,
767 None => return Ok(Value::Null),
768 };
769 let dt = DateTime::from_timestamp(ts, 0).unwrap();
770 let start = dt.date_naive().and_hms_opt(0, 0, 0).unwrap().and_utc();
771
772 Ok(Value::String(
773 start.format("%Y-%m-%dT%H:%M:%SZ").to_string(),
774 ))
775 }
776}
777
778defn!(EndOfDayFn, vec![arg!(any)], None);
783
784impl Function for EndOfDayFn {
785 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
786 self.signature.validate(args, ctx)?;
787
788 let ts = match parse_date_value(&args[0]) {
789 Some(t) => t,
790 None => return Ok(Value::Null),
791 };
792 let dt = DateTime::from_timestamp(ts, 0).unwrap();
793 let end = dt.date_naive().and_hms_opt(23, 59, 59).unwrap().and_utc();
794
795 Ok(Value::String(end.format("%Y-%m-%dT%H:%M:%SZ").to_string()))
796 }
797}
798
799defn!(StartOfWeekFn, vec![arg!(any)], None);
804
805impl Function for StartOfWeekFn {
806 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
807 self.signature.validate(args, ctx)?;
808
809 let ts = match parse_date_value(&args[0]) {
810 Some(t) => t,
811 None => return Ok(Value::Null),
812 };
813 let dt = DateTime::from_timestamp(ts, 0).unwrap();
814
815 let days_since_monday = dt.weekday().num_days_from_monday();
817 let monday = dt.date_naive() - chrono::Duration::days(days_since_monday as i64);
818 let start = monday.and_hms_opt(0, 0, 0).unwrap().and_utc();
819
820 Ok(Value::String(
821 start.format("%Y-%m-%dT%H:%M:%SZ").to_string(),
822 ))
823 }
824}
825
826defn!(StartOfMonthFn, vec![arg!(any)], None);
831
832impl Function for StartOfMonthFn {
833 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
834 self.signature.validate(args, ctx)?;
835
836 let ts = match parse_date_value(&args[0]) {
837 Some(t) => t,
838 None => return Ok(Value::Null),
839 };
840 let dt = DateTime::from_timestamp(ts, 0).unwrap();
841
842 let start = dt
843 .date_naive()
844 .with_day(1)
845 .unwrap()
846 .and_hms_opt(0, 0, 0)
847 .unwrap()
848 .and_utc();
849
850 Ok(Value::String(
851 start.format("%Y-%m-%dT%H:%M:%SZ").to_string(),
852 ))
853 }
854}
855
856defn!(StartOfYearFn, vec![arg!(any)], None);
861
862impl Function for StartOfYearFn {
863 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
864 self.signature.validate(args, ctx)?;
865
866 let ts = match parse_date_value(&args[0]) {
867 Some(t) => t,
868 None => return Ok(Value::Null),
869 };
870 let dt = DateTime::from_timestamp(ts, 0).unwrap();
871
872 let start = chrono::NaiveDate::from_ymd_opt(dt.year(), 1, 1)
873 .unwrap()
874 .and_hms_opt(0, 0, 0)
875 .unwrap()
876 .and_utc();
877
878 Ok(Value::String(
879 start.format("%Y-%m-%dT%H:%M:%SZ").to_string(),
880 ))
881 }
882}
883
884defn!(IsSameDayFn, vec![arg!(any), arg!(any)], None);
889
890impl Function for IsSameDayFn {
891 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
892 self.signature.validate(args, ctx)?;
893
894 let ts1 = match parse_date_value(&args[0]) {
895 Some(t) => t,
896 None => return Ok(Value::Null),
897 };
898 let ts2 = match parse_date_value(&args[1]) {
899 Some(t) => t,
900 None => return Ok(Value::Null),
901 };
902
903 let dt1 = match DateTime::from_timestamp(ts1, 0) {
904 Some(dt) => dt,
905 None => return Ok(Value::Null),
906 };
907 let dt2 = match DateTime::from_timestamp(ts2, 0) {
908 Some(dt) => dt,
909 None => return Ok(Value::Null),
910 };
911
912 let same_day = dt1.date_naive() == dt2.date_naive();
913
914 Ok(Value::Bool(same_day))
915 }
916}
917
918defn!(ParseDatetimeFn, vec![arg!(string)], None);
923
924impl Function for ParseDatetimeFn {
925 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
926 self.signature.validate(args, ctx)?;
927
928 let input = args[0].as_str().unwrap();
929
930 match dateparser::parse_with_timezone(input, &Utc) {
931 Ok(dt) => {
932 let iso = dt.format("%Y-%m-%dT%H:%M:%SZ").to_string();
933 Ok(Value::String(iso))
934 }
935 Err(_) => Ok(Value::Null),
936 }
937 }
938}
939
940defn!(ParseNaturalDateFn, vec![arg!(string)], None);
945
946impl Function for ParseNaturalDateFn {
947 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
948 self.signature.validate(args, ctx)?;
949
950 let input = args[0].as_str().unwrap();
951
952 match chrono_english::parse_date_string(input, Utc::now(), chrono_english::Dialect::Us) {
953 Ok(dt) => {
954 let iso = dt.format("%Y-%m-%dT%H:%M:%SZ").to_string();
955 Ok(Value::String(iso))
956 }
957 Err(_) => Ok(Value::Null),
958 }
959 }
960}