clock_lib/lib.rs
1//! # clock-lib
2//!
3//! TIME READINGS FOR RUST
4//!
5//! Monotonic and wall-clock time readings with a mockable clock for deterministic
6//! testing. Simple Tier-1 API, zero dependencies, no `unsafe`.
7//!
8//! # Two kinds of time
9//!
10//! There are two fundamentally different kinds of time, and conflating them is a
11//! common source of bugs:
12//!
13//! - **Monotonic time** ([`now`]) never goes backwards. Use it to measure elapsed
14//! time: rate limiting, timeouts, benchmarks. Only meaningful as a delta.
15//! - **Wall-clock time** ([`wall`], [`unix`]) is calendar time. It can jump (NTP,
16//! DST, manual changes). Use it for timestamps and logging, never for measuring
17//! elapsed time.
18//!
19//! The two are returned as distinct types — [`Monotonic`] and [`Wall`] — so the
20//! compiler rejects any attempt to subtract one from the other. That separation
21//! is the central design choice of the crate.
22//!
23//! # Tier-1 API (the lazy path)
24//!
25//! ```
26//! use clock_lib as clock;
27//!
28//! let start = clock::now(); // monotonic reading
29//! // ... do work ...
30//! let took = clock::elapsed(start); // Duration since `start`
31//!
32//! let secs = clock::unix(); // unix seconds (like PHP time())
33//! # let _ = (took, secs);
34//! ```
35//!
36//! # Tier-2 API (the mockable clock)
37//!
38//! The real value is deterministic time in tests. The [`Clock`] trait has two
39//! implementations — [`SystemClock`] for production and [`ManualClock`]
40//! for tests — so timing-driven code can be exercised without ever
41//! calling `sleep`.
42//!
43//! ```
44//! use clock_lib::{Clock, ManualClock, Monotonic};
45//! use std::time::Duration;
46//!
47//! fn expired<C: Clock>(clock: &C, stamp: Monotonic, ttl: Duration) -> bool {
48//! clock.now().duration_since(stamp) >= ttl
49//! }
50//!
51//! let clock = ManualClock::new();
52//! let stamp = clock.now();
53//! assert!(!expired(&clock, stamp, Duration::from_secs(60)));
54//!
55//! clock.advance(Duration::from_secs(60));
56//! assert!(expired(&clock, stamp, Duration::from_secs(60)));
57//! ```
58//!
59//! # Feature flags
60//!
61//! - **`std`** (default): enables every reading API — [`Monotonic`],
62//! [`Wall`], [`Clock`], [`SystemClock`], [`ManualClock`], and the Tier-1
63//! free functions.
64//!
65//! Disable default features to build for a `no_std` target. With `std`
66//! off, only [`VERSION`] is exposed — the readings themselves require
67//! an operating system clock and cannot be portably provided without one.
68//!
69//! ```toml
70//! [dependencies]
71//! clock-lib = { version = "1.0", default-features = false }
72//! ```
73//!
74//! # License
75//!
76//! Dual-licensed under Apache-2.0 OR MIT.
77
78#![doc(html_root_url = "https://docs.rs/clock-lib")]
79#![cfg_attr(docsrs, feature(doc_cfg))]
80#![cfg_attr(not(feature = "std"), no_std)]
81#![forbid(unsafe_code)]
82#![deny(warnings)]
83#![deny(missing_docs)]
84#![deny(unsafe_op_in_unsafe_fn)]
85#![deny(unused_must_use)]
86#![deny(unused_results)]
87#![deny(clippy::unwrap_used)]
88#![deny(clippy::expect_used)]
89#![deny(clippy::todo)]
90#![deny(clippy::unimplemented)]
91#![deny(clippy::print_stdout)]
92#![deny(clippy::print_stderr)]
93#![deny(clippy::dbg_macro)]
94#![deny(clippy::unreachable)]
95#![deny(clippy::undocumented_unsafe_blocks)]
96#![deny(clippy::missing_safety_doc)]
97#![warn(clippy::pedantic)]
98#![allow(clippy::module_name_repetitions)]
99
100mod clock;
101mod monotonic;
102mod wall;
103
104#[cfg(feature = "std")]
105pub use clock::{Clock, ManualClock, SystemClock};
106#[cfg(feature = "std")]
107pub use monotonic::Monotonic;
108#[cfg(feature = "std")]
109pub use wall::Wall;
110
111/// Crate version string, populated by Cargo at build time.
112pub const VERSION: &str = env!("CARGO_PKG_VERSION");
113
114/// Captures the current monotonic time.
115///
116/// Shortcut for [`Monotonic::now`]. Pair it with [`elapsed`] to measure how
117/// long an operation took.
118///
119/// # Examples
120///
121/// ```
122/// use clock_lib as clock;
123///
124/// let start = clock::now();
125/// // ... work ...
126/// let took = clock::elapsed(start);
127/// # let _ = took;
128/// ```
129#[cfg(feature = "std")]
130#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
131#[inline]
132#[must_use]
133pub fn now() -> Monotonic {
134 Monotonic::now()
135}
136
137/// Returns the [`Duration`](core::time::Duration) elapsed since `earlier`.
138///
139/// Shortcut for [`Monotonic::elapsed`]. The argument is the
140/// [`Monotonic`] captured at the start of the interval; the return value
141/// is the time from then until now.
142///
143/// # Examples
144///
145/// ```
146/// use clock_lib as clock;
147///
148/// let start = clock::now();
149/// // ... work ...
150/// let took = clock::elapsed(start);
151/// # let _ = took;
152/// ```
153#[cfg(feature = "std")]
154#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
155#[inline]
156#[must_use]
157pub fn elapsed(earlier: Monotonic) -> core::time::Duration {
158 earlier.elapsed()
159}
160
161/// Captures the current wall-clock time.
162///
163/// Shortcut for [`Wall::now`]. Use it for timestamps. For elapsed-time
164/// measurement, use [`now`] instead — wall-clock readings can jump.
165///
166/// # Examples
167///
168/// ```
169/// use clock_lib as clock;
170///
171/// let stamp = clock::wall();
172/// let secs = stamp.unix_seconds();
173/// assert!(secs > 0);
174/// ```
175#[cfg(feature = "std")]
176#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
177#[inline]
178#[must_use]
179pub fn wall() -> Wall {
180 Wall::now()
181}
182
183/// Returns the current Unix time in whole seconds.
184///
185/// Shortcut for `wall().unix_seconds()`. Equivalent in spirit to C's
186/// `time(NULL)` or PHP's `time()`.
187///
188/// Returns zero if the system clock is set to a moment before the Unix
189/// epoch.
190///
191/// # Examples
192///
193/// ```
194/// use clock_lib as clock;
195///
196/// let now = clock::unix();
197/// assert!(now > 0);
198/// ```
199#[cfg(feature = "std")]
200#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
201#[inline]
202#[must_use]
203pub fn unix() -> u64 {
204 Wall::now().unix_seconds()
205}
206
207/// Returns the current Unix time in whole milliseconds.
208///
209/// Shortcut for `wall().unix_millis()`. Returns zero if the system clock is
210/// set to a moment before the Unix epoch.
211///
212/// # Examples
213///
214/// ```
215/// use clock_lib as clock;
216///
217/// let now = clock::unix_ms();
218/// assert!(now > 0);
219/// ```
220#[cfg(feature = "std")]
221#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
222#[inline]
223#[must_use]
224pub fn unix_ms() -> u128 {
225 Wall::now().unix_millis()
226}
227
228/// Returns the current Unix time in whole nanoseconds.
229///
230/// Shortcut for `wall().unix_nanos()`. Returns zero if the system clock is
231/// set to a moment before the Unix epoch.
232///
233/// # Examples
234///
235/// ```
236/// use clock_lib as clock;
237///
238/// let now = clock::unix_ns();
239/// assert!(now > 0);
240/// ```
241#[cfg(feature = "std")]
242#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
243#[inline]
244#[must_use]
245pub fn unix_ns() -> u128 {
246 Wall::now().unix_nanos()
247}