Skip to main content

spo_rhai/packages/
time_basic.rs

1#![cfg(not(feature = "no_time"))]
2
3use super::arithmetic::make_err as make_arithmetic_err;
4use crate::module::ModuleFlags;
5use crate::plugin::*;
6use crate::{def_package, Dynamic, RhaiResult, RhaiResultOf, INT};
7
8#[cfg(not(feature = "no_float"))]
9use crate::FLOAT;
10
11#[cfg(any(not(target_family = "wasm"), not(target_os = "unknown")))]
12use std::time::{Duration, Instant};
13
14#[cfg(all(target_family = "wasm", target_os = "unknown"))]
15use instant::{Duration, Instant};
16
17def_package! {
18    /// Package of basic timing utilities.
19    pub BasicTimePackage(lib) {
20        lib.flags |= ModuleFlags::STANDARD_LIB;
21
22        // Register date/time functions
23        combine_with_exported_module!(lib, "time", time_functions);
24    }
25}
26
27#[export_module]
28mod time_functions {
29    /// Create a timestamp containing the current system time.
30    ///
31    /// # Example
32    ///
33    /// ```rhai
34    /// let now = timestamp();
35    ///
36    /// sleep(10.0);            // sleep for 10 seconds
37    ///
38    /// print(now.elapsed);     // prints 10.???
39    /// ```
40    #[rhai_fn(volatile)]
41    pub fn timestamp() -> Instant {
42        Instant::now()
43    }
44
45    /// Return the number of seconds between the current system time and the timestamp.
46    ///
47    /// # Example
48    ///
49    /// ```rhai
50    /// let now = timestamp();
51    ///
52    /// sleep(10.0);            // sleep for 10 seconds
53    ///
54    /// print(now.elapsed);     // prints 10.???
55    /// ```
56    #[rhai_fn(name = "elapsed", get = "elapsed", return_raw)]
57    pub fn elapsed(timestamp: Instant) -> RhaiResult {
58        #[cfg(not(feature = "no_float"))]
59        if timestamp > Instant::now() {
60            Err(make_arithmetic_err("Time-stamp is later than now"))
61        } else {
62            Ok((timestamp.elapsed().as_secs_f64() as FLOAT).into())
63        }
64
65        #[cfg(feature = "no_float")]
66        {
67            let seconds = timestamp.elapsed().as_secs();
68
69            if cfg!(not(feature = "unchecked")) && seconds > (INT::MAX as u64) {
70                return Err(make_arithmetic_err(format!(
71                    "Integer overflow for timestamp.elapsed: {seconds}"
72                )));
73            }
74            if timestamp > Instant::now() {
75                return Err(make_arithmetic_err("Time-stamp is later than now"));
76            }
77
78            Ok((seconds as INT).into())
79        }
80    }
81
82    /// Return the number of seconds between two timestamps.
83    #[rhai_fn(return_raw, name = "-")]
84    #[allow(clippy::unnecessary_wraps)]
85    pub fn time_diff(timestamp1: Instant, timestamp2: Instant) -> RhaiResult {
86        #[cfg(not(feature = "no_float"))]
87        return Ok(if timestamp2 > timestamp1 {
88            -(timestamp2 - timestamp1).as_secs_f64() as FLOAT
89        } else {
90            (timestamp1 - timestamp2).as_secs_f64() as FLOAT
91        }
92        .into());
93
94        #[cfg(feature = "no_float")]
95        if timestamp2 > timestamp1 {
96            let seconds = (timestamp2 - timestamp1).as_secs();
97
98            if cfg!(not(feature = "unchecked")) && seconds > (INT::MAX as u64) {
99                return Err(make_arithmetic_err(format!(
100                    "Integer overflow for timestamp duration: -{seconds}"
101                )));
102            }
103
104            Ok((-(seconds as INT)).into())
105        } else {
106            let seconds = (timestamp1 - timestamp2).as_secs();
107
108            if cfg!(not(feature = "unchecked")) && seconds > (INT::MAX as u64) {
109                return Err(make_arithmetic_err(format!(
110                    "Integer overflow for timestamp duration: {seconds}"
111                )));
112            }
113
114            Ok((seconds as INT).into())
115        }
116    }
117
118    #[cfg(not(feature = "no_float"))]
119    pub mod float_functions {
120        #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
121        fn add_impl(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf<Instant> {
122            if seconds < 0.0 {
123                return subtract_impl(timestamp, -seconds);
124            }
125            if cfg!(not(feature = "unchecked")) {
126                if seconds > (INT::MAX as FLOAT).min(u64::MAX as FLOAT) {
127                    return Err(make_arithmetic_err(format!(
128                        "Integer overflow for timestamp add: {seconds}"
129                    )));
130                }
131
132                timestamp
133                    .checked_add(Duration::from_millis((seconds * 1000.0) as u64))
134                    .ok_or_else(|| {
135                        make_arithmetic_err(format!(
136                            "Timestamp overflow when adding {seconds} second(s)"
137                        ))
138                    })
139            } else {
140                Ok(timestamp + Duration::from_millis((seconds * 1000.0) as u64))
141            }
142        }
143        #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
144        fn subtract_impl(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf<Instant> {
145            if seconds < 0.0 {
146                return add_impl(timestamp, -seconds);
147            }
148            if cfg!(not(feature = "unchecked")) {
149                if seconds > (INT::MAX as FLOAT).min(u64::MAX as FLOAT) {
150                    return Err(make_arithmetic_err(format!(
151                        "Integer overflow for timestamp subtract: {seconds}"
152                    )));
153                }
154
155                timestamp
156                    .checked_sub(Duration::from_millis((seconds * 1000.0) as u64))
157                    .ok_or_else(|| {
158                        make_arithmetic_err(format!(
159                            "Timestamp overflow when subtracting {seconds} second(s)"
160                        ))
161                    })
162            } else {
163                Ok(timestamp
164                    .checked_sub(Duration::from_millis((seconds * 1000.0) as u64))
165                    .unwrap())
166            }
167        }
168
169        /// Add the specified number of `seconds` to the timestamp and return it as a new timestamp.
170        #[rhai_fn(return_raw, name = "+")]
171        pub fn add(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf<Instant> {
172            add_impl(timestamp, seconds)
173        }
174        /// Add the specified number of `seconds` to the timestamp.
175        #[rhai_fn(return_raw, name = "+=")]
176        pub fn add_assign(timestamp: &mut Instant, seconds: FLOAT) -> RhaiResultOf<()> {
177            *timestamp = add_impl(*timestamp, seconds)?;
178            Ok(())
179        }
180        /// Subtract the specified number of `seconds` from the timestamp and return it as a new timestamp.
181        #[rhai_fn(return_raw, name = "-")]
182        pub fn subtract(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf<Instant> {
183            subtract_impl(timestamp, seconds)
184        }
185        /// Subtract the specified number of `seconds` from the timestamp.
186        #[rhai_fn(return_raw, name = "-=")]
187        pub fn subtract_assign(timestamp: &mut Instant, seconds: FLOAT) -> RhaiResultOf<()> {
188            *timestamp = subtract_impl(*timestamp, seconds)?;
189            Ok(())
190        }
191    }
192
193    #[inline(always)]
194    fn add_inner(timestamp: Instant, seconds: u64) -> Option<Instant> {
195        if cfg!(not(feature = "unchecked")) {
196            timestamp.checked_add(Duration::from_secs(seconds))
197        } else {
198            Some(timestamp + Duration::from_secs(seconds))
199        }
200    }
201    #[inline]
202    fn add_impl(timestamp: Instant, seconds: INT) -> RhaiResultOf<Instant> {
203        if seconds < 0 {
204            subtract_inner(timestamp, seconds.unsigned_abs() as u64)
205        } else {
206            #[allow(clippy::cast_sign_loss)]
207            add_inner(timestamp, seconds as u64)
208        }
209        .ok_or_else(|| {
210            make_arithmetic_err(format!(
211                "Timestamp overflow when adding {seconds} second(s)"
212            ))
213        })
214    }
215    #[inline(always)]
216    fn subtract_inner(timestamp: Instant, seconds: u64) -> Option<Instant> {
217        if cfg!(not(feature = "unchecked")) {
218            timestamp.checked_sub(Duration::from_secs(seconds))
219        } else {
220            Some(timestamp - Duration::from_secs(seconds))
221        }
222    }
223    #[inline]
224    fn subtract_impl(timestamp: Instant, seconds: INT) -> RhaiResultOf<Instant> {
225        if seconds < 0 {
226            add_inner(timestamp, seconds.unsigned_abs() as u64)
227        } else {
228            #[allow(clippy::cast_sign_loss)]
229            subtract_inner(timestamp, seconds as u64)
230        }
231        .ok_or_else(|| {
232            make_arithmetic_err(format!(
233                "Timestamp overflow when subtracting {seconds} second(s)"
234            ))
235        })
236    }
237
238    /// Add the specified number of `seconds` to the timestamp and return it as a new timestamp.
239    #[rhai_fn(return_raw, name = "+")]
240    pub fn add(timestamp: Instant, seconds: INT) -> RhaiResultOf<Instant> {
241        add_impl(timestamp, seconds)
242    }
243    /// Add the specified number of `seconds` to the timestamp.
244    #[rhai_fn(return_raw, name = "+=")]
245    pub fn add_assign(timestamp: &mut Instant, seconds: INT) -> RhaiResultOf<()> {
246        *timestamp = add_impl(*timestamp, seconds)?;
247        Ok(())
248    }
249    /// Subtract the specified number of `seconds` from the timestamp and return it as a new timestamp.
250    #[rhai_fn(return_raw, name = "-")]
251    pub fn subtract(timestamp: Instant, seconds: INT) -> RhaiResultOf<Instant> {
252        subtract_impl(timestamp, seconds)
253    }
254    /// Subtract the specified number of `seconds` from the timestamp.
255    #[rhai_fn(return_raw, name = "-=")]
256    pub fn subtract_assign(timestamp: &mut Instant, seconds: INT) -> RhaiResultOf<()> {
257        *timestamp = subtract_impl(*timestamp, seconds)?;
258        Ok(())
259    }
260
261    /// Return `true` if two timestamps are equal.
262    #[rhai_fn(name = "==")]
263    pub fn eq(timestamp1: Instant, timestamp2: Instant) -> bool {
264        timestamp1 == timestamp2
265    }
266    /// Return `true` if two timestamps are not equal.
267    #[rhai_fn(name = "!=")]
268    pub fn ne(timestamp1: Instant, timestamp2: Instant) -> bool {
269        timestamp1 != timestamp2
270    }
271    /// Return `true` if the first timestamp is earlier than the second.
272    #[rhai_fn(name = "<")]
273    pub fn lt(timestamp1: Instant, timestamp2: Instant) -> bool {
274        timestamp1 < timestamp2
275    }
276    /// Return `true` if the first timestamp is earlier than or equals to the second.
277    #[rhai_fn(name = "<=")]
278    pub fn lte(timestamp1: Instant, timestamp2: Instant) -> bool {
279        timestamp1 <= timestamp2
280    }
281    /// Return `true` if the first timestamp is later than the second.
282    #[rhai_fn(name = ">")]
283    pub fn gt(timestamp1: Instant, timestamp2: Instant) -> bool {
284        timestamp1 > timestamp2
285    }
286    /// Return `true` if the first timestamp is later than or equals to the second.
287    #[rhai_fn(name = ">=")]
288    pub fn gte(timestamp1: Instant, timestamp2: Instant) -> bool {
289        timestamp1 >= timestamp2
290    }
291}