weavegraph 0.7.0

Graph-driven, concurrent agent workflow framework with versioned state, deterministic barrier merges, and rich diagnostics.
Documentation
//! Injectable clock abstraction for deterministic testing and time-based operations.

use chrono::{DateTime, Utc};
use std::time::{Duration, SystemTime, UNIX_EPOCH};

/// A mockable time source for dependency injection.
///
/// Implementors must provide `now()`, `now_datetime()`, and `now_system_time()`.
/// All other methods are derived from those three.
pub trait Clock: Send + Sync + std::fmt::Debug {
    /// Current time as seconds since the Unix epoch.
    fn now(&self) -> u64;

    /// Current time as milliseconds since the Unix epoch.
    fn now_unix_ms(&self) -> i64 {
        self.now_datetime().timestamp_millis()
    }

    /// Current time as a `DateTime<Utc>`.
    fn now_datetime(&self) -> DateTime<Utc>;

    /// Current time as a `SystemTime` for interop with the standard library.
    fn now_system_time(&self) -> SystemTime;

    /// Returns `true` if at least `duration` has elapsed since `since`.
    fn has_elapsed(&self, since: u64, duration: Duration) -> bool {
        self.now().saturating_sub(since) >= duration.as_secs()
    }

    /// Elapsed time since `timestamp`, saturating at zero.
    fn duration_since(&self, timestamp: u64) -> Duration {
        Duration::from_secs(self.now().saturating_sub(timestamp))
    }
}

/// Production clock backed by the operating system.
///
/// ```rust
/// use weavegraph::utils::clock::{Clock, SystemClock};
///
/// assert!(SystemClock.now() > 0);
/// ```
#[derive(Debug, Clone, Copy, Default)]
pub struct SystemClock;

impl Clock for SystemClock {
    fn now(&self) -> u64 {
        Utc::now().timestamp() as u64
    }

    fn now_datetime(&self) -> DateTime<Utc> {
        Utc::now()
    }

    fn now_system_time(&self) -> SystemTime {
        SystemTime::now()
    }
}

/// Manually-driven clock for deterministic tests.
///
/// ```rust
/// use weavegraph::utils::clock::{Clock, MockClock};
/// use std::time::Duration;
///
/// let mut clock = MockClock::new(1000);
/// clock.advance(Duration::from_secs(30));
/// assert_eq!(clock.now(), 1030);
/// assert!(clock.has_elapsed(1000, Duration::from_secs(25)));
/// ```
#[derive(Debug, Clone)]
pub struct MockClock {
    current_time: u64,
}

impl MockClock {
    /// Creates a clock pinned to `start_time` (seconds since Unix epoch).
    #[must_use]
    pub fn new(start_time: u64) -> Self {
        Self {
            current_time: start_time,
        }
    }

    /// Creates a clock pinned to the current system time.
    #[must_use]
    pub fn now() -> Self {
        Self::new(SystemClock.now())
    }

    /// Advances the clock by `duration`.
    ///
    /// ```rust
    /// use weavegraph::utils::clock::{Clock, MockClock};
    /// use std::time::Duration;
    ///
    /// let mut clock = MockClock::new(0);
    /// clock.advance(Duration::from_secs(60));
    /// assert_eq!(clock.now(), 60);
    /// ```
    pub fn advance(&mut self, duration: Duration) {
        self.current_time += duration.as_secs();
    }

    /// Advances the clock by `seconds`.
    pub fn advance_secs(&mut self, seconds: u64) {
        self.current_time += seconds;
    }

    /// Pins the clock to `timestamp`.
    pub fn set_time(&mut self, timestamp: u64) {
        self.current_time = timestamp;
    }

    /// Resets the clock to the Unix epoch.
    pub fn reset(&mut self) {
        self.current_time = 0;
    }
}

impl Clock for MockClock {
    fn now(&self) -> u64 {
        self.current_time
    }

    fn now_datetime(&self) -> DateTime<Utc> {
        DateTime::from_timestamp(self.current_time as i64, 0)
            .unwrap_or_else(|| DateTime::from_timestamp(0, 0).expect("epoch is valid"))
    }

    fn now_system_time(&self) -> SystemTime {
        UNIX_EPOCH + Duration::from_secs(self.current_time)
    }
}

/// Returns a boxed [`SystemClock`] when `use_mock` is `false`, otherwise a
/// [`MockClock`] starting at `mock_start_time`.
#[must_use]
pub fn create_clock(use_mock: bool, mock_start_time: u64) -> Box<dyn Clock> {
    if use_mock {
        Box::new(MockClock::new(mock_start_time))
    } else {
        Box::new(SystemClock)
    }
}

/// Utility functions for common timestamp operations.
pub mod time_utils {
    use super::*;

    /// Formats `timestamp` as `"YYYY-MM-DD HH:MM:SS UTC"`.
    ///
    /// Returns `"invalid-timestamp-<N>"` for out-of-range values.
    ///
    /// ```rust
    /// use weavegraph::utils::clock::time_utils::format_timestamp;
    ///
    /// assert!(format_timestamp(1640995200).contains("2022"));
    /// ```
    #[must_use]
    pub fn format_timestamp(timestamp: u64) -> String {
        DateTime::from_timestamp(timestamp as i64, 0)
            .map(|dt| dt.format("%Y-%m-%d %H:%M:%S UTC").to_string())
            .unwrap_or_else(|| format!("invalid-timestamp-{timestamp}"))
    }

    /// Duration between `earlier` and `later`, saturating at zero.
    #[must_use]
    pub fn duration_between(earlier: u64, later: u64) -> Duration {
        Duration::from_secs(later.saturating_sub(earlier))
    }

    /// Returns `true` if `timestamp` is within `max_age` of the current time.
    pub fn is_recent(timestamp: u64, max_age: Duration, clock: &dyn Clock) -> bool {
        clock.duration_since(timestamp) <= max_age
    }
}