From 3b7d3a0e66753602ddb468833865d3bc89ba1eb5 Mon Sep 17 00:00:00 2001 From: Kwabena Boadu Date: Fri, 26 Jan 2024 14:10:14 -0500 Subject: [PATCH] feat: Implement mezmo_add_ts_components and mezmo_set_ts_components functions mezmo_add_ts_components allows clients to add various durations to a given DateTime. It is fallible. mezmo_set_ts_components allows clients to set components of a DateTime such as year, months, etc. It is also fallible. Ref: LOG-19068 --- src/stdlib/mezmo_add_ts_components.rs | 493 ++++++++++++++++++++++++++ src/stdlib/mezmo_set_ts_components.rs | 476 +++++++++++++++++++++++++ src/stdlib/mod.rs | 6 + 3 files changed, 975 insertions(+) create mode 100644 src/stdlib/mezmo_add_ts_components.rs create mode 100644 src/stdlib/mezmo_set_ts_components.rs diff --git a/src/stdlib/mezmo_add_ts_components.rs b/src/stdlib/mezmo_add_ts_components.rs new file mode 100644 index 000000000..5a586464d --- /dev/null +++ b/src/stdlib/mezmo_add_ts_components.rs @@ -0,0 +1,493 @@ +use crate::compiler::prelude::*; +use chrono::{DateTime, Duration, Months, TimeZone, Utc}; + +const MONTHS_IN_A_YEAR: u8 = 12; +const ERROR_INVALID_TIME_COMPONENTS: &str = "Invalid time components"; +const MILLISECONDS_IN_SECOND: u32 = 1000; +const MILLISECONDS_IN_MINUTE: u32 = 60 * MILLISECONDS_IN_SECOND; +const MILLISECONDS_IN_HOUR: u32 = 3600 * MILLISECONDS_IN_SECOND; +const MILLISECONDS_IN_DAY: u32 = 24 * MILLISECONDS_IN_HOUR; + +fn add_ts_components( + ts: Value, + years: Option, + months: Option, + days: Option, + hours: Option, + minutes: Option, + seconds: Option, + milliseconds: Option, +) -> Resolved { + let mut ts: DateTime = ts.try_timestamp()?; + let months = total_months(years, months)?; + let total_ms = total_milliseconds(days, hours, minutes, seconds, milliseconds)?; + + ts = add_months(ts, months)?; + ts = add_milliseconds(ts, total_ms)?; + + Ok(Value::from(ts)) +} + +#[derive(Clone, Copy, Debug)] +pub struct MezmoAddTsComponents; + +impl Function for MezmoAddTsComponents { + fn identifier(&self) -> &'static str { + "mezmo_add_ts_components" + } + + fn parameters(&self) -> &'static [Parameter] { + &[ + Parameter { + keyword: "value", + kind: kind::TIMESTAMP, + required: true, + }, + Parameter { + keyword: "years", + kind: kind::INTEGER, + required: false, + }, + Parameter { + keyword: "months", + kind: kind::INTEGER, + required: false, + }, + Parameter { + keyword: "days", + kind: kind::INTEGER, + required: false, + }, + Parameter { + keyword: "hours", + kind: kind::INTEGER, + required: false, + }, + Parameter { + keyword: "minutes", + kind: kind::INTEGER, + required: false, + }, + Parameter { + keyword: "seconds", + kind: kind::INTEGER, + required: false, + }, + Parameter { + keyword: "milliseconds", + kind: kind::INTEGER, + required: false, + }, + ] + } + + fn examples(&self) -> &'static [Example] { + &[ + Example { + title: "add months", + source: r#"mezmo_add_ts_components(t'2021-02-10T23:32:00+00:00', months: 2)"#, + result: Ok(r#"2021-04-10T23:32:00+00:00"#), + }, + Example { + title: "add months and days", + source: r#"mezmo_add_ts_components(t'2021-02-10T23:32:00+00:00', months: 2, days: 6)"#, + result: Ok(r#"2021-04-1623:32:00+00:00"#), + }, + Example { + title: "add years, months, days, hours, minutes, seconds and milliseconds", + source: r#"mezmo_add_ts_components(t'2021-02-10T23:32:00+00:00', years: 3, months: 2, days: 6, hours: 4, minutes: 10, seconds: 5, milliseconds: 200)"#, + result: Ok(r#"2024-04-17T03:42:05.200+00:00"#), + }, + Example { + title: "subtract years, months and days", + source: r#"mezmo_add_ts_components(t'2021-02-10T23:32:00+00:00', years: -5, months: -2, days: -6)"#, + result: Ok(r#"2015-12-04T23:32:00+00:00"#), + }, + ] + } + + fn compile( + &self, + _state: &state::TypeState, + _ctx: &mut FunctionCompileContext, + arguments: ArgumentList, + ) -> Compiled { + let value = arguments.required("value"); + let years = arguments.optional("years"); + let months = arguments.optional("months"); + let days = arguments.optional("days"); + let hours = arguments.optional("hours"); + let minutes = arguments.optional("minutes"); + let seconds = arguments.optional("seconds"); + let milliseconds = arguments.optional("milliseconds"); + + Ok(MezmoAddTsComponentsFn { + value, + years, + months, + days, + hours, + minutes, + seconds, + milliseconds, + } + .as_expr()) + } +} + +#[derive(Debug, Clone)] +struct MezmoAddTsComponentsFn { + value: Box, + years: Option>, + months: Option>, + days: Option>, + hours: Option>, + minutes: Option>, + seconds: Option>, + milliseconds: Option>, +} + +impl FunctionExpression for MezmoAddTsComponentsFn { + fn resolve(&self, ctx: &mut Context) -> Resolved { + let ts = self.value.resolve(ctx)?; + let years = self + .years + .as_ref() + .map(|expr| expr.resolve(ctx)) + .transpose()?; + let months = self + .months + .as_ref() + .map(|expr| expr.resolve(ctx)) + .transpose()?; + let days = self + .days + .as_ref() + .map(|expr| expr.resolve(ctx)) + .transpose()?; + let hours = self + .hours + .as_ref() + .map(|expr| expr.resolve(ctx)) + .transpose()?; + let minutes = self + .minutes + .as_ref() + .map(|expr| expr.resolve(ctx)) + .transpose()?; + let seconds = self + .seconds + .as_ref() + .map(|expr| expr.resolve(ctx)) + .transpose()?; + let milliseconds = self + .milliseconds + .as_ref() + .map(|expr| expr.resolve(ctx)) + .transpose()?; + + add_ts_components( + ts, + years, + months, + days, + hours, + minutes, + seconds, + milliseconds, + ) + } + + fn type_def(&self, _state: &state::TypeState) -> TypeDef { + TypeDef::bytes().fallible() + } +} + +fn timezone_component_or_default(val: Option) -> Result { + match val { + Some(expr) => expr.try_integer(), + None => Ok(0), + } +} + +fn total_months(years: Option, months: Option) -> Result { + let years = timezone_component_or_default(years)?; + let years = match years.checked_mul(MONTHS_IN_A_YEAR as i64) { + Some(val) => val, + None => { + return Err(ERROR_INVALID_TIME_COMPONENTS.into()); + } + }; + let months = timezone_component_or_default(months)?; + match years.checked_add(months) { + Some(val) => Ok(val), + None => Err(ERROR_INVALID_TIME_COMPONENTS.into()), + } +} + +fn add_months(ts: DateTime, months: i64) -> Result, ExpressionError> { + if months == 0 { + return Ok(ts); + } + let perform_add = months > 0; + let months = match u32::try_from(months.abs()) { + Ok(val) => Months::new(val), + Err(_) => { + return Err(ERROR_INVALID_TIME_COMPONENTS.into()); + } + }; + + if perform_add { + match ts.checked_add_months(months) { + Some(val) => Ok(val), + None => Err(ERROR_INVALID_TIME_COMPONENTS.into()), + } + } else { + match ts.checked_sub_months(months) { + Some(val) => Ok(val), + None => Err(ERROR_INVALID_TIME_COMPONENTS.into()), + } + } +} + +fn total_milliseconds( + days: Option, + hours: Option, + minutes: Option, + seconds: Option, + milliseconds: Option, +) -> Result { + let mut total = 0 as i64; + let values = vec![ + to_milliseconds(timezone_component_or_default(days)?, MILLISECONDS_IN_DAY)?, + to_milliseconds(timezone_component_or_default(hours)?, MILLISECONDS_IN_HOUR)?, + to_milliseconds( + timezone_component_or_default(minutes)?, + MILLISECONDS_IN_MINUTE, + )?, + to_milliseconds( + timezone_component_or_default(seconds)?, + MILLISECONDS_IN_SECOND, + )?, + timezone_component_or_default(milliseconds)?, + ]; + + for val in values.iter() { + total = match total.checked_add(*val) { + Some(new_val) => new_val, + None => { + return Err(ERROR_INVALID_TIME_COMPONENTS.into()); + } + } + } + + Ok(total) +} + +fn to_milliseconds(val: i64, multiplier: u32) -> Result { + match val.checked_mul(multiplier as i64) { + Some(val) => Ok(val), + None => { + return Err(ERROR_INVALID_TIME_COMPONENTS.into()); + } + } +} + +fn add_milliseconds( + ts: DateTime, + milliseconds: i64, +) -> Result, ExpressionError> { + if milliseconds == 0 { + return Ok(ts); + } + let duration = Duration::milliseconds(milliseconds); + match ts.checked_add_signed(duration) { + Some(val) => Ok(val), + None => Err(ERROR_INVALID_TIME_COMPONENTS.into()), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::value; + use chrono::Utc; + + test_function![ + add_ts_components => MezmoAddTsComponents; + + add_nothing { + args: func_args![value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap()], + want: Ok(value!(Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap())), + tdef: TypeDef::bytes().fallible(), + } + + add_years { + args: func_args![ + value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap(), + years: 2 + ], + want: Ok(value!(Utc.with_ymd_and_hms(2023, 02, 10, 23, 32, 0).unwrap())), + tdef: TypeDef::bytes().fallible(), + } + + add_years_and_months { + args: func_args![ + value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap(), + years: 2, + months: 6 + ], + want: Ok(value!(Utc.with_ymd_and_hms(2023, 08, 10, 23, 32, 0).unwrap())), + tdef: TypeDef::bytes().fallible(), + } + + add_years_months_days_hours_minutes_seconds_milliseconds { + args: func_args![ + value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap(), + years: 4, + months: 10, + days: 22, + hours: 3, + minutes: 20, + seconds: 40, + milliseconds: 100, + ], + want: Ok(value!(DateTime::parse_from_rfc3339("2026-01-02T02:52:40.100+00:00").unwrap().with_timezone(&Utc))), + tdef: TypeDef::bytes().fallible(), + } + + add_day_in_leap_year { + args: func_args![ + value: Utc.with_ymd_and_hms(2024, 02, 28, 23, 32, 0).unwrap(), + days: 1 + ], + want: Ok(value!(Utc.with_ymd_and_hms(2024, 02, 29, 23, 32, 0).unwrap())), + tdef: TypeDef::bytes().fallible(), + } + + add_month_in_leap_year { + args: func_args![ + value: Utc.with_ymd_and_hms(2024, 01, 31, 23, 32, 0).unwrap(), + months: 1 + ], + want: Ok(value!(Utc.with_ymd_and_hms(2024, 02, 29, 23, 32, 0).unwrap())), + tdef: TypeDef::bytes().fallible(), + } + + add_day_in_non_leap_year { + args: func_args![ + value: Utc.with_ymd_and_hms(2023, 02, 28, 23, 32, 0).unwrap(), + days: 1 + ], + want: Ok(value!(Utc.with_ymd_and_hms(2023, 03, 01, 23, 32, 0).unwrap())), + tdef: TypeDef::bytes().fallible(), + } + + add_month_in_non_leap_year { + args: func_args![ + value: Utc.with_ymd_and_hms(2023, 01, 31, 23, 32, 0).unwrap(), + months: 1 + ], + want: Ok(value!(Utc.with_ymd_and_hms(2023, 02, 28, 23, 32, 0).unwrap())), + tdef: TypeDef::bytes().fallible(), + } + + add_week_in_leap_year { + args: func_args![ + value: Utc.with_ymd_and_hms(2024, 02, 26, 23, 32, 0).unwrap(), + days: 7 + ], + want: Ok(value!(Utc.with_ymd_and_hms(2024, 03, 04, 23, 32, 0).unwrap())), + tdef: TypeDef::bytes().fallible(), + } + + add_week_in_non_leap_year { + args: func_args![ + value: Utc.with_ymd_and_hms(2023, 02, 27, 23, 32, 0).unwrap(), + days: 7 + ], + want: Ok(value!(Utc.with_ymd_and_hms(2023, 03, 06, 23, 32, 0).unwrap())), + tdef: TypeDef::bytes().fallible(), + } + + add_year_to_leap_year { + args: func_args![ + value: Utc.with_ymd_and_hms(2024, 02, 29, 23, 32, 0).unwrap(), + years: 1 + ], + want: Ok(value!(Utc.with_ymd_and_hms(2025, 02, 28, 23, 32, 0).unwrap())), + tdef: TypeDef::bytes().fallible(), + } + + add_year_to_non_leap_year { + args: func_args![ + value: Utc.with_ymd_and_hms(2023, 02, 28, 23, 32, 0).unwrap(), + years: 1 + ], + want: Ok(value!(Utc.with_ymd_and_hms(2024, 02, 28, 23, 32, 0).unwrap())), + tdef: TypeDef::bytes().fallible(), + } + + subtract_nothing { + args: func_args![ + value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap(), + ], + want: Ok(value!(Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap())), + tdef: TypeDef::bytes().fallible(), + } + + subtract_years { + args: func_args![ + value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap(), + years: -5 + ], + want: Ok(value!(Utc.with_ymd_and_hms(2016, 02, 10, 23, 32, 0).unwrap())), + tdef: TypeDef::bytes().fallible(), + } + + subtract_years_months_days_hours_minutes_seconds_milliseconds { + args: func_args![ + value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap(), + years: -5, + months: -4, + days: -10, + hours: -22, + minutes: -70, + seconds: -10, + milliseconds: -200, + ], + want: Ok(value!(DateTime::parse_from_rfc3339("2015-09-30T00:21:49.800+00:00").unwrap().with_timezone(&Utc))), + tdef: TypeDef::bytes().fallible(), + } + + // errors + error_add_years { + args: func_args![ + value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap(), + years: i64::MAX, + ], + want: Err("Invalid time components"), + tdef: TypeDef::bytes().fallible(), + } + + error_add_months { + args: func_args![ + value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap(), + months: i64::MAX, + ], + want: Err("Invalid time components"), + tdef: TypeDef::bytes().fallible(), + } + + error_subtract_time { + args: func_args![ + value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap(), + days: i64::MIN, + minutes: i64::MIN, + ], + want: Err("Invalid time components"), + tdef: TypeDef::bytes().fallible(), + } + + ]; +} diff --git a/src/stdlib/mezmo_set_ts_components.rs b/src/stdlib/mezmo_set_ts_components.rs new file mode 100644 index 000000000..17eb1801f --- /dev/null +++ b/src/stdlib/mezmo_set_ts_components.rs @@ -0,0 +1,476 @@ +use crate::compiler::prelude::*; +use chrono::{DateTime, Datelike, Timelike, Utc}; +use std::fmt::Display; + +const ONE_MILLISECOND_IN_NANOSECONDS: u32 = 1_000_000; + +fn set_ts_components( + ts: Value, + years: Option, + months: Option, + days: Option, + hours: Option, + minutes: Option, + seconds: Option, + milliseconds: Option, +) -> Resolved { + let mut ts: DateTime = ts.try_timestamp()?; + let years = get_arg_value::("years", years, 0, None)?; + let months = get_arg_value::("months", months, 1, Some(12))?; + let days = get_arg_value::("days", days, 1, Some(31))?; + let hours = get_arg_value::("hours", hours, 0, Some(23))?; + let minutes = get_arg_value::("minutes", minutes, 0, Some(59))?; + let seconds = get_arg_value::("seconds", seconds, 0, Some(59))?; + let milliseconds = get_arg_value::("milliseconds", milliseconds, 0, Some(999))?; + + if let Some(years) = years { + ts = match ts.with_year(years) { + Some(val) => val, + None => { + return Err("Invalid years".into()); + } + }; + } + if let Some(months) = months { + ts = match ts.with_month(months) { + Some(val) => val, + None => { + return Err("Invalid months".into()); + } + } + } + if let Some(days) = days { + ts = match ts.with_day(days) { + Some(val) => val, + None => { + return Err("Invalid days".into()); + } + } + } + if let Some(hours) = hours { + ts = match ts.with_hour(hours) { + Some(val) => val, + None => { + return Err("Invalid hours".into()); + } + } + } + if let Some(minutes) = minutes { + ts = match ts.with_minute(minutes) { + Some(val) => val, + None => { + return Err("Invalid minutes".into()); + } + } + } + if let Some(seconds) = seconds { + ts = match ts.with_second(seconds) { + Some(val) => val, + None => { + return Err("Invalid seconds".into()); + } + } + } + if let Some(milliseconds) = milliseconds { + ts = match ts.with_nanosecond(milliseconds * ONE_MILLISECOND_IN_NANOSECONDS) { + Some(val) => val, + None => { + return Err("Invalid milliseconds".into()); + } + } + } + + Ok(Value::from(ts)) +} + +#[derive(Clone, Copy, Debug)] +pub struct MezmoSetTsComponents; + +impl Function for MezmoSetTsComponents { + fn identifier(&self) -> &'static str { + "mezmo_set_ts_components" + } + + fn parameters(&self) -> &'static [Parameter] { + &[ + Parameter { + keyword: "value", + kind: kind::TIMESTAMP, + required: true, + }, + Parameter { + keyword: "years", + kind: kind::INTEGER, + required: false, + }, + Parameter { + keyword: "months", + kind: kind::INTEGER, + required: false, + }, + Parameter { + keyword: "days", + kind: kind::INTEGER, + required: false, + }, + Parameter { + keyword: "hours", + kind: kind::INTEGER, + required: false, + }, + Parameter { + keyword: "minutes", + kind: kind::INTEGER, + required: false, + }, + Parameter { + keyword: "seconds", + kind: kind::INTEGER, + required: false, + }, + Parameter { + keyword: "milliseconds", + kind: kind::INTEGER, + required: false, + }, + ] + } + + fn examples(&self) -> &'static [Example] { + &[ + Example { + title: "set months", + source: r#"mezmo_set_ts_components(t'2021-02-10T23:32:00+00:00', months: 6)"#, + result: Ok(r#"2021-06-10T23:32:00+00:00"#), + }, + Example { + title: "set months and days", + source: r#"mezmo_set_ts_components(t'2021-02-10T23:32:00+00:00', months: 3, days: 6)"#, + result: Ok(r#"2021-03-0623:32:00+00:00"#), + }, + Example { + title: "set years, months, days, hours, minutes, seconds and milliseconds", + source: r#"mezmo_set_ts_components(t'2021-02-10T23:32:00+00:00', years: 2000, months: 10, days: 4, hours: 4, minutes: 10, seconds: 55, milliseconds: 200)"#, + result: Ok(r#"2000-10-04T04:10:55.200+00:00"#), + }, + ] + } + + fn compile( + &self, + _state: &state::TypeState, + _ctx: &mut FunctionCompileContext, + arguments: ArgumentList, + ) -> Compiled { + let value = arguments.required("value"); + let years = arguments.optional("years"); + let months = arguments.optional("months"); + let days = arguments.optional("days"); + let hours = arguments.optional("hours"); + let minutes = arguments.optional("minutes"); + let seconds = arguments.optional("seconds"); + let milliseconds = arguments.optional("milliseconds"); + + Ok(MezmoSetTsComponentsFn { + value, + years, + months, + days, + hours, + minutes, + seconds, + milliseconds, + } + .as_expr()) + } +} + +#[derive(Debug, Clone)] +struct MezmoSetTsComponentsFn { + value: Box, + years: Option>, + months: Option>, + days: Option>, + hours: Option>, + minutes: Option>, + seconds: Option>, + milliseconds: Option>, +} + +impl FunctionExpression for MezmoSetTsComponentsFn { + fn resolve(&self, ctx: &mut Context) -> Resolved { + let ts = self.value.resolve(ctx)?; + let years = self + .years + .as_ref() + .map(|expr| expr.resolve(ctx)) + .transpose()?; + let months = self + .months + .as_ref() + .map(|expr| expr.resolve(ctx)) + .transpose()?; + let days = self + .days + .as_ref() + .map(|expr| expr.resolve(ctx)) + .transpose()?; + let hours = self + .hours + .as_ref() + .map(|expr| expr.resolve(ctx)) + .transpose()?; + let minutes = self + .minutes + .as_ref() + .map(|expr| expr.resolve(ctx)) + .transpose()?; + let seconds = self + .seconds + .as_ref() + .map(|expr| expr.resolve(ctx)) + .transpose()?; + let milliseconds = self + .milliseconds + .as_ref() + .map(|expr| expr.resolve(ctx)) + .transpose()?; + + set_ts_components( + ts, + years, + months, + days, + hours, + minutes, + seconds, + milliseconds, + ) + } + + fn type_def(&self, _state: &state::TypeState) -> TypeDef { + TypeDef::bytes().fallible() + } +} + +fn get_arg_value( + arg_name: &str, + arg_value: Option, + min_allowed: T, + max_allowed: Option, +) -> Result, ExpressionError> +where + T: Display + TryFrom + PartialOrd + Copy, +{ + let val = match arg_value { + Some(expr) => expr.try_integer()?, + None => { + return Ok(None); + } + }; + let error_message = match max_allowed { + Some(max) => format!("{} must be between {} and {}", arg_name, min_allowed, max), + None => format!( + "{} must be greater than or equal to {}", + arg_name, min_allowed + ), + }; + + let val = match T::try_from(val) { + Ok(new_val) => new_val, + Err(_) => return Err(error_message.into()), + }; + + let is_valid = match max_allowed { + Some(max) => val >= min_allowed && val <= max, + None => val >= min_allowed, + }; + if !is_valid { + return Err(error_message.into()); + } + + Ok(Some(val)) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::value; + use chrono::{DateTime, TimeZone, Utc}; + + test_function![ + set_ts_components => MezmoSetTsComponents; + + set_nothing { + args: func_args![value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap()], + want: Ok(value!(Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap())), + tdef: TypeDef::bytes().fallible(), + } + + set_years { + args: func_args![ + value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap(), + years: 1990, + ], + want: Ok(value!(Utc.with_ymd_and_hms(1990, 02, 10, 23, 32, 0).unwrap())), + tdef: TypeDef::bytes().fallible(), + } + + set_months { + args: func_args![ + value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap(), + months: 10, + ], + want: Ok(value!(Utc.with_ymd_and_hms(2021, 10, 10, 23, 32, 0).unwrap())), + tdef: TypeDef::bytes().fallible(), + } + + set_days { + args: func_args![ + value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap(), + days: 22, + ], + want: Ok(value!(Utc.with_ymd_and_hms(2021, 02, 22, 23, 32, 0).unwrap())), + tdef: TypeDef::bytes().fallible(), + } + + set_hours_minutes_seconds_milliseconds { + args: func_args![ + value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap(), + hours: 09, + minutes: 55, + seconds: 20, + milliseconds: 100, + ], + want: Ok(value!(DateTime::parse_from_rfc3339("2021-02-10T09:55:20.100+00:00").unwrap().with_timezone(&Utc))), + tdef: TypeDef::bytes().fallible(), + } + + error_min_years { + args: func_args![ + value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap(), + years: -200, + ], + want: Err("years must be greater than or equal to 0"), + tdef: TypeDef::bytes().fallible(), + } + + error_min_months { + args: func_args![ + value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap(), + months: 0, + ], + want: Err("months must be between 1 and 12"), + tdef: TypeDef::bytes().fallible(), + } + + error_max_months { + args: func_args![ + value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap(), + months: 13, + ], + want: Err("months must be between 1 and 12"), + tdef: TypeDef::bytes().fallible(), + } + + error_min_days { + args: func_args![ + value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap(), + days: 0, + ], + want: Err("days must be between 1 and 31"), + tdef: TypeDef::bytes().fallible(), + } + + error_max_days { + args: func_args![ + value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap(), + days: 32, + ], + want: Err("days must be between 1 and 31"), + tdef: TypeDef::bytes().fallible(), + } + + error_min_hours { + args: func_args![ + value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap(), + hours: -1, + ], + want: Err("hours must be between 0 and 23"), + tdef: TypeDef::bytes().fallible(), + } + + error_max_hours { + args: func_args![ + value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap(), + hours: 24, + ], + want: Err("hours must be between 0 and 23"), + tdef: TypeDef::bytes().fallible(), + } + + error_min_minutes { + args: func_args![ + value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap(), + minutes: -1, + ], + want: Err("minutes must be between 0 and 59"), + tdef: TypeDef::bytes().fallible(), + } + + error_max_minutes { + args: func_args![ + value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap(), + minutes: 60, + ], + want: Err("minutes must be between 0 and 59"), + tdef: TypeDef::bytes().fallible(), + } + + error_min_seconds { + args: func_args![ + value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap(), + seconds: -1, + ], + want: Err("seconds must be between 0 and 59"), + tdef: TypeDef::bytes().fallible(), + } + + error_max_seconds { + args: func_args![ + value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap(), + seconds: -1, + ], + want: Err("seconds must be between 0 and 59"), + tdef: TypeDef::bytes().fallible(), + } + + error_min_millisecondss { + args: func_args![ + value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap(), + milliseconds: -1, + ], + want: Err("milliseconds must be between 0 and 999"), + tdef: TypeDef::bytes().fallible(), + } + + error_max_millisecondss { + args: func_args![ + value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap(), + milliseconds: 1000, + ], + want: Err("milliseconds must be between 0 and 999"), + tdef: TypeDef::bytes().fallible(), + } + + error_set_days { + args: func_args![ + value: Utc.with_ymd_and_hms(2021, 02, 10, 23, 32, 0).unwrap(), + // no leap years in 2021 + days: 29, + ], + want: Err("Invalid days"), + tdef: TypeDef::bytes().fallible(), + } + ]; +} diff --git a/src/stdlib/mod.rs b/src/stdlib/mod.rs index d5297ddea..558dfd021 100644 --- a/src/stdlib/mod.rs +++ b/src/stdlib/mod.rs @@ -122,6 +122,7 @@ cfg_if::cfg_if! { mod match_datadog_query; mod md5; mod merge; + mod mezmo_add_ts_components; mod mezmo_arithmetic_operation; mod mezmo_char_at; mod mezmo_concat_or_add_fallible; @@ -136,6 +137,7 @@ cfg_if::cfg_if! { mod mezmo_parse_int; mod mezmo_relational_comparison; mod mezmo_repeat; + mod mezmo_set_ts_components; mod mezmo_string_at; mod mezmo_string_slice; mod mezmo_substring; @@ -307,6 +309,7 @@ cfg_if::cfg_if! { pub use match_array::MatchArray; pub use match_datadog_query::MatchDatadogQuery; pub use merge::Merge; + pub use mezmo_add_ts_components::MezmoAddTsComponents; pub use mezmo_arithmetic_operation::*; pub use mezmo_char_at::MezmoCharAt; pub use mezmo_concat_or_add::MezmoConcatOrAdd; @@ -321,6 +324,7 @@ cfg_if::cfg_if! { pub use mezmo_parse_int::MezmoParseInt; pub use mezmo_relational_comparison::*; pub use mezmo_repeat::MezmoRepeat; + pub use mezmo_set_ts_components::MezmoSetTsComponents; pub use mezmo_string_at::MezmoStringAt; pub use mezmo_string_slice::MezmoStringSlice; pub use mezmo_substring::MezmoSubstring; @@ -500,6 +504,7 @@ pub fn all() -> Vec> { Box::new(MatchDatadogQuery), Box::new(Md5), Box::new(Merge), + Box::new(MezmoAddTsComponents), Box::new(MezmoCharAt), Box::new(MezmoConcatOrAdd), Box::new(MezmoConcatOrAddFallible), @@ -518,6 +523,7 @@ pub fn all() -> Vec> { Box::new(MezmoParseFloat), Box::new(MezmoParseInt), Box::new(MezmoRepeat), + Box::new(MezmoSetTsComponents), Box::new(MezmoStringAt), Box::new(MezmoStringSlice), Box::new(MezmoSubstring),