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