boa_engine/context/
time.rs1use crate::sys::time::Instant;
4
5#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
19pub struct JsInstant {
20 inner: std::time::Duration,
22}
23
24impl JsInstant {
25 #[must_use]
27 pub fn new(secs: u64, nanos: u32) -> Self {
28 let inner = std::time::Duration::new(secs, nanos);
29 Self::new_unchecked(inner)
30 }
31
32 #[must_use]
34 fn new_unchecked(inner: std::time::Duration) -> Self {
35 Self { inner }
36 }
37
38 #[must_use]
44 pub fn millis_since_epoch(&self) -> u64 {
45 self.inner.as_millis() as u64
46 }
47
48 #[must_use]
54 pub fn nanos_since_epoch(&self) -> u128 {
55 self.inner.as_nanos()
56 }
57}
58
59#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
61pub struct JsDuration {
62 inner: std::time::Duration,
63}
64
65impl JsDuration {
66 #[must_use]
68 pub fn from_millis(millis: u64) -> Self {
69 Self {
70 inner: std::time::Duration::from_millis(millis),
71 }
72 }
73
74 #[must_use]
76 pub fn as_millis(&self) -> u64 {
77 self.inner.as_millis() as u64
78 }
79
80 #[must_use]
82 pub fn as_secs(&self) -> u64 {
83 self.inner.as_secs()
84 }
85
86 #[must_use]
88 pub fn as_nanos(&self) -> u128 {
89 self.inner.as_nanos()
90 }
91}
92
93impl From<std::time::Duration> for JsDuration {
94 fn from(duration: std::time::Duration) -> Self {
95 Self { inner: duration }
96 }
97}
98
99impl From<JsDuration> for std::time::Duration {
100 fn from(duration: JsDuration) -> Self {
101 duration.inner
102 }
103}
104
105macro_rules! impl_duration_ops {
106 ($($trait:ident $trait_fn:ident),*) => {
107 $(
108 impl std::ops::$trait for JsDuration {
109 type Output = JsDuration;
110
111 #[inline]
112 fn $trait_fn(self, rhs: JsDuration) -> Self::Output {
113 Self {
114 inner: std::ops::$trait::$trait_fn(self.inner, rhs.inner)
115 }
116 }
117 }
118 impl std::ops::$trait<JsDuration> for JsInstant {
119 type Output = JsInstant;
120
121 #[inline]
122 fn $trait_fn(self, rhs: JsDuration) -> Self::Output {
123 Self {
124 inner: std::ops::$trait::$trait_fn(self.inner, rhs.inner)
125 }
126 }
127 }
128 )*
129 };
130}
131
132impl_duration_ops!(Add add, Sub sub);
133
134impl std::ops::Sub for JsInstant {
135 type Output = JsDuration;
136
137 #[inline]
138 fn sub(self, rhs: JsInstant) -> Self::Output {
139 JsDuration {
140 inner: self.inner.saturating_sub(rhs.inner),
142 }
143 }
144}
145
146pub trait Clock {
148 fn now(&self) -> JsInstant;
153
154 fn system_time_millis(&self) -> i64;
159}
160
161#[derive(Debug, Clone, Copy)]
171pub struct StdClock {
172 base: Instant,
174}
175
176impl Default for StdClock {
177 fn default() -> Self {
178 Self::new()
179 }
180}
181
182impl StdClock {
183 #[must_use]
185 pub fn new() -> Self {
186 Self {
187 base: Instant::now(),
188 }
189 }
190}
191
192impl Clock for StdClock {
193 fn now(&self) -> JsInstant {
194 let elapsed = self.base.elapsed();
195 JsInstant::new_unchecked(elapsed)
196 }
197
198 fn system_time_millis(&self) -> i64 {
199 let now = crate::sys::time::SystemTime::now();
200 let duration = now
201 .duration_since(crate::sys::time::UNIX_EPOCH)
202 .expect("System clock is before Unix epoch");
203 duration.as_millis() as i64
204 }
205}
206
207#[derive(Debug, Clone, Default)]
212pub struct FixedClock(std::cell::RefCell<u64>);
213
214impl FixedClock {
215 #[must_use]
217 pub fn from_millis(millis: u64) -> Self {
218 Self(std::cell::RefCell::new(millis))
219 }
220
221 pub fn forward(&self, millis: u64) {
223 *self.0.borrow_mut() += millis;
224 }
225}
226
227impl Clock for FixedClock {
228 fn now(&self) -> JsInstant {
229 let millis = *self.0.borrow();
230 JsInstant::new_unchecked(std::time::Duration::new(
231 millis / 1000,
232 ((millis % 1000) * 1_000_000) as u32,
233 ))
234 }
235
236 fn system_time_millis(&self) -> i64 {
237 *self.0.borrow() as i64
238 }
239}
240
241#[test]
242fn basic() {
243 let clock = StdClock::new();
244 let now = clock.now();
245 let _millis = now.millis_since_epoch();
247 let _nanos = now.nanos_since_epoch();
248
249 let duration = JsDuration::from_millis(1000);
250 let later = now + duration;
251 assert!(later > now);
252
253 let duration_small = JsDuration::from_millis(100);
255 let later_small = now + duration_small;
256 let earlier = later_small - duration_small;
257 assert_eq!(earlier, now);
258
259 let diff = later - now;
260 assert_eq!(diff.as_millis(), 1000);
261
262 let fixed = FixedClock::from_millis(0);
263 let now2 = fixed.now();
264 assert_eq!(now2.millis_since_epoch(), 0);
265
266 fixed.forward(1000);
267 let now3 = fixed.now();
268 assert_eq!(now3.millis_since_epoch(), 1000);
269 assert!(now3 > now2);
270
271 fixed.forward(u64::MAX - 1000);
273 let now4 = fixed.now();
274 assert_eq!(now4.millis_since_epoch(), u64::MAX);
275 assert!(now4 > now3);
276}
277
278#[test]
279fn monotonic_behavior() {
280 let clock = StdClock::new();
281
282 let t1 = clock.now();
284 std::thread::sleep(std::time::Duration::from_millis(1));
285 let t2 = clock.now();
286 std::thread::sleep(std::time::Duration::from_millis(1));
287 let t3 = clock.now();
288
289 assert!(t2 > t1, "Time must move forward");
291 assert!(t3 > t2, "Time must continue moving forward");
292 assert!(t3 > t1, "Time must be transitive");
293
294 let elapsed = t3 - t1;
296 assert!(elapsed.as_millis() >= 2, "At least 2ms should have elapsed");
297}
298
299#[test]
300fn clock_independence() {
301 let clock1 = StdClock::new();
303 std::thread::sleep(std::time::Duration::from_millis(10));
304 let clock2 = StdClock::new();
305
306 let t1 = clock1.now();
307 let t2 = clock2.now();
308
309 assert!(t1.millis_since_epoch() >= t2.millis_since_epoch());
311}