1use adana_script_core::primitive::{Compiler, NativeFunctionCallResult, Primitive};
2use anyhow::Context;
3use chrono::{offset::Local, Datelike, NaiveDate, NaiveDateTime, Timelike};
4use std::collections::BTreeMap;
5use std::fmt::Write;
6
7pub static DATE_FORMATS: [&str; 8] = [
8 "%d/%m/%Y %H:%M:%S",
9 "%Y-%m-%d %H:%M:%S",
10 "%d-%m-%Y %H:%M:%S",
11 "%Y-%m-%d",
12 "%d-%m-%Y",
13 "%Y-%d-%m",
14 "%m/%d/%Y",
15 "%d/%m/%Y",
16];
17pub static TIME_FORMATS: [&str; 4] = ["%H:%M:%S%.3f%Z", "%H:%M:%S%Z", "%H:%M:%S", "%H:%M"];
18
19fn make_date_time_struct(d: &NaiveDateTime) -> Primitive {
20 let date = d.date();
21 let time = d.time();
22
23 Primitive::Struct(BTreeMap::from([
24 (
25 "timestamp".into(),
26 Primitive::Int(d.timestamp_millis() as i128),
27 ),
28 (
29 "weekDay".into(),
30 Primitive::String(date.weekday().to_string()),
31 ),
32 ("week".into(), Primitive::U8(d.iso_week().week() as u8)),
33 ("day".into(), Primitive::U8(date.day() as u8)),
34 ("month".into(), Primitive::U8(date.month() as u8)),
35 ("year".into(), Primitive::Int(date.year() as i128)),
36 ("hour".into(), Primitive::U8(time.hour() as u8)),
37 ("minute".into(), Primitive::U8(time.minute() as u8)),
38 ("second".into(), Primitive::U8(time.second() as u8)),
39 ("leap_year".into(), Primitive::Bool(date.leap_year())),
40 ]))
41}
42
43#[no_mangle]
44fn from(mut params: Vec<Primitive>, _compiler: Box<Compiler>) -> NativeFunctionCallResult {
45 if params.len() < 3 {
46 return Err(anyhow::anyhow!(
47 "not enough parameters. at least year, month, day must be provided"
48 ));
49 }
50 let get_i32_from_prim = |prim| match prim {
51 Primitive::I8(n) => Ok(n as i32),
52 Primitive::U8(n) => Ok(n as i32),
53 Primitive::Int(n) => Ok(n as i32),
54 _ => Err(anyhow::anyhow!("not an int")),
55 };
56 let year = get_i32_from_prim(params.remove(0))?;
57 let month = get_i32_from_prim(params.remove(0))? as u32;
58 let day = get_i32_from_prim(params.remove(0))? as u32;
59
60 let date = {
61 let date = NaiveDate::from_ymd_opt(year, month, day).context("could not extract date")?;
62
63 if params.len() == 3 {
64 let hour = get_i32_from_prim(params.remove(0))? as u32;
65 let minute = get_i32_from_prim(params.remove(0))? as u32;
66 let second = get_i32_from_prim(params.remove(0))? as u32;
67 date.and_hms_opt(hour, minute, second)
68 } else {
69 date.and_hms_opt(0, 0, 0)
70 }
71 }
72 .context("could not make date")?;
73 Ok(make_date_time_struct(&date))
74}
75
76#[no_mangle]
77fn format(mut params: Vec<Primitive>, _compiler: Box<Compiler>) -> NativeFunctionCallResult {
78 if params.is_empty() {
79 return Err(anyhow::anyhow!(
80 "not enough parameter. at least a timestamp should be provided."
81 ));
82 }
83
84 let Primitive::Int(s) = params.remove(0) else {
85 return Err(anyhow::anyhow!(
86 "first parameter should be the timestamp (int)"
87 ));
88 };
89
90 let date = NaiveDateTime::from_timestamp_millis(s as i64)
91 .context("could not convert timestamp to date")?;
92 if !params.is_empty() {
93 let Primitive::String(ref f) = params.remove(0) else {
94 return Err(anyhow::anyhow!(
95 "second parameter (optional) should be the format as string"
96 ));
97 };
98 let mut res = String::new();
99 write!(res, "{}", date.format(f))?;
100 Ok(Primitive::String(res))
101 } else {
102 let mut res = String::new();
103 write!(res, "{}", date.format(DATE_FORMATS[0]))?;
104 Ok(Primitive::String(res))
105 }
106}
107
108#[no_mangle]
109fn parse(mut params: Vec<Primitive>, _compiler: Box<Compiler>) -> NativeFunctionCallResult {
110 if params.is_empty() {
111 return Err(anyhow::anyhow!(
112 "not enough parameter. at least a string should be provided."
113 ));
114 }
115
116 let Primitive::String(s) = params.remove(0) else {
117 return Err(anyhow::anyhow!(
118 "first parameter should be the date formatted as a string"
119 ));
120 };
121
122 if !params.is_empty() {
123 let Primitive::String(ref f) = params.remove(0) else {
124 return Err(anyhow::anyhow!(
125 "second parameter (optional) should be the format as string"
126 ));
127 };
128 let date = NaiveDateTime::parse_from_str(s.as_str(), f)?;
129 Ok(make_date_time_struct(&date))
130 } else {
131 let mut date = None;
132 for format in DATE_FORMATS {
133 match NaiveDateTime::parse_from_str(s.as_str(), format) {
134 Ok(d) => {
135 date = Some(d);
136 break;
137 }
138 Err(_e) => {}
139 }
140 }
141 if let Some(date) = date {
142 Ok(make_date_time_struct(&date))
143 } else {
144 Err(anyhow::anyhow!("could not determine date format. {s}"))
145 }
146 }
147}
148
149#[no_mangle]
150pub fn now(_params: Vec<Primitive>, _compiler: Box<Compiler>) -> NativeFunctionCallResult {
151 let now = Local::now().naive_local();
152 Ok(make_date_time_struct(&now))
153}
154
155#[no_mangle]
157pub fn api_description(
158 _params: Vec<Primitive>,
159 _compiler: Box<Compiler>,
160) -> NativeFunctionCallResult {
161 Ok(Primitive::Struct(BTreeMap::from([
162 (
163 "from".into(),
164 Primitive::String(
165 r#"from(year, month, day, [hour, min, sec]) -> struct |
166 construct a date struct from year month day"#
167 .into(),
168 ),
169 ),
170 (
171 "format".into(),
172 Primitive::String(
173 "format(timestamp_millis, [format]) -> string | format a timestamp".into(),
174 ),
175 ),
176 (
177 "parse".into(),
178 Primitive::String(
179 r#"parse(date_str, [format]) -> struct |
180 parse a date string. optional format can be provided"#
181 .into(),
182 ),
183 ),
184 (
185 "now".into(),
186 Primitive::String("now() -> struct | return current date struct ".into()),
187 ),
188 ])))
189}
190
191#[cfg(test)]
192mod test {
193 use adana_script_core::primitive::Primitive;
194 use chrono::Local;
195
196 use crate::format;
197
198 #[test]
199 fn check_str() {
200 let now = Local::now().naive_local();
201 let r = format(
202 vec![Primitive::Int(now.timestamp_millis() as i128)],
203 Box::new(|_, _| Ok(Primitive::Unit)),
204 )
205 .unwrap();
206 dbg!(r);
207 }
208}