1#[cfg(not(target_arch = "wasm32"))]
8use std::time::{Duration as StdDuration, Instant as StdInstant};
9#[cfg(not(target_arch = "wasm32"))]
10use tokio::time as tokio_time;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
17pub struct Duration {
18 millis: u64,
19}
20
21impl Duration {
22 pub fn from_secs(secs: u64) -> Self {
37 Self {
38 millis: secs * 1000,
39 }
40 }
41
42 pub fn from_millis(millis: u64) -> Self {
58 Self { millis }
59 }
60
61 pub fn as_secs(&self) -> u64 {
63 self.millis / 1000
64 }
65
66 pub fn as_millis(&self) -> u64 {
68 self.millis
69 }
70
71 pub fn as_secs_f64(&self) -> f64 {
73 self.millis as f64 / 1000.0
74 }
75
76 pub fn is_zero(&self) -> bool {
78 self.millis == 0
79 }
80}
81
82impl Default for Duration {
83 fn default() -> Self {
84 Self::from_secs(0)
85 }
86}
87
88impl From<u64> for Duration {
89 fn from(secs: u64) -> Self {
90 Self::from_secs(secs)
91 }
92}
93
94#[cfg(not(target_arch = "wasm32"))]
99pub async fn sleep(duration: Duration) {
100 if duration.is_zero() {
101 return;
102 }
103 tokio_time::sleep(StdDuration::from_millis(duration.as_millis())).await;
104}
105
106#[cfg(target_arch = "wasm32")]
107pub async fn sleep(duration: Duration) {
108 use wasm_bindgen::prelude::*;
109
110 if duration.is_zero() {
111 return;
112 }
113 let ms = duration.as_millis() as f64;
114 let promise = js_sys::Promise::new(&mut |resolve, _| {
115 let global = js_sys::global();
116 if let Ok(set_timeout) = js_sys::Reflect::get(&global, &JsValue::from_str("setTimeout")) {
117 let set_timeout_fn: js_sys::Function = set_timeout.unchecked_into();
118 let _ = set_timeout_fn.call2(&JsValue::undefined(), &resolve, &JsValue::from_f64(ms));
119 }
120 });
121 let _ = wasm_bindgen_futures::JsFuture::from(promise).await;
122}
123
124#[cfg(not(target_arch = "wasm32"))]
129#[derive(Clone, Copy, Debug)]
130pub struct Instant {
131 inner: StdInstant,
132}
133
134#[cfg(not(target_arch = "wasm32"))]
135impl Instant {
136 pub fn now() -> Self {
137 Self {
138 inner: StdInstant::now(),
139 }
140 }
141
142 pub fn duration_since(&self, earlier: Instant) -> Duration {
143 let std_dur = self.inner.duration_since(earlier.inner);
144 Duration::from_millis(std_dur.as_millis() as u64)
145 }
146
147 pub fn elapsed(&self) -> Duration {
148 let std_dur = self.inner.elapsed();
149 Duration::from_millis(std_dur.as_millis() as u64)
150 }
151}
152
153#[cfg(target_arch = "wasm32")]
154#[derive(Clone, Copy, Debug)]
155pub struct Instant {
156 epoch_millis: f64,
157}
158
159#[cfg(target_arch = "wasm32")]
160impl Instant {
161 pub fn now() -> Self {
162 Self {
163 epoch_millis: js_sys::Date::now(),
164 }
165 }
166
167 pub fn duration_since(&self, earlier: Instant) -> Duration {
168 let diff = (self.epoch_millis - earlier.epoch_millis).max(0.0);
169 Duration::from_millis(diff as u64)
170 }
171
172 pub fn elapsed(&self) -> Duration {
173 let now = js_sys::Date::now();
174 let diff = (now - self.epoch_millis).max(0.0);
175 Duration::from_millis(diff as u64)
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn test_duration_creation() {
185 let duration = Duration::from_secs(30);
186 assert_eq!(duration.as_secs(), 30);
187 assert_eq!(duration.as_millis(), 30000);
188 assert_eq!(duration.as_secs_f64(), 30.0);
189 }
190
191 #[test]
192 fn test_duration_from_millis() {
193 let duration = Duration::from_millis(1500);
194 assert_eq!(duration.as_secs(), 1);
195 assert_eq!(duration.as_millis(), 1500);
196 }
197
198 #[test]
199 fn test_duration_zero() {
200 let duration = Duration::default();
201 assert!(duration.is_zero());
202
203 let non_zero = Duration::from_secs(1);
204 assert!(!non_zero.is_zero());
205 }
206
207 #[test]
208 fn test_duration_ordering() {
209 let dur1 = Duration::from_secs(10);
210 let dur2 = Duration::from_secs(20);
211
212 assert!(dur1 < dur2);
213 assert!(dur2 > dur1);
214 assert_eq!(dur1, dur1);
215 }
216
217 #[test]
218 fn test_duration_from_u64() {
219 let duration: Duration = 42u64.into();
220 assert_eq!(duration.as_secs(), 42);
221 }
222
223 #[test]
224 fn test_instant_elapsed_is_non_negative() {
225 let instant = Instant::now();
226 let elapsed = instant.elapsed();
227 assert!(elapsed.as_millis() < 1000);
228 }
229
230 #[tokio::test]
231 async fn test_instant_duration_since() {
232 let earlier = Instant::now();
233 sleep(Duration::from_millis(50)).await;
234 let later = Instant::now();
235 let diff = later.duration_since(earlier);
236 assert!(diff.as_millis() >= 30);
237 }
238
239 #[tokio::test]
240 async fn test_sleep_functionality() {
241 let duration = Duration::from_secs(0);
242 sleep(duration).await;
243 }
244
245 #[tokio::test]
246 async fn test_sleep_actually_waits() {
247 let before = Instant::now();
248 sleep(Duration::from_millis(100)).await;
249 let elapsed = before.elapsed();
250 assert!(elapsed.as_millis() >= 50);
251 }
252}