# 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`:
```toml
[dependencies]
switchy_time = "0.1.4"
```
Choose your backend/features:
```toml
# 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
```rust
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)
```rust
#[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
```rust
#[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
```rust
#[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
```rust
#[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
```rust
#[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:
```bash
# 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.