rhai/packages/
time_basic.rs

1#![cfg(not(feature = "no_time"))]
2
3use super::arithmetic::make_err as make_arithmetic_err;
4use crate::plugin::*;
5use crate::{def_package, Dynamic, RhaiResult, RhaiResultOf, INT};
6use std::convert::TryFrom;
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 web_time::{Duration, Instant};
16
17def_package! {
18    /// Package of basic timing utilities.
19    pub BasicTimePackage(lib) {
20        lib.set_standard_lib(true);
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            let Ok(seconds) = INT::try_from(seconds) else {
70                return Err(make_arithmetic_err(format!(
71                    "Integer overflow for timestamp.elapsed: {seconds}"
72                )));
73            };
74
75            if timestamp > Instant::now() {
76                return Err(make_arithmetic_err("Time-stamp is later than now"));
77            }
78
79            Ok(seconds.into())
80        }
81    }
82
83    /// Return the number of seconds between two timestamps.
84    #[rhai_fn(return_raw, name = "-")]
85    #[allow(clippy::unnecessary_wraps)]
86    pub fn time_diff(timestamp1: Instant, timestamp2: Instant) -> RhaiResult {
87        #[cfg(not(feature = "no_float"))]
88        return Ok(if timestamp2 > timestamp1 {
89            -(timestamp2 - timestamp1).as_secs_f64() as FLOAT
90        } else {
91            (timestamp1 - timestamp2).as_secs_f64() as FLOAT
92        }
93        .into());
94
95        #[cfg(feature = "no_float")]
96        if timestamp2 > timestamp1 {
97            let seconds = (timestamp2 - timestamp1).as_secs();
98
99            let Ok(seconds) = INT::try_from(seconds) else {
100                return Err(make_arithmetic_err(format!(
101                    "Integer overflow for timestamp.elapsed: {seconds}"
102                )));
103            };
104
105            Ok((-seconds).into())
106        } else {
107            let seconds = (timestamp1 - timestamp2).as_secs();
108
109            let Ok(seconds) = INT::try_from(seconds) else {
110                return Err(make_arithmetic_err(format!(
111                    "Integer overflow for timestamp.elapsed: {seconds}"
112                )));
113            };
114
115            Ok(seconds.into())
116        }
117    }
118
119    #[cfg(not(feature = "no_float"))]
120    pub mod float_functions {
121        #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
122        fn add_impl(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf<Instant> {
123            if seconds < 0.0 {
124                return subtract_impl(timestamp, -seconds);
125            }
126            if cfg!(not(feature = "unchecked")) {
127                #[allow(clippy::cast_precision_loss)]
128                if seconds > (INT::MAX as FLOAT).min(u64::MAX as FLOAT) {
129                    return Err(make_arithmetic_err(format!(
130                        "Integer overflow for timestamp add: {seconds}"
131                    )));
132                }
133
134                timestamp
135                    .checked_add(Duration::from_millis((seconds * 1000.0) as u64))
136                    .ok_or_else(|| {
137                        make_arithmetic_err(format!(
138                            "Timestamp overflow when adding {seconds} second(s)"
139                        ))
140                    })
141            } else {
142                Ok(timestamp + Duration::from_millis((seconds * 1000.0) as u64))
143            }
144        }
145        #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
146        fn subtract_impl(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf<Instant> {
147            if seconds < 0.0 {
148                return add_impl(timestamp, -seconds);
149            }
150            if cfg!(not(feature = "unchecked")) {
151                #[allow(clippy::cast_precision_loss)]
152                if seconds > (INT::MAX as FLOAT).min(u64::MAX as FLOAT) {
153                    return Err(make_arithmetic_err(format!(
154                        "Integer overflow for timestamp subtract: {seconds}"
155                    )));
156                }
157
158                timestamp
159                    .checked_sub(Duration::from_millis((seconds * 1000.0) as u64))
160                    .ok_or_else(|| {
161                        make_arithmetic_err(format!(
162                            "Timestamp overflow when subtracting {seconds} second(s)"
163                        ))
164                    })
165            } else {
166                Ok(timestamp
167                    .checked_sub(Duration::from_millis((seconds * 1000.0) as u64))
168                    .unwrap())
169            }
170        }
171
172        /// Add the specified number of `seconds` to the timestamp and return it as a new timestamp.
173        #[rhai_fn(return_raw, name = "+")]
174        pub fn add(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf<Instant> {
175            add_impl(timestamp, seconds)
176        }
177        /// Add the specified number of `seconds` to the timestamp.
178        #[rhai_fn(return_raw, name = "+=")]
179        pub fn add_assign(timestamp: &mut Instant, seconds: FLOAT) -> RhaiResultOf<()> {
180            *timestamp = add_impl(*timestamp, seconds)?;
181            Ok(())
182        }
183        /// Subtract the specified number of `seconds` from the timestamp and return it as a new timestamp.
184        #[rhai_fn(return_raw, name = "-")]
185        pub fn subtract(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf<Instant> {
186            subtract_impl(timestamp, seconds)
187        }
188        /// Subtract the specified number of `seconds` from the timestamp.
189        #[rhai_fn(return_raw, name = "-=")]
190        pub fn subtract_assign(timestamp: &mut Instant, seconds: FLOAT) -> RhaiResultOf<()> {
191            *timestamp = subtract_impl(*timestamp, seconds)?;
192            Ok(())
193        }
194    }
195
196    #[inline(always)]
197    fn add_inner(timestamp: Instant, seconds: u64) -> Option<Instant> {
198        if cfg!(not(feature = "unchecked")) {
199            timestamp.checked_add(Duration::from_secs(seconds))
200        } else {
201            Some(timestamp + Duration::from_secs(seconds))
202        }
203    }
204    #[inline]
205    fn add_impl(timestamp: Instant, seconds: INT) -> RhaiResultOf<Instant> {
206        if seconds < 0 {
207            #[allow(clippy::useless_conversion)]
208            subtract_inner(timestamp, u64::try_from(seconds.unsigned_abs()).unwrap())
209        } else {
210            add_inner(timestamp, u64::try_from(seconds).unwrap())
211        }
212        .ok_or_else(|| {
213            make_arithmetic_err(format!(
214                "Timestamp overflow when adding {seconds} second(s)"
215            ))
216        })
217    }
218    #[inline(always)]
219    fn subtract_inner(timestamp: Instant, seconds: u64) -> Option<Instant> {
220        #[cfg(not(feature = "unchecked"))]
221        return timestamp.checked_sub(Duration::from_secs(seconds));
222
223        #[cfg(feature = "unchecked")]
224        return Some(timestamp - Duration::from_secs(seconds));
225    }
226    #[inline]
227    fn subtract_impl(timestamp: Instant, seconds: INT) -> RhaiResultOf<Instant> {
228        if seconds < 0 {
229            add_inner(timestamp, u64::try_from(seconds.unsigned_abs()).unwrap())
230        } else {
231            subtract_inner(timestamp, u64::try_from(seconds).unwrap())
232        }
233        .ok_or_else(|| {
234            make_arithmetic_err(format!(
235                "Timestamp overflow when subtracting {seconds} second(s)"
236            ))
237        })
238    }
239
240    /// Add the specified number of `seconds` to the timestamp and return it as a new timestamp.
241    #[rhai_fn(return_raw, name = "+")]
242    pub fn add(timestamp: Instant, seconds: INT) -> RhaiResultOf<Instant> {
243        add_impl(timestamp, seconds)
244    }
245    /// Add the specified number of `seconds` to the timestamp.
246    #[rhai_fn(return_raw, name = "+=")]
247    pub fn add_assign(timestamp: &mut Instant, seconds: INT) -> RhaiResultOf<()> {
248        *timestamp = add_impl(*timestamp, seconds)?;
249        Ok(())
250    }
251    /// Subtract the specified number of `seconds` from the timestamp and return it as a new timestamp.
252    #[rhai_fn(return_raw, name = "-")]
253    pub fn subtract(timestamp: Instant, seconds: INT) -> RhaiResultOf<Instant> {
254        subtract_impl(timestamp, seconds)
255    }
256    /// Subtract the specified number of `seconds` from the timestamp.
257    #[rhai_fn(return_raw, name = "-=")]
258    pub fn subtract_assign(timestamp: &mut Instant, seconds: INT) -> RhaiResultOf<()> {
259        *timestamp = subtract_impl(*timestamp, seconds)?;
260        Ok(())
261    }
262
263    /// Return `true` if two timestamps are equal.
264    #[rhai_fn(name = "==")]
265    pub fn eq(timestamp1: Instant, timestamp2: Instant) -> bool {
266        timestamp1 == timestamp2
267    }
268    /// Return `true` if two timestamps are not equal.
269    #[rhai_fn(name = "!=")]
270    pub fn ne(timestamp1: Instant, timestamp2: Instant) -> bool {
271        timestamp1 != timestamp2
272    }
273    /// Return `true` if the first timestamp is earlier than the second.
274    #[rhai_fn(name = "<")]
275    pub fn lt(timestamp1: Instant, timestamp2: Instant) -> bool {
276        timestamp1 < timestamp2
277    }
278    /// Return `true` if the first timestamp is earlier than or equals to the second.
279    #[rhai_fn(name = "<=")]
280    pub fn lte(timestamp1: Instant, timestamp2: Instant) -> bool {
281        timestamp1 <= timestamp2
282    }
283    /// Return `true` if the first timestamp is later than the second.
284    #[rhai_fn(name = ">")]
285    pub fn gt(timestamp1: Instant, timestamp2: Instant) -> bool {
286        timestamp1 > timestamp2
287    }
288    /// Return `true` if the first timestamp is later than or equals to the second.
289    #[rhai_fn(name = ">=")]
290    pub fn gte(timestamp1: Instant, timestamp2: Instant) -> bool {
291        timestamp1 >= timestamp2
292    }
293}