diff --git a/doc/src/language/timestamps.md b/doc/src/language/timestamps.md index 8fef7cdd..0a8a0adc 100644 --- a/doc/src/language/timestamps.md +++ b/doc/src/language/timestamps.md @@ -18,10 +18,12 @@ Built-in Functions The following methods (defined in the [`BasicTimePackage`][packages] but excluded if using a [raw `Engine`]) operate on timestamps: -| Function | Parameter(s) | Description | -| ----------------------------- | ---------------------------------- | -------------------------------------------------------- | -| `elapsed` method and property | _none_ | returns the number of seconds since the timestamp | -| `-` operator | later timestamp, earlier timestamp | returns the number of seconds between the two timestamps | +| Function | Parameter(s) | Description | +| ----------------------------- | ------------------------------------------- | -------------------------------------------------------- | +| `elapsed` method and property | _none_ | returns the number of seconds since the timestamp | +| `-` operator | 1) later timestamp
2) earlier timestamp | returns the number of seconds between the two timestamps | +| `+` operator | number of seconds to add | returns a new timestamp | +| `-` operator | number of seconds to subtract | returns a new timestamp | Examples diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index caa5fc47..b6aabdd6 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -1,23 +1,23 @@ #![cfg(not(feature = "no_std"))] -#[cfg(feature = "no_float")] -use super::{arithmetic::make_err, math_basic::MAX_INT}; +use super::{arithmetic::make_err as make_arithmetic_err, math_basic::MAX_INT}; +use crate::any::Dynamic; use crate::def_package; +use crate::parser::INT; use crate::plugin::*; use crate::result::EvalAltResult; #[cfg(not(feature = "no_float"))] use crate::parser::FLOAT; -#[cfg(feature = "no_float")] -use crate::parser::INT; +use crate::stdlib::boxed::Box; #[cfg(not(target_arch = "wasm32"))] -use crate::stdlib::time::Instant; +use crate::stdlib::time::{Duration, Instant}; #[cfg(target_arch = "wasm32")] -use instant::Instant; +use instant::{Duration, Instant}; def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, { // Register date/time functions @@ -43,7 +43,7 @@ mod time_functions { let seconds = timestamp.elapsed().as_secs(); if cfg!(not(feature = "unchecked")) && seconds > (MAX_INT as u64) { - Err(make_err(format!( + Err(make_arithmetic_err(format!( "Integer overflow for timestamp.elapsed: {}", seconds ))) @@ -70,18 +70,18 @@ mod time_functions { let seconds = (ts2 - ts1).as_secs(); if cfg!(not(feature = "unchecked")) && seconds > (MAX_INT as u64) { - Err(make_err(format!( + Err(make_arithmetic_err(format!( "Integer overflow for timestamp duration: -{}", seconds ))) } else { - Ok(Dynamic::from(-(seconds as INT))) + Ok((-(seconds as INT)).into()) } } else { let seconds = (ts1 - ts2).as_secs(); if cfg!(not(feature = "unchecked")) && seconds > (MAX_INT as u64) { - Err(make_err(format!( + Err(make_arithmetic_err(format!( "Integer overflow for timestamp duration: {}", seconds ))) @@ -91,6 +91,103 @@ mod time_functions { } } + #[cfg(not(feature = "no_float"))] + pub mod float_functions { + #[rhai_fn(return_raw, name = "+")] + pub fn add(x: Instant, seconds: FLOAT) -> Result> { + if seconds < 0.0 { + return subtract(x, -seconds); + } + + if cfg!(not(feature = "unchecked")) { + if seconds > (MAX_INT as FLOAT) { + Err(make_arithmetic_err(format!( + "Integer overflow for timestamp add: {}", + seconds + ))) + } else { + x.checked_add(Duration::from_millis((seconds * 1000.0) as u64)) + .ok_or_else(|| { + make_arithmetic_err(format!( + "Timestamp overflow when adding {} second(s)", + seconds + )) + }) + .map(Into::::into) + } + } else { + Ok((x + Duration::from_millis((seconds * 1000.0) as u64)).into()) + } + } + + #[rhai_fn(return_raw, name = "-")] + pub fn subtract(x: Instant, seconds: FLOAT) -> Result> { + if seconds < 0.0 { + return add(x, -seconds); + } + + if cfg!(not(feature = "unchecked")) { + if seconds > (MAX_INT as FLOAT) { + Err(make_arithmetic_err(format!( + "Integer overflow for timestamp add: {}", + seconds + ))) + } else { + x.checked_sub(Duration::from_millis((seconds * 1000.0) as u64)) + .ok_or_else(|| { + make_arithmetic_err(format!( + "Timestamp overflow when adding {} second(s)", + seconds + )) + }) + .map(Into::::into) + } + } else { + Ok((x - Duration::from_millis((seconds * 1000.0) as u64)).into()) + } + } + } + + #[rhai_fn(return_raw, name = "+")] + pub fn add(x: Instant, seconds: INT) -> Result> { + if seconds < 0 { + return subtract(x, -seconds); + } + + if cfg!(not(feature = "unchecked")) { + x.checked_add(Duration::from_secs(seconds as u64)) + .ok_or_else(|| { + make_arithmetic_err(format!( + "Timestamp overflow when adding {} second(s)", + seconds + )) + }) + .map(Into::::into) + } else { + Ok((x + Duration::from_secs(seconds as u64)).into()) + } + } + + #[rhai_fn(return_raw, name = "-")] + pub fn subtract(x: Instant, seconds: INT) -> Result> { + if seconds < 0 { + return add(x, -seconds); + } + + if cfg!(not(feature = "unchecked")) { + x.checked_sub(Duration::from_secs(seconds as u64)) + .ok_or_else(|| { + make_arithmetic_err(format!( + "Timestamp overflow when adding {} second(s)", + seconds + )) + }) + .map(Into::::into) + } else { + Ok((x - Duration::from_secs(seconds as u64)).into()) + } + } + #[rhai_fn(name = "==")] #[inline(always)] pub fn eq(x: Instant, y: Instant) -> bool { diff --git a/tests/time.rs b/tests/time.rs index ad7fc4c9..54240db8 100644 --- a/tests/time.rs +++ b/tests/time.rs @@ -50,3 +50,60 @@ fn test_timestamp() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_timestamp_op() -> Result<(), Box> { + let engine = Engine::new(); + + #[cfg(not(feature = "no_float"))] + assert!( + (engine.eval::( + r#" + let time1 = timestamp(); + let time2 = time1 + 123.45; + time2 - time1 + "# + )? - 123.45) + .abs() + < 0.001 + ); + + #[cfg(not(feature = "no_float"))] + assert!( + (engine.eval::( + r#" + let time1 = timestamp(); + let time2 = time1 - 123.45; + time1 - time2 + "# + )? - 123.45) + .abs() + < 0.001 + ); + + #[cfg(feature = "no_float")] + assert_eq!( + engine.eval::( + r#" + let time1 = timestamp(); + let time2 = time1 + 42; + time2 - time1 + "# + )?, + 42 + ); + + #[cfg(feature = "no_float")] + assert_eq!( + engine.eval::( + r#" + let time1 = timestamp(); + let time2 = time1 - 42; + time1 - time2 + "# + )?, + 42 + ); + + Ok(()) +}