1use std::ops::{Add, AddAssign, Sub, SubAssign};
4use std::time::Duration;
5
6use super::js::PERFORMANCE;
7
8#[cfg(target_feature = "atomics")]
9thread_local! {
10 static ORIGIN: f64 = PERFORMANCE.with(super::js::Performance::time_origin);
11}
12
13#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
15pub struct Instant(Duration);
16
17impl Instant {
18 #[must_use]
28 pub fn now() -> Self {
29 let now = PERFORMANCE.with(|performance| {
30 #[cfg(target_feature = "atomics")]
31 return ORIGIN.with(|origin| performance.now() + origin);
32
33 #[cfg(not(target_feature = "atomics"))]
34 performance.now()
35 });
36
37 Self(time_stamp_to_duration(now))
38 }
39
40 #[must_use]
42 pub fn duration_since(&self, earlier: Self) -> Duration {
43 self.checked_duration_since(earlier).unwrap_or_default()
44 }
45
46 #[must_use]
48 #[allow(clippy::missing_const_for_fn)]
49 pub fn checked_duration_since(&self, earlier: Self) -> Option<Duration> {
50 self.0.checked_sub(earlier.0)
51 }
52
53 #[must_use]
55 pub fn saturating_duration_since(&self, earlier: Self) -> Duration {
56 self.checked_duration_since(earlier).unwrap_or_default()
57 }
58
59 #[must_use]
61 pub fn elapsed(&self) -> Duration {
62 Self::now() - *self
63 }
64
65 pub fn checked_add(&self, duration: Duration) -> Option<Self> {
67 self.0.checked_add(duration).map(Instant)
68 }
69
70 pub fn checked_sub(&self, duration: Duration) -> Option<Self> {
72 self.0.checked_sub(duration).map(Instant)
73 }
74}
75
76impl Add<Duration> for Instant {
77 type Output = Self;
78
79 fn add(self, other: Duration) -> Self {
85 self.checked_add(other)
86 .expect("overflow when adding duration to instant")
87 }
88}
89
90impl AddAssign<Duration> for Instant {
91 fn add_assign(&mut self, other: Duration) {
92 *self = *self + other;
93 }
94}
95
96impl Sub<Duration> for Instant {
97 type Output = Self;
98
99 fn sub(self, other: Duration) -> Self {
100 self.checked_sub(other)
101 .expect("overflow when subtracting duration from instant")
102 }
103}
104
105impl Sub<Self> for Instant {
106 type Output = Duration;
107
108 fn sub(self, other: Self) -> Duration {
111 self.duration_since(other)
112 }
113}
114
115impl SubAssign<Duration> for Instant {
116 fn sub_assign(&mut self, other: Duration) {
117 *self = *self - other;
118 }
119}
120
121#[allow(
128 clippy::as_conversions,
129 clippy::cast_possible_truncation,
130 clippy::cast_sign_loss
131)]
132fn time_stamp_to_duration(time_stamp: f64) -> Duration {
133 Duration::from_millis(time_stamp.trunc() as u64)
134 + Duration::from_nanos((time_stamp.fract() * 1.0e6).round() as u64)
135}
136
137#[cfg(test)]
138mod test {
139 use std::time::Duration;
140
141 use rand::distributions::Uniform;
142 use rand::Rng;
143 use wasm_bindgen_test::wasm_bindgen_test;
144
145 wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
146
147 const MAXIMUM_ACCURATE_SECS: u64 = 285_616 * 365 * 24 * 60 * 60;
149 #[allow(clippy::as_conversions, clippy::cast_precision_loss)]
150 const MAXIMUM_ACCURATE_MILLIS: f64 = MAXIMUM_ACCURATE_SECS as f64 * 1_000.;
151
152 #[derive(Debug)]
153 struct ControlDuration(Duration);
154
155 impl ControlDuration {
156 fn new(time_stamp: f64) -> Self {
157 let time_stamp = Duration::from_secs_f64(time_stamp);
159 let secs = time_stamp.as_secs() / 1000;
160 let carry = time_stamp.as_secs() - secs * 1000;
161 #[allow(clippy::as_conversions, clippy::cast_possible_truncation)]
162 let extra_nanos = (carry * 1_000_000_000 / 1000) as u32;
163 let nanos = time_stamp.subsec_micros()
165 + u32::from(time_stamp.subsec_nanos() % 1000 > 499)
166 + extra_nanos;
167 Self(Duration::new(secs, nanos))
170 }
171 }
172
173 impl PartialEq<Duration> for ControlDuration {
174 fn eq(&self, duration: &Duration) -> bool {
175 if self.0 == *duration {
178 true
179 } else if let Some(diff) = self.0.checked_sub(*duration) {
180 diff == Duration::from_nanos(1)
181 } else {
182 false
183 }
184 }
185 }
186
187 #[wasm_bindgen_test]
188 fn sanity() {
189 #[track_caller]
190 fn assert(time_stamp: f64, result: Duration) {
191 let control = ControlDuration::new(time_stamp);
192 let duration = super::time_stamp_to_duration(time_stamp);
193
194 assert_eq!(control, result, "control and expected result are different");
195 assert_eq!(control, duration);
196 }
197
198 assert(0.000_000, Duration::ZERO);
199 assert(0.000_000_4, Duration::ZERO);
200 assert(0.000_000_5, Duration::from_nanos(1));
201 assert(0.000_001, Duration::from_nanos(1));
202 assert(0.000_001_4, Duration::from_nanos(1));
203 assert(0.000_001_5, Duration::from_nanos(2));
204 assert(0.999_999, Duration::from_nanos(999_999));
205 assert(0.999_999_4, Duration::from_nanos(999_999));
206 assert(0.999_999_5, Duration::from_millis(1));
207 assert(1., Duration::from_millis(1));
208 assert(1.000_000_4, Duration::from_millis(1));
209 assert(1.000_000_5, Duration::from_nanos(1_000_001));
210 assert(1.000_001, Duration::from_nanos(1_000_001));
211 assert(1.000_001_4, Duration::from_nanos(1_000_001));
212 assert(1.000_001_5, Duration::from_nanos(1_000_002));
213 assert(999.999_999, Duration::from_nanos(999_999_999));
214 assert(999.999_999_4, Duration::from_nanos(999_999_999));
215 assert(999.999_999_5, Duration::from_secs(1));
216 assert(1000., Duration::from_secs(1));
217 assert(1_000.000_000_4, Duration::from_secs(1));
218 assert(1_000.000_000_5, Duration::from_nanos(1_000_000_001));
219 assert(1_000.000_001, Duration::from_nanos(1_000_000_001));
220 assert(1_000.000_001_4, Duration::from_nanos(1_000_000_001));
221 assert(1_000.000_001_5, Duration::from_nanos(1_000_000_002));
222 assert(
223 MAXIMUM_ACCURATE_MILLIS,
224 Duration::from_secs(MAXIMUM_ACCURATE_SECS),
225 );
226 }
227
228 #[wasm_bindgen_test]
229 fn fuzzing() {
230 let mut random =
231 rand::thread_rng().sample_iter(Uniform::new_inclusive(0., MAXIMUM_ACCURATE_MILLIS));
232
233 for _ in 0..10_000_000 {
234 let time_stamp = random.next().unwrap();
235
236 let control = ControlDuration::new(time_stamp);
237 let duration = super::time_stamp_to_duration(time_stamp);
238
239 assert_eq!(control, duration);
240 }
241 }
242}