diff --git a/interpreter/src/context.rs b/interpreter/src/context.rs index ebf7b9d..01034f8 100644 --- a/interpreter/src/context.rs +++ b/interpreter/src/context.rs @@ -185,10 +185,21 @@ impl<'a> Default for Context<'a> { #[cfg(feature = "regex")] ctx.add_function("matches", functions::matches); - #[cfg(feature = "chrono")] - ctx.add_function("duration", functions::duration); - #[cfg(feature = "chrono")] - ctx.add_function("timestamp", functions::timestamp); + + if cfg!(feature = "chrono") { + ctx.add_function("duration", functions::time::duration); + ctx.add_function("timestamp", functions::time::timestamp); + ctx.add_function("getFullYear", functions::time::timestamp_year); + ctx.add_function("getMonth", functions::time::timestamp_month); + ctx.add_function("getDayOfYear", functions::time::timestamp_year_day); + ctx.add_function("getDayOfMonth", functions::time::timestamp_month_day); + ctx.add_function("getDate", functions::time::timestamp_date); + ctx.add_function("getDayOfWeek", functions::time::timestamp_weekday); + ctx.add_function("getHours", functions::time::timestamp_hours); + ctx.add_function("getMinutes", functions::time::timestamp_minutes); + ctx.add_function("getSeconds", functions::time::timestamp_seconds); + ctx.add_function("getMilliseconds", functions::time::timestamp_millis); + } ctx } diff --git a/interpreter/src/functions.rs b/interpreter/src/functions.rs index bd85997..a63cb38 100644 --- a/interpreter/src/functions.rs +++ b/interpreter/src/functions.rs @@ -503,49 +503,119 @@ pub fn exists_one( } } -/// Duration parses the provided argument into a [`Value::Duration`] value. -/// -/// The argument must be string, and must be in the format of a duration. See -/// the [`parse_duration`] documentation for more information on the supported -/// formats. -/// -/// # Examples -/// - `1h` parses as 1 hour -/// - `1.5h` parses as 1 hour and 30 minutes -/// - `1h30m` parses as 1 hour and 30 minutes -/// - `1h30m1s` parses as 1 hour, 30 minutes, and 1 second -/// - `1ms` parses as 1 millisecond -/// - `1.5ms` parses as 1 millisecond and 500 microseconds -/// - `1ns` parses as 1 nanosecond -/// - `1.5ns` parses as 1 nanosecond (sub-nanosecond durations not supported) #[cfg(feature = "chrono")] -pub fn duration(value: Arc) -> Result { - Ok(Value::Duration(_duration(value.as_str())?)) -} +pub mod time { + use super::Result; + use crate::magic::This; + use crate::{ExecutionError, Value}; + use chrono::{Datelike, Days, Months, Timelike}; + use std::sync::Arc; + + /// Duration parses the provided argument into a [`Value::Duration`] value. + /// + /// The argument must be string, and must be in the format of a duration. See + /// the [`parse_duration`] documentation for more information on the supported + /// formats. + /// + /// # Examples + /// - `1h` parses as 1 hour + /// - `1.5h` parses as 1 hour and 30 minutes + /// - `1h30m` parses as 1 hour and 30 minutes + /// - `1h30m1s` parses as 1 hour, 30 minutes, and 1 second + /// - `1ms` parses as 1 millisecond + /// - `1.5ms` parses as 1 millisecond and 500 microseconds + /// - `1ns` parses as 1 nanosecond + /// - `1.5ns` parses as 1 nanosecond (sub-nanosecond durations not supported) + pub fn duration(value: Arc) -> crate::functions::Result { + Ok(Value::Duration(_duration(value.as_str())?)) + } + + /// Timestamp parses the provided argument into a [`Value::Timestamp`] value. + /// The + pub fn timestamp(value: Arc) -> Result { + Ok(Value::Timestamp( + chrono::DateTime::parse_from_rfc3339(value.as_str()) + .map_err(|e| ExecutionError::function_error("timestamp", e.to_string().as_str()))?, + )) + } + + /// A wrapper around [`parse_duration`] that converts errors into [`ExecutionError`]. + /// and only returns the duration, rather than returning the remaining input. + fn _duration(i: &str) -> Result { + let (_, duration) = crate::duration::parse_duration(i) + .map_err(|e| ExecutionError::function_error("duration", e.to_string()))?; + Ok(duration) + } + + fn _timestamp(i: &str) -> Result> { + chrono::DateTime::parse_from_rfc3339(i) + .map_err(|e| ExecutionError::function_error("timestamp", e.to_string())) + } + + pub fn timestamp_year( + This(this): This>, + ) -> Result { + Ok(this.year().into()) + } + + pub fn timestamp_month( + This(this): This>, + ) -> Result { + Ok((this.month0() as i32).into()) + } + + pub fn timestamp_year_day( + This(this): This>, + ) -> Result { + let year = this + .checked_sub_days(Days::new(this.day0() as u64)) + .unwrap() + .checked_sub_months(Months::new(this.month0())) + .unwrap(); + Ok(this.signed_duration_since(year).num_days().into()) + } -/// Timestamp parses the provided argument into a [`Value::Timestamp`] value. -/// The -#[cfg(feature = "chrono")] -pub fn timestamp(value: Arc) -> Result { - Ok(Value::Timestamp( - chrono::DateTime::parse_from_rfc3339(value.as_str()) - .map_err(|e| ExecutionError::function_error("timestamp", e.to_string().as_str()))?, - )) -} + pub fn timestamp_month_day( + This(this): This>, + ) -> Result { + Ok((this.day0() as i32).into()) + } -/// A wrapper around [`parse_duration`] that converts errors into [`ExecutionError`]. -/// and only returns the duration, rather than returning the remaining input. -#[cfg(feature = "chrono")] -fn _duration(i: &str) -> Result { - let (_, duration) = crate::duration::parse_duration(i) - .map_err(|e| ExecutionError::function_error("duration", e.to_string()))?; - Ok(duration) -} + pub fn timestamp_date( + This(this): This>, + ) -> Result { + Ok((this.day() as i32).into()) + } -#[cfg(feature = "chrono")] -fn _timestamp(i: &str) -> Result> { - chrono::DateTime::parse_from_rfc3339(i) - .map_err(|e| ExecutionError::function_error("timestamp", e.to_string())) + pub fn timestamp_weekday( + This(this): This>, + ) -> Result { + Ok((this.weekday().num_days_from_sunday() as i32).into()) + } + + pub fn timestamp_hours( + This(this): This>, + ) -> Result { + Ok((this.hour() as i32).into()) + } + + pub fn timestamp_minutes( + This(this): This>, + ) -> Result { + Ok((this.minute() as i32).into()) + } + + pub fn timestamp_seconds( + This(this): This>, + ) -> Result { + Ok((this.second() as i32).into()) + } + + pub fn timestamp_millis( + This(this): This>, + ) -> Result { + Ok((this.timestamp_subsec_millis() as i32).into()) + } } pub fn max(Arguments(args): Arguments) -> Result { @@ -734,7 +804,49 @@ mod tests { ( "timestamp string", "timestamp('2023-05-28T00:00:00Z').string() == '2023-05-28T00:00:00+00:00'", - )] + ), + ( + "timestamp getFullYear", + "timestamp('2023-05-28T00:00:00Z').getFullYear() == 2023", + ), + ( + "timestamp getMonth", + "timestamp('2023-05-28T00:00:00Z').getMonth() == 4", + ), + ( + "timestamp getDayOfMonth", + "timestamp('2023-05-28T00:00:00Z').getDayOfMonth() == 27", + ), + ( + "timestamp getDayOfYear", + "timestamp('2023-05-28T00:00:00Z').getDayOfYear() == 147", + ), + ( + "timestamp getDate", + "timestamp('2023-05-28T00:00:00Z').getDate() == 28", + ), + ( + "timestamp getDayOfWeek", + "timestamp('2023-05-28T00:00:00Z').getDayOfWeek() == 0", + ), + ( + "timestamp getHours", + "timestamp('2023-05-28T02:00:00Z').getHours() == 2", + ), + ( + "timestamp getMinutes", + " timestamp('2023-05-28T00:05:00Z').getMinutes() == 5", + ), + ( + "timestamp getSeconds", + "timestamp('2023-05-28T00:00:06Z').getSeconds() == 6", + ), + ( + "timestamp getMilliseconds", + "timestamp('2023-05-28T00:00:42.123Z').getMilliseconds() == 123", + ), + + ] .iter() .for_each(assert_script); }