1#![doc(html_favicon_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo.png")]
3#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9
10use std::{fmt, ops, time::Duration};
11
12use parking_lot::RwLock;
13use zng_app_context::app_local;
14
15#[cfg(not(target_arch = "wasm32"))]
16use std::time::Instant;
17
18#[cfg(target_arch = "wasm32")]
19use web_time::Instant;
20
21pub struct INSTANT;
23impl INSTANT {
24 pub fn now(&self) -> DInstant {
31 if zng_app_context::LocalContext::current_app().is_some() {
32 if let Some(now) = INSTANT_SV.read().now {
33 return now;
34 }
35 }
36 DInstant(self.epoch().elapsed())
37 }
38
39 pub fn epoch(&self) -> Instant {
45 if let Some(t) = *EPOCH.read() {
46 return t;
47 }
48 *EPOCH.write().get_or_insert_with(|| {
49 let mut now = Instant::now();
50 for t in [60 * 60 * 24, 60 * 60, 60 * 30, 60 * 15, 60 * 10, 60] {
52 if let Some(t) = now.checked_sub(Duration::from_secs(t)) {
53 now = t;
54 break;
55 }
56 }
57 now
58 })
59 }
60
61 pub fn mode(&self) -> InstantMode {
67 INSTANT_SV.read().mode
68 }
69}
70
71#[expect(non_camel_case_types)]
73pub struct INSTANT_APP;
74impl INSTANT_APP {
75 pub fn set_mode(&self, mode: InstantMode) {
79 let mut sv = INSTANT_SV.write();
80 sv.mode = mode;
81 if let InstantMode::Now = mode {
82 sv.now = None;
83 }
84 }
85
86 pub fn set_now(&self, now: DInstant) {
94 let mut sv = INSTANT_SV.write();
95 if let InstantMode::Now = sv.mode {
96 panic!("cannot set now with `TimeMode::Now`");
97 }
98 sv.now = Some(now);
99 }
100
101 pub fn advance_now(&self, advance: Duration) {
109 let mut sv = INSTANT_SV.write();
110 if let InstantMode::Manual = sv.mode {
111 *sv.now.get_or_insert_with(|| DInstant(INSTANT.epoch().elapsed())) += advance;
112 } else {
113 panic!("cannot advance now, not `InstantMode::Manual`");
114 }
115 }
116
117 pub fn unset_now(&self) {
119 INSTANT_SV.write().now = None;
120 }
121
122 pub fn custom_now(&self) -> Option<DInstant> {
128 INSTANT_SV.read().now
129 }
130
131 pub fn pause_for_update(&self) -> Option<InstantUpdatePause> {
134 let mut sv = INSTANT_SV.write();
135 match sv.mode {
136 InstantMode::UpdatePaused => {
137 let now = DInstant(INSTANT.epoch().elapsed());
138 sv.now = Some(now);
139 Some(InstantUpdatePause { now })
140 }
141 _ => None,
142 }
143 }
144}
145
146#[must_use = "unset_now on drop"]
150pub struct InstantUpdatePause {
151 now: DInstant,
152}
153impl Drop for InstantUpdatePause {
154 fn drop(&mut self) {
155 let mut sv = INSTANT_SV.write();
156 if sv.now == Some(self.now) {
157 sv.now = None;
158 }
159 }
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
166pub struct DInstant(Duration);
167impl DInstant {
168 pub fn elapsed(self) -> Duration {
170 INSTANT.now().0 - self.0
171 }
172
173 pub fn duration_since(self, earlier: DInstant) -> Duration {
176 self.0 - earlier.0
177 }
178
179 pub fn checked_add(&self, duration: Duration) -> Option<DInstant> {
181 self.0.checked_add(duration).map(Self)
182 }
183
184 pub fn checked_sub(self, duration: Duration) -> Option<DInstant> {
187 self.0.checked_sub(duration).map(Self)
188 }
189
190 pub fn checked_duration_since(&self, earlier: DInstant) -> Option<Duration> {
192 self.0.checked_sub(earlier.0)
193 }
194
195 pub fn saturating_duration_since(&self, earlier: DInstant) -> Duration {
197 self.0.saturating_sub(earlier.0)
198 }
199
200 pub const EPOCH: DInstant = DInstant(Duration::ZERO);
202
203 pub const MAX: DInstant = DInstant(Duration::MAX);
205}
206impl ops::Add<Duration> for DInstant {
207 type Output = Self;
208
209 fn add(self, rhs: Duration) -> Self {
210 Self(self.0.saturating_add(rhs))
211 }
212}
213impl ops::AddAssign<Duration> for DInstant {
214 fn add_assign(&mut self, rhs: Duration) {
215 self.0 = self.0.saturating_add(rhs);
216 }
217}
218impl ops::Sub<Duration> for DInstant {
219 type Output = Self;
220
221 fn sub(self, rhs: Duration) -> Self {
222 Self(self.0.saturating_sub(rhs))
223 }
224}
225impl ops::SubAssign<Duration> for DInstant {
226 fn sub_assign(&mut self, rhs: Duration) {
227 self.0 = self.0.saturating_sub(rhs);
228 }
229}
230impl ops::Sub for DInstant {
231 type Output = Duration;
232
233 fn sub(self, rhs: Self) -> Self::Output {
234 self.0.saturating_sub(rhs.0)
235 }
236}
237impl From<DInstant> for Instant {
238 fn from(t: DInstant) -> Self {
239 INSTANT.epoch() + t.0
240 }
241}
242
243#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
247pub enum InstantMode {
248 UpdatePaused,
251 Now,
253 Manual,
255}
256
257static EPOCH: RwLock<Option<Instant>> = RwLock::new(None);
258
259app_local! {
260 static INSTANT_SV: InstantService = const {
261 InstantService {
262 mode: InstantMode::UpdatePaused,
263 now: None,
264 }
265 };
266}
267
268struct InstantService {
269 mode: InstantMode,
270 now: Option<DInstant>,
271}
272
273#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
294pub struct Deadline(pub DInstant);
295impl Deadline {
296 pub fn timeout(dur: Duration) -> Self {
298 Deadline(INSTANT.now() + dur)
299 }
300
301 pub fn has_elapsed(self) -> bool {
303 self.0 <= INSTANT.now()
304 }
305
306 pub fn time_left(self) -> Option<Duration> {
308 self.0.checked_duration_since(INSTANT.now())
309 }
310
311 pub fn min(self, other: Deadline) -> Deadline {
313 Deadline(self.0.min(other.0))
314 }
315
316 pub fn max(self, other: Deadline) -> Deadline {
318 Deadline(self.0.max(other.0))
319 }
320
321 pub const ELAPSED: Deadline = Deadline(DInstant::EPOCH);
323
324 pub const MAX: Deadline = Deadline(DInstant::MAX);
326}
327impl fmt::Display for Deadline {
328 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
329 let dur = self.0 - INSTANT.now();
330 write!(f, "{dur:?} left")
331 }
332}
333impl fmt::Debug for Deadline {
334 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
335 write!(f, "Deadline({self})")
336 }
337}
338impl From<DInstant> for Deadline {
339 fn from(value: DInstant) -> Self {
340 Deadline(value)
341 }
342}
343impl From<Duration> for Deadline {
344 fn from(value: Duration) -> Self {
345 Deadline::timeout(value)
346 }
347}
348impl ops::Add<Duration> for Deadline {
349 type Output = Self;
350
351 fn add(mut self, rhs: Duration) -> Self {
352 self.0 += rhs;
353 self
354 }
355}
356impl ops::AddAssign<Duration> for Deadline {
357 fn add_assign(&mut self, rhs: Duration) {
358 self.0 += rhs;
359 }
360}
361impl ops::Sub<Duration> for Deadline {
362 type Output = Self;
363
364 fn sub(mut self, rhs: Duration) -> Self {
365 self.0 -= rhs;
366 self
367 }
368}
369impl ops::SubAssign<Duration> for Deadline {
370 fn sub_assign(&mut self, rhs: Duration) {
371 self.0 -= rhs;
372 }
373}