Skip to main content

ember_core/
time.rs

1//! Compact monotonic time utilities.
2//!
3//! Uses a process-local monotonic clock for timestamps that are smaller
4//! than std::time::Instant (8 bytes vs 16 bytes for Option<Instant>).
5
6use std::sync::OnceLock;
7use std::time::Instant;
8
9/// Returns current monotonic time in milliseconds since process start.
10#[inline]
11pub fn now_ms() -> u64 {
12    static START: OnceLock<Instant> = OnceLock::new();
13    let start = START.get_or_init(Instant::now);
14    start.elapsed().as_millis() as u64
15}
16
17/// Sentinel value meaning "no expiry".
18pub const NO_EXPIRY: u64 = 0;
19
20/// Returns true if the given expiry timestamp has passed.
21#[inline]
22pub fn is_expired(expires_at_ms: u64) -> bool {
23    expires_at_ms != NO_EXPIRY && now_ms() >= expires_at_ms
24}
25
26/// Converts a Duration to an absolute expiry timestamp.
27#[inline]
28pub fn expiry_from_duration(ttl: Option<std::time::Duration>) -> u64 {
29    ttl.map(|d| {
30        let ms = d.as_millis().min(u64::MAX as u128) as u64;
31        now_ms().saturating_add(ms)
32    })
33    .unwrap_or(NO_EXPIRY)
34}
35
36/// Returns remaining TTL in seconds, or None if no expiry.
37#[inline]
38pub fn remaining_secs(expires_at_ms: u64) -> Option<u64> {
39    remaining(expires_at_ms, 1000)
40}
41
42/// Returns remaining TTL in milliseconds, or None if no expiry.
43#[inline]
44pub fn remaining_ms(expires_at_ms: u64) -> Option<u64> {
45    remaining(expires_at_ms, 1)
46}
47
48/// Shared implementation for remaining TTL with a unit divisor.
49#[inline]
50fn remaining(expires_at_ms: u64, divisor: u64) -> Option<u64> {
51    if expires_at_ms == NO_EXPIRY {
52        None
53    } else {
54        let now = now_ms();
55        Some(expires_at_ms.saturating_sub(now) / divisor)
56    }
57}