adana_std_date/
lib.rs

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/// Api description
156#[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}