use std::time::Duration;
pub fn epoch(now: Duration, window: Duration) -> u64 {
now.as_secs() / window.as_secs().max(1)
}
pub fn elapsed_in_window(now: Duration, window: Duration) -> Duration {
let w = window.as_secs().max(1);
Duration::from_secs(now.as_secs() % w) + Duration::from_nanos(u64::from(now.subsec_nanos()))
}
pub fn sliding_estimate(cur: u64, prev: u64, elapsed: Duration, window: Duration) -> f64 {
let w = window.as_secs_f64().max(f64::MIN_POSITIVE);
let frac = (elapsed.as_secs_f64() / w).clamp(0.0, 1.0);
cur as f64 + prev as f64 * (1.0 - frac)
}
#[cfg(test)]
mod tests {
use super::*;
const W: Duration = Duration::from_secs(60);
#[test]
fn epoch_advances_once_per_window() {
assert_eq!(epoch(Duration::from_secs(0), W), 0);
assert_eq!(epoch(Duration::from_secs(59), W), 0);
assert_eq!(epoch(Duration::from_secs(60), W), 1);
assert_eq!(epoch(Duration::from_secs(125), W), 2);
}
#[test]
fn estimate_at_window_start_counts_full_previous() {
assert_eq!(sliding_estimate(0, 100, Duration::ZERO, W), 100.0);
}
#[test]
fn estimate_at_window_end_drops_previous() {
let est = sliding_estimate(10, 100, W, W);
assert!((est - 10.0).abs() < 1e-9);
}
#[test]
fn estimate_midway_halves_previous() {
let est = sliding_estimate(10, 100, Duration::from_secs(30), W);
assert!((est - 60.0).abs() < 1e-9); }
}