| Crates.io | hourglass-rs |
| lib.rs | hourglass-rs |
| version | 0.1.1 |
| created_at | 2025-08-02 21:19:42.827069+00 |
| updated_at | 2025-08-02 21:50:13.096815+00 |
| description | A time abstraction crate for testing time-dependent code |
| homepage | |
| repository | https://github.com/oeo/hourglass-rs |
| max_upload_size | |
| id | 1779121 |
| size | 96,394 |
A time abstraction crate for Rust that allows you to test time-dependent code by manipulating time in tests while maintaining zero overhead in production.
Add to your Cargo.toml:
[dependencies]
hourglass_rs = "0.1.1"
Write your time-dependent code using SafeTimeProvider:
use hourglass_rs::{SafeTimeProvider, TimeSource};
use chrono::Duration;
async fn daily_task(time_provider: &SafeTimeProvider) {
loop {
println!("Task executed at: {}", time_provider.now());
// Wait for 24 hours
time_provider.wait(Duration::days(1)).await;
}
}
#[tokio::main]
async fn main() {
// In production, use system time
let time = SafeTimeProvider::new(TimeSource::System);
daily_task(&time).await;
}
Test time-dependent code by controlling time:
#[tokio::test]
async fn test_daily_task() {
// Create a test time provider starting at a specific time
let time = SafeTimeProvider::new(
TimeSource::Test("2024-01-01T00:00:00Z".parse().unwrap())
);
// Get time control for the test
let control = time.test_control().expect("Should be in test mode");
// Start the task
let task_handle = tokio::spawn(daily_task(time.clone()));
// Advance time by 3 days instantly
control.advance(Duration::days(3));
// Verify the task executed 3 times
assert_eq!(control.wait_call_count(), 3);
assert_eq!(control.total_waited(), Duration::days(3));
}
Calculate compound interest with testable time:
use hourglass_rs::{SafeTimeProvider, TimeSource};
use chrono::{DateTime, Duration, Utc};
struct InterestCalculator {
time_provider: SafeTimeProvider,
}
impl InterestCalculator {
fn calculate_interest(
&self,
principal: f64,
rate: f64,
last_accrual: DateTime<Utc>,
) -> f64 {
let now = self.time_provider.now();
let days = (now - last_accrual).num_days() as f64;
principal * rate * days / 365.0
}
}
Run scheduled jobs:
async fn run_hourly_job(time: &SafeTimeProvider) {
loop {
process_job().await;
time.wait(Duration::hours(1)).await;
}
}
#[test]
async fn test_hourly_job() {
let time = SafeTimeProvider::new(TimeSource::TestNow);
let control = time.test_control().unwrap();
// Simulate 24 hours instantly
for _ in 0..24 {
control.advance(Duration::hours(1));
}
}
The crate includes several example applications demonstrating different use cases:
# Basic time manipulation
cargo run --example basic_usage
# Async operations with time control
cargo run --example async_wait
# Interest calculation simulation
cargo run --example interest_calc
# Loan accrual simulation (daily interest, monthly cycles)
cargo run --example loan_accrual
# Collateral margin monitoring (CVL ratios, liquidation)
cargo run --example margin_monitoring
# Loan lifecycle edge cases (month-end handling, overdue detection)
cargo run --example loan_lifecycle
All examples use test mode by default to demonstrate time manipulation. To run in production mode, set:
TIME_SOURCE=system cargo run --example basic_usage
TimeSource::System - Uses actual system time (production)TimeSource::Test(start_time) - Test mode starting at specific timeTimeSource::TestNow - Test mode starting at current system timeConfigure time source via environment:
TIME_SOURCE=system (default) or TIME_SOURCE=testTIME_START=2024-01-01T00:00:00Z (RFC3339 format for test mode)The main interface for time operations:
now() - Get current timewait(duration) - Async wait for durationwait_until(deadline) - Async wait until specific timeis_test_mode() - Check if running in test modetest_control() - Get time control (test mode only)Test-only time manipulation (via test_control()):
advance(duration) - Advance time forwardset(time) - Set time to specific valuetotal_waited() - Get total duration waitedwait_call_count() - Get number of wait callsreset_wait_tracking() - Reset wait statisticsSafeTimeProvider to your structs/functionsUtc::now()Contributions accepted via Pull Request.