Lock-free, thread-safe atomic versions of Duration, SystemTime, Instant and their Option variants
Rust
11
24 commits
updated Oct 7, 2026
Thread-safe atomic versions of Duration, SystemTime, Instant and their Option variants; lock-free on platforms with native AtomicU128 support, otherwise portable-atomic may fall back to a global lock
English | 简体中文
atomic-time provides thread-safe atomic versions of Rust's standard time types. They are lock-free on platforms with native AtomicU128 support; otherwise, portable-atomic may fall back to a global lock. All types use AtomicU128 (via portable-atomic) under the hood and expose the same API patterns as the standard std::sync::atomic types (load, store, swap, compare_exchange, compare_exchange_weak, fetch_update).
| Type | Wraps | no_std | arbitrary | quickcheck / proptest |
|---|---|---|---|---|
AtomicDuration | Duration | Yes | With std | With std |
AtomicOptionDuration | Option<Duration> | Yes | With std | With std |
AtomicSystemTime | SystemTime | No | With std | With std |
AtomicOptionSystemTime | Option<SystemTime> | No | With std | With std |
AtomicInstant | Instant | No | No | No |
AtomicOptionInstant | Option<Instant> | No | No | No |
[dependencies]
atomic-time = "1"
| Feature | Default | Description |
|---|---|---|
std | Yes | Enables SystemTime and Instant types |
serde | No | Enables Serialize/Deserialize for all types, including no_std builds |
arbitrary | No | Enables arbitrary for duration and system-time types; requires std |
quickcheck | No | Enables quickcheck for duration and system-time types, and their snapshot Clone implementations |
proptest | No | Enables proptest strategies for duration and system-time types |
For no_std environments (only AtomicDuration and AtomicOptionDuration are available):
[dependencies]
atomic-time = { version = "1", default-features = false }
The optional serde feature can also be enabled in a no_std build with features = ["serde"].
All three property-testing integrations require std. This includes
arbitrary, because the current upstream arbitrary v1 crate itself uses
std. Opt into one of the integrations with:
[dependencies]
atomic-time = { version = "1", features = ["arbitrary"] }
# or: atomic-time = { version = "1", features = ["quickcheck"] }
# or: atomic-time = { version = "1", features = ["proptest"] }
The quickcheck feature implements Clone for AtomicDuration,
AtomicOptionDuration, AtomicSystemTime, and AtomicOptionSystemTime.
Cloning performs a SeqCst load, so under concurrent writes the cloned value
is the value observed at that load's atomic linearization point.
AtomicInstant and AtomicOptionInstant intentionally do not implement
arbitrary, quickcheck, or proptest traits (and do not gain Clone from
these features). Their encodings use a process-local baseline, so a seed or
corpus cannot produce portable, deterministic values across process runs.
On some bare-metal targets without native compare-and-swap support, the final application must use Cargo feature unification to enable portable-atomic's critical-section feature and provide a target-appropriate critical-section implementation, or adopt another safe configuration from the official portable-atomic guide.
All six atomic types provide is_always_lock_free, try_update, update,
fetch_min, and fetch_max; the existing fetch_update remains available for
compatibility. fetch_min and fetch_max return the value observed before the
operation. For Option types, ordering is None < Some; Instant
comparisons are meaningful only for values using the same process-local
baseline.
AtomicDuration and AtomicOptionDuration also provide
fetch_saturating_add and fetch_saturating_sub, returning the old value. For
AtomicDuration, these saturate at Duration::MAX and Duration::ZERO. For
AtomicOptionDuration, Some values saturate while None remains None (it
is not treated as zero). These names intentionally use
fetch_saturating_add/sub, rather than fetch_add/sub, to avoid implying
integer wrapping; saturating add/sub helpers apply only to the Duration types.
AtomicSystemTime and AtomicOptionSystemTime accept only values at or after SystemTime::UNIX_EPOCH; earlier values panic.
AtomicInstant and AtomicOptionInstant encode Instant relative to a process-local baseline initialized from SystemTime::now() and Instant::now(). Within the same process, values within the platform's representable Instant range round-trip precisely. Across processes or restarts, the encoding is not portable and decoding only approximates wall-clock time. System clock adjustments can change that cross-process or restart interpretation. Behavior across sleep follows the platform's Instant semantics. Encoded instants must not be persisted as deadlines. If an extreme Duration cannot be represented by the platform's Instant, decoding falls back to the process baseline instead of panicking.
use std::sync::Arc;
use std::sync::atomic::Ordering;
use std::time::Duration;
use atomic_time::AtomicDuration;
let timeout = Arc::new(AtomicDuration::new(Duration::from_secs(30)));
// Update from another thread
let timeout_clone = timeout.clone();
std::thread::spawn(move || {
timeout_clone.store(Duration::from_secs(60), Ordering::Release);
});
use std::sync::atomic::Ordering;
use std::time::Instant;
use atomic_time::AtomicOptionInstant;
// Track the last time an event occurred
let last_event = AtomicOptionInstant::none();
assert_eq!(last_event.load(Ordering::Relaxed), None);
last_event.store(Some(Instant::now()), Ordering::Release);
assert!(last_event.load(Ordering::Acquire).is_some());
Run with cargo bench in the benchmark/ directory. Apple M4 Pro.
cargo bench --bench duration)| Implementation | Single-thread load | Single-thread store | Contended read | Load under write contention | Contended store |
|---|---|---|---|---|---|
AtomicDuration | 1.04 ns | 0.72 ns | 1.08 ns | 4.44 ns | 10.7 ns |
AtomicOptionDuration | 1.16 ns | 0.74 ns | 1.20 ns | 4.73 ns | 14.5 ns |
ArcSwap<Duration> | 2.30 ns | 86.5 ns | 2.38 ns | 15.5 ns | 1,400 ns |
parking_lot::RwLock | 3.40 ns | 2.09 ns | 9.3 ns | 241.8 ns | 37.7 ns |
std::sync::RwLock | 4.44 ns | 2.26 ns | 411.2 ns | 89.0 ns | 36.8 ns |
cargo bench --bench instant)| Implementation | Single-thread load | Single-thread store | Contended read | Load under write contention | Contended store |
|---|---|---|---|---|---|
AtomicInstant | 2.16 ns | 3.52 ns | 2.17 ns | 14.97 ns | 22.7 ns |
AtomicOptionInstant | 2.36 ns | 3.51 ns | 2.40 ns | 17.63 ns | 21.2 ns |
ArcSwap<Instant> | 2.95 ns | 74.9 ns | 2.92 ns | 17.53 ns | 972 ns |
parking_lot::RwLock | 3.43 ns | 2.10 ns | 81.3 ns | 213.9 ns | 26.9 ns |
std::sync::RwLock | 4.48 ns | 2.28 ns | 422.3 ns | 87.7 ns | 76.9 ns |
cargo bench --bench system_time)| Implementation | Single-thread load | Single-thread store | Contended read | Load under write contention | Contended store |
|---|---|---|---|---|---|
AtomicSystemTime | 1.26 ns | 3.41 ns | 1.28 ns | 8.89 ns | 27.1 ns |
AtomicOptionSystemTime | 1.25 ns | 3.47 ns | 1.30 ns | 9.17 ns | 25.2 ns |
ArcSwap<SystemTime> | 2.29 ns | 83.7 ns | 2.39 ns | 16.4 ns | 1,227 ns |
parking_lot::RwLock | 3.50 ns | 2.13 ns | 22.0 ns | 204.5 ns | 31.0 ns |
std::sync::RwLock | 4.69 ns | 2.31 ns | 554.2 ns | 84.4 ns | 38.7 ns |
Contended read = 4 background threads also reading; Load under write contention = 4 background threads writing, load measured on main thread; Contended store = 4 background threads writing, store measured on main thread. All numbers from Apple M4 Pro.
The minimum supported Rust version is 1.85.
atomic-time is under the terms of both the MIT license and the
Apache License (Version 2.0).
See LICENSE-APACHE, LICENSE-MIT for details.
Copyright (c) 2026 Al Liu.
Lock-free, thread-safe atomic versions of Duration, SystemTime, Instant and their Option variants
Rust
11
24 commits
updated Oct 7, 2026
Thread-safe atomic versions of Duration, SystemTime, Instant and their Option variants; lock-free on platforms with native AtomicU128 support, otherwise portable-atomic may fall back to a global lock
English | 简体中文
atomic-time provides thread-safe atomic versions of Rust's standard time types. They are lock-free on platforms with native AtomicU128 support; otherwise, portable-atomic may fall back to a global lock. All types use AtomicU128 (via portable-atomic) under the hood and expose the same API patterns as the standard std::sync::atomic types (load, store, swap, compare_exchange, compare_exchange_weak, fetch_update).
| Type | Wraps | no_std | arbitrary | quickcheck / proptest |
|---|---|---|---|---|
AtomicDuration | Duration | Yes | With std | With std |
AtomicOptionDuration | Option<Duration> | Yes | With std | With std |
AtomicSystemTime | SystemTime | No | With std | With std |
AtomicOptionSystemTime | Option<SystemTime> | No | With std | With std |
AtomicInstant | Instant | No | No | No |
AtomicOptionInstant | Option<Instant> | No | No | No |
[dependencies]
atomic-time = "1"
| Feature | Default | Description |
|---|---|---|
std | Yes | Enables SystemTime and Instant types |
serde | No | Enables Serialize/Deserialize for all types, including no_std builds |
arbitrary | No | Enables arbitrary for duration and system-time types; requires std |
quickcheck | No | Enables quickcheck for duration and system-time types, and their snapshot Clone implementations |
proptest | No | Enables proptest strategies for duration and system-time types |
For no_std environments (only AtomicDuration and AtomicOptionDuration are available):
[dependencies]
atomic-time = { version = "1", default-features = false }
The optional serde feature can also be enabled in a no_std build with features = ["serde"].
All three property-testing integrations require std. This includes
arbitrary, because the current upstream arbitrary v1 crate itself uses
std. Opt into one of the integrations with:
[dependencies]
atomic-time = { version = "1", features = ["arbitrary"] }
# or: atomic-time = { version = "1", features = ["quickcheck"] }
# or: atomic-time = { version = "1", features = ["proptest"] }
The quickcheck feature implements Clone for AtomicDuration,
AtomicOptionDuration, AtomicSystemTime, and AtomicOptionSystemTime.
Cloning performs a SeqCst load, so under concurrent writes the cloned value
is the value observed at that load's atomic linearization point.
AtomicInstant and AtomicOptionInstant intentionally do not implement
arbitrary, quickcheck, or proptest traits (and do not gain Clone from
these features). Their encodings use a process-local baseline, so a seed or
corpus cannot produce portable, deterministic values across process runs.
On some bare-metal targets without native compare-and-swap support, the final application must use Cargo feature unification to enable portable-atomic's critical-section feature and provide a target-appropriate critical-section implementation, or adopt another safe configuration from the official portable-atomic guide.
All six atomic types provide is_always_lock_free, try_update, update,
fetch_min, and fetch_max; the existing fetch_update remains available for
compatibility. fetch_min and fetch_max return the value observed before the
operation. For Option types, ordering is None < Some; Instant
comparisons are meaningful only for values using the same process-local
baseline.
AtomicDuration and AtomicOptionDuration also provide
fetch_saturating_add and fetch_saturating_sub, returning the old value. For
AtomicDuration, these saturate at Duration::MAX and Duration::ZERO. For
AtomicOptionDuration, Some values saturate while None remains None (it
is not treated as zero). These names intentionally use
fetch_saturating_add/sub, rather than fetch_add/sub, to avoid implying
integer wrapping; saturating add/sub helpers apply only to the Duration types.
AtomicSystemTime and AtomicOptionSystemTime accept only values at or after SystemTime::UNIX_EPOCH; earlier values panic.
AtomicInstant and AtomicOptionInstant encode Instant relative to a process-local baseline initialized from SystemTime::now() and Instant::now(). Within the same process, values within the platform's representable Instant range round-trip precisely. Across processes or restarts, the encoding is not portable and decoding only approximates wall-clock time. System clock adjustments can change that cross-process or restart interpretation. Behavior across sleep follows the platform's Instant semantics. Encoded instants must not be persisted as deadlines. If an extreme Duration cannot be represented by the platform's Instant, decoding falls back to the process baseline instead of panicking.
use std::sync::Arc;
use std::sync::atomic::Ordering;
use std::time::Duration;
use atomic_time::AtomicDuration;
let timeout = Arc::new(AtomicDuration::new(Duration::from_secs(30)));
// Update from another thread
let timeout_clone = timeout.clone();
std::thread::spawn(move || {
timeout_clone.store(Duration::from_secs(60), Ordering::Release);
});
use std::sync::atomic::Ordering;
use std::time::Instant;
use atomic_time::AtomicOptionInstant;
// Track the last time an event occurred
let last_event = AtomicOptionInstant::none();
assert_eq!(last_event.load(Ordering::Relaxed), None);
last_event.store(Some(Instant::now()), Ordering::Release);
assert!(last_event.load(Ordering::Acquire).is_some());
Run with cargo bench in the benchmark/ directory. Apple M4 Pro.
cargo bench --bench duration)| Implementation | Single-thread load | Single-thread store | Contended read | Load under write contention | Contended store |
|---|---|---|---|---|---|
AtomicDuration | 1.04 ns | 0.72 ns | 1.08 ns | 4.44 ns | 10.7 ns |
AtomicOptionDuration | 1.16 ns | 0.74 ns | 1.20 ns | 4.73 ns | 14.5 ns |
ArcSwap<Duration> | 2.30 ns | 86.5 ns | 2.38 ns | 15.5 ns | 1,400 ns |
parking_lot::RwLock | 3.40 ns | 2.09 ns | 9.3 ns | 241.8 ns | 37.7 ns |
std::sync::RwLock | 4.44 ns | 2.26 ns | 411.2 ns | 89.0 ns | 36.8 ns |
cargo bench --bench instant)| Implementation | Single-thread load | Single-thread store | Contended read | Load under write contention | Contended store |
|---|---|---|---|---|---|
AtomicInstant | 2.16 ns | 3.52 ns | 2.17 ns | 14.97 ns | 22.7 ns |
AtomicOptionInstant | 2.36 ns | 3.51 ns | 2.40 ns | 17.63 ns | 21.2 ns |
ArcSwap<Instant> | 2.95 ns | 74.9 ns | 2.92 ns | 17.53 ns | 972 ns |
parking_lot::RwLock | 3.43 ns | 2.10 ns | 81.3 ns | 213.9 ns | 26.9 ns |
std::sync::RwLock | 4.48 ns | 2.28 ns | 422.3 ns | 87.7 ns | 76.9 ns |
cargo bench --bench system_time)| Implementation | Single-thread load | Single-thread store | Contended read | Load under write contention | Contended store |
|---|---|---|---|---|---|
AtomicSystemTime | 1.26 ns | 3.41 ns | 1.28 ns | 8.89 ns | 27.1 ns |
AtomicOptionSystemTime | 1.25 ns | 3.47 ns | 1.30 ns | 9.17 ns | 25.2 ns |
ArcSwap<SystemTime> | 2.29 ns | 83.7 ns | 2.39 ns | 16.4 ns | 1,227 ns |
parking_lot::RwLock | 3.50 ns | 2.13 ns | 22.0 ns | 204.5 ns | 31.0 ns |
std::sync::RwLock | 4.69 ns | 2.31 ns | 554.2 ns | 84.4 ns | 38.7 ns |
Contended read = 4 background threads also reading; Load under write contention = 4 background threads writing, load measured on main thread; Contended store = 4 background threads writing, store measured on main thread. All numbers from Apple M4 Pro.
The minimum supported Rust version is 1.85.
atomic-time is under the terms of both the MIT license and the
Apache License (Version 2.0).
See LICENSE-APACHE, LICENSE-MIT for details.
Copyright (c) 2026 Al Liu.