Skip to main content

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//! # License
60//!
61//! Dual-licensed under Apache-2.0 OR MIT.
62
63#![doc(html_root_url = "https://docs.rs/clock-lib")]
64#![cfg_attr(docsrs, feature(doc_cfg))]
65#![cfg_attr(not(feature = "std"), no_std)]
66#![forbid(unsafe_code)]
67#![deny(warnings)]
68#![deny(missing_docs)]
69#![deny(unsafe_op_in_unsafe_fn)]
70#![deny(unused_must_use)]
71#![deny(unused_results)]
72#![deny(clippy::unwrap_used)]
73#![deny(clippy::expect_used)]
74#![deny(clippy::todo)]
75#![deny(clippy::unimplemented)]
76#![deny(clippy::print_stdout)]
77#![deny(clippy::print_stderr)]
78#![deny(clippy::dbg_macro)]
79#![deny(clippy::unreachable)]
80#![deny(clippy::undocumented_unsafe_blocks)]
81#![deny(clippy::missing_safety_doc)]
82#![warn(clippy::pedantic)]
83#![allow(clippy::module_name_repetitions)]
84
85mod clock;
86mod monotonic;
87mod wall;
88
89#[cfg(feature = "std")]
90pub use clock::{Clock, ManualClock, SystemClock};
91#[cfg(feature = "std")]
92pub use monotonic::Monotonic;
93#[cfg(feature = "std")]
94pub use wall::Wall;
95
96/// Crate version string, populated by Cargo at build time.
97pub const VERSION: &str = env!("CARGO_PKG_VERSION");
98
99/// Captures the current monotonic time.
100///
101/// Shortcut for [`Monotonic::now`]. Pair it with [`elapsed`] to measure how
102/// long an operation took.
103///
104/// # Examples
105///
106/// ```
107/// use clock_lib as clock;
108///
109/// let start = clock::now();
110/// // ... work ...
111/// let took = clock::elapsed(start);
112/// # let _ = took;
113/// ```
114#[cfg(feature = "std")]
115#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
116#[inline]
117#[must_use]
118pub fn now() -> Monotonic {
119    Monotonic::now()
120}
121
122/// Returns the [`Duration`](core::time::Duration) elapsed since `earlier`.
123///
124/// Shortcut for [`Monotonic::elapsed`]. The argument is the
125/// [`Monotonic`] captured at the start of the interval; the return value
126/// is the time from then until now.
127///
128/// # Examples
129///
130/// ```
131/// use clock_lib as clock;
132///
133/// let start = clock::now();
134/// // ... work ...
135/// let took = clock::elapsed(start);
136/// # let _ = took;
137/// ```
138#[cfg(feature = "std")]
139#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
140#[inline]
141#[must_use]
142pub fn elapsed(earlier: Monotonic) -> core::time::Duration {
143    earlier.elapsed()
144}
145
146/// Captures the current wall-clock time.
147///
148/// Shortcut for [`Wall::now`]. Use it for timestamps. For elapsed-time
149/// measurement, use [`now`] instead — wall-clock readings can jump.
150///
151/// # Examples
152///
153/// ```
154/// use clock_lib as clock;
155///
156/// let stamp = clock::wall();
157/// let secs = stamp.unix_seconds();
158/// assert!(secs > 0);
159/// ```
160#[cfg(feature = "std")]
161#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
162#[inline]
163#[must_use]
164pub fn wall() -> Wall {
165    Wall::now()
166}
167
168/// Returns the current Unix time in whole seconds.
169///
170/// Shortcut for `wall().unix_seconds()`. Equivalent in spirit to C's
171/// `time(NULL)` or PHP's `time()`.
172///
173/// Returns zero if the system clock is set to a moment before the Unix
174/// epoch.
175///
176/// # Examples
177///
178/// ```
179/// use clock_lib as clock;
180///
181/// let now = clock::unix();
182/// assert!(now > 0);
183/// ```
184#[cfg(feature = "std")]
185#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
186#[inline]
187#[must_use]
188pub fn unix() -> u64 {
189    Wall::now().unix_seconds()
190}
191
192/// Returns the current Unix time in whole milliseconds.
193///
194/// Shortcut for `wall().unix_millis()`. Returns zero if the system clock is
195/// set to a moment before the Unix epoch.
196///
197/// # Examples
198///
199/// ```
200/// use clock_lib as clock;
201///
202/// let now = clock::unix_ms();
203/// assert!(now > 0);
204/// ```
205#[cfg(feature = "std")]
206#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
207#[inline]
208#[must_use]
209pub fn unix_ms() -> u128 {
210    Wall::now().unix_millis()
211}
212
213/// Returns the current Unix time in whole nanoseconds.
214///
215/// Shortcut for `wall().unix_nanos()`. Returns zero if the system clock is
216/// set to a moment before the Unix epoch.
217///
218/// # Examples
219///
220/// ```
221/// use clock_lib as clock;
222///
223/// let now = clock::unix_ns();
224/// assert!(now > 0);
225/// ```
226#[cfg(feature = "std")]
227#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
228#[inline]
229#[must_use]
230pub fn unix_ns() -> u128 {
231    Wall::now().unix_nanos()
232}