switchy_time 0.3.0

Switchy Time package
Documentation

Switchy Time

A simple time abstraction library providing unified time access with support for both standard system time and simulated time for testing.

Features

  • Time Abstraction: Unified now() and instant_now() functions that work with different time backends
  • Standard Time: Use system time for production scenarios
  • Simulated Time: Controllable time simulation for testing and development
  • Step Control: Manually advance simulated time in discrete steps
  • Epoch Offset: Configurable time offset for simulation scenarios
  • Thread Local State: Per-thread time simulation state management
  • Chrono Integration: Optional chrono support with datetime_local_now() and datetime_utc_now() functions

Installation

Add this to your Cargo.toml:

[dependencies]
switchy_time = "0.1.4"

Choose your backend/features:

# standard backend only
switchy_time = { version = "0.1.4", default-features = false, features = ["std"] }

# simulator backend only
switchy_time = { version = "0.1.4", default-features = false, features = ["simulator"] }

# simulator backend with chrono support
switchy_time = { version = "0.1.4", default-features = false, features = ["simulator", "chrono"] }

Usage

Basic Time Access

use switchy_time::{now, instant_now};
use std::time::{SystemTime, Instant};

fn main() {
    // Get current time from the active backend
    let current_time: SystemTime = now();
    println!("Current time: {:?}", current_time);

    // Get current instant (monotonic time)
    let current_instant: Instant = instant_now();
    println!("Current instant: {:?}", current_instant);

    // Time behaves like SystemTime::now() when using only the std backend
    let duration_since_epoch = current_time
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap();

    println!("Seconds since epoch: {}", duration_since_epoch.as_secs());
}

Simulated Time (Testing)

#[cfg(feature = "simulator")]
use switchy_time::simulator::{now, instant_now, reset_step, next_step, set_step, current_step};

#[cfg(feature = "simulator")]
fn test_with_simulated_time() {
    // Reset to initial state
    reset_step();

    let time1 = now();
    let instant1 = instant_now();
    println!("Step 0 time: {:?}, instant: {:?}", time1, instant1);

    // Advance time by one step
    next_step();
    let time2 = now();
    let instant2 = instant_now();
    println!("Step 1 time: {:?}, instant: {:?}", time2, instant2);

    // Jump to specific step
    set_step(100);
    let time3 = now();
    println!("Step 100 time: {:?}", time3);

    // Check current step
    println!("Current step: {}", current_step());
}

Time Simulation Configuration

#[cfg(feature = "simulator")]
use switchy_time::simulator::{
    reset_epoch_offset, epoch_offset,
    reset_step_multiplier, step_multiplier
};

#[cfg(feature = "simulator")]
fn configure_simulation() {
    // Reset epoch offset (randomized base time)
    reset_epoch_offset();
    println!("Epoch offset: {}", epoch_offset());

    // Reset step multiplier (time advancement per step)
    reset_step_multiplier();
    println!("Step multiplier: {}", step_multiplier());

    // Environment variables can control these values:
    // SIMULATOR_EPOCH_OFFSET - fixed epoch offset (milliseconds)
    // SIMULATOR_EPOCH_MIN / SIMULATOR_EPOCH_MAX - bounded random epoch offset (inclusive)
    // SIMULATOR_EPOCH_RANGE_PROFILE - preset random epoch range: low | wide | full
    // SIMULATOR_STEP_MULTIPLIER - sets the step multiplier (milliseconds)
}

Real Time in Simulation Mode

#[cfg(feature = "simulator")]
use switchy_time::simulator::{with_real_time, now, instant_now};

#[cfg(feature = "simulator")]
fn use_real_time_temporarily() {
    // In simulator mode, get simulated time
    let simulated_time = now();
    let simulated_instant = instant_now();
    println!("Simulated time: {:?}", simulated_time);

    // Temporarily use real system time
    let real_time = with_real_time(|| {
        (now(), instant_now()) // Returns actual SystemTime::now() and Instant::now()
    });
    println!("Real time: {:?}", real_time);

    // Back to simulated time
    let simulated_again = now();
    println!("Simulated time again: {:?}", simulated_again);
}

Chrono Integration

#[cfg(all(feature = "simulator", feature = "chrono"))]
use switchy_time::simulator::{datetime_local_now, datetime_utc_now, reset_step, set_step};

#[cfg(all(feature = "simulator", feature = "chrono"))]
fn use_chrono_datetime() {
    reset_step();

    // Get current time as chrono DateTime
    let local_time = datetime_local_now();
    let utc_time = datetime_utc_now();

    println!("Local time: {}", local_time);
    println!("UTC time: {}", utc_time);

    // Advance time
    set_step(1000);
    let later_time = datetime_utc_now();
    println!("Later UTC time: {}", later_time);
}

Testing Time-Dependent Code

#[cfg(feature = "simulator")]
use switchy_time::{now, simulator::{reset_step, next_step, set_step}};
use std::time::Duration;

#[cfg(feature = "simulator")]
struct TimestampedEvent {
    timestamp: std::time::SystemTime,
    data: String,
}

#[cfg(feature = "simulator")]
fn test_time_dependent_logic() {
    reset_step();

    let mut events = Vec::new();

    // Create events at different time steps
    for i in 0..5 {
        set_step(i * 1000); // Each step is 1000 multiplier units apart

        events.push(TimestampedEvent {
            timestamp: now(),
            data: format!("Event {}", i),
        });
    }

    // Verify event ordering
    for (i, event) in events.iter().enumerate() {
        println!("Event {}: {} at {:?}", i, event.data, event.timestamp);

        if i > 0 {
            let duration = event.timestamp
                .duration_since(events[i-1].timestamp)
                .unwrap();
            println!("  Time since previous: {:?}", duration);
        }
    }
}

Environment Configuration

The simulator can be configured via environment variables:

# 1) Fixed epoch offset override (highest precedence)
export SIMULATOR_EPOCH_OFFSET=1640995200000

# 2) Or bounded random epoch offset (inclusive)
export SIMULATOR_EPOCH_MIN=946684800000
export SIMULATOR_EPOCH_MAX=2524608000000

# 3) Or preset epoch range profile
# low  = practical range
# wide = broader range
# full = broad/default range
export SIMULATOR_EPOCH_RANGE_PROFILE=wide

# Set step multiplier (milliseconds per step)
export SIMULATOR_STEP_MULTIPLIER=1000

# Run your application
cargo run --features simulator

Epoch offset precedence order:

  1. SIMULATOR_EPOCH_OFFSET
  2. SIMULATOR_EPOCH_MIN + SIMULATOR_EPOCH_MAX
  3. SIMULATOR_EPOCH_RANGE_PROFILE
  4. Default profile (full)

Notes:

  • SIMULATOR_EPOCH_MIN and SIMULATOR_EPOCH_MAX must both be set together.
  • Bounds are inclusive and must satisfy MIN <= MAX.

API Reference

Universal Functions

  • now() - Returns SystemTime from appropriate backend
  • instant_now() - Returns Instant from appropriate backend
  • When simulator is enabled, these top-level functions use the simulator backend; they use the standard backend only when simulator is disabled.

Chrono Functions (with chrono feature)

  • datetime_local_now() - Returns chrono::DateTime<chrono::Local> from appropriate backend
  • datetime_utc_now() - Returns chrono::DateTime<chrono::Utc> from appropriate backend

Standard Backend (std feature)

  • Uses std::time::SystemTime::now() and std::time::Instant::now() directly

Simulator Backend (simulator feature)

  • now() - Returns simulated time based on current step
  • instant_now() - Returns simulated instant based on current step
  • reset_step() - Reset step counter to 0
  • next_step() - Advance to next step and return new step number
  • set_step(step) - Set specific step number
  • current_step() - Get current step number
  • reset_epoch_offset() - Generate new random epoch offset
  • epoch_offset() - Get current epoch offset
  • reset_step_multiplier() - Generate new random step multiplier
  • step_multiplier() - Get current step multiplier
  • with_real_time(f) - Execute function with real system time
  • datetime_local_now() - Returns simulated chrono::DateTime<chrono::Local> (requires chrono feature)
  • datetime_utc_now() - Returns simulated chrono::DateTime<chrono::Utc> (requires chrono feature)

Time Calculation

In simulator mode, time is calculated as:

time = UNIX_EPOCH + Duration::from_millis(epoch_offset + (step * step_multiplier))
  • epoch_offset: Base time offset (fixed override, bounded random, profile random, or default profile random)
  • step: Current step counter (controlled by your code)
  • step_multiplier: Milliseconds per step (randomized or from environment)

Cargo Features

  • std - Enable standard system time backend (default)
  • simulator - Enable time simulation backend (default)
  • chrono - Enable chrono DateTime support

Use Cases

  • Production: Use std feature for normal time operations
  • Testing: Use simulator feature for deterministic time testing
  • Development: Use simulator feature to test time-dependent logic
  • Benchmarking: Control time advancement for consistent measurements

Thread Safety

Each thread maintains its own simulation state (step, epoch offset, step multiplier) using thread-local storage.