bacnet_time/lib.rs
1//! The time the portable BACnet cores share: a monotonic [`Instant`] and a
2//! [`Duration`], both counted in nanoseconds on a `u64`.
3//!
4//! The cores read no clock. Each call that depends on time takes `now`, an
5//! [`Instant`] on whatever monotonic timeline the caller keeps, counted from
6//! an epoch the caller chooses. A `u64` of nanoseconds lasts about 584 years
7//! from that epoch. One type for every core lets firmware that runs several
8//! of them, an MS/TP port and a B/IP port say, feed them all one clock.
9//!
10//! - A host maps its monotonic clock onto the timeline exactly: nanoseconds
11//! are the finest unit `core::time::Duration` has. A [`Duration`] converts
12//! into one with [`From`], and one converts back with [`TryFrom`], which
13//! refuses a length past [`Duration::MAX`] ([`DurationTooLong`]), or with
14//! [`Duration::saturating_from_core`], which clamps it.
15//! - Firmware with a millisecond or microsecond tick builds its `now` with
16//! [`Instant::from_millis`] or [`Instant::from_micros`].
17//! - With the `std` feature, `HostClock` does the mapping for a host: it
18//! fixes one `std::time::Instant` to a point of the timeline and converts
19//! both ways, exactly.
20//!
21//! A core with several timers hands back its next deadline as an
22//! `Option<Instant>`, `None` for none. [`earliest`] merges such deadlines;
23//! `Option::min` would not, since it orders `None` first.
24//!
25//! # Arithmetic
26//!
27//! Nothing panics. The `+` and `-` operators saturate, at [`Instant::MAX`]
28//! or [`Duration::MAX`] and at zero, and an earlier instant less a later one
29//! is [`Duration::ZERO`]. The `checked_*` methods return `None` instead, for
30//! a caller that must tell.
31//!
32//! The unit constructors (`from_micros`, `from_millis`, `from_secs`)
33//! multiply, so they are exact or saturate. The unit accessors (`as_micros`,
34//! `as_millis`, `as_secs`) round toward zero. `min` and `max` are `const`.
35//!
36//! `Debug` prints nanoseconds with the unit: `Duration(1500ns)`,
37//! `Instant(1500ns)`.
38//!
39//! # No 64-bit division
40//!
41//! A 32-bit target has no instruction that divides a 64-bit value, so a
42//! division there links a routine from compiler-builtins, close to 1 KB,
43//! which the firmware images of rusty-bacnet don't allow. The unit accessors,
44//! [`Duration::checked_rem`], the conversion into `core::time::Duration` and
45//! `Debug` divide by binary long division instead: shifts, compares and
46//! subtractions, one step per bit of the quotient. Nothing else divides.
47//!
48//! # Features
49//!
50//! No dependencies, and no features by default: the crate is `no_std` and
51//! needs no allocator.
52//!
53//! - `std`: `HostClock`, which maps a host's `std::time::Instant` onto the
54//! timeline and back.
55//!
56//! # Example
57//!
58//! ```
59//! use bacnet_time::{Duration, Instant};
60//!
61//! // A deadline 500 ms after a millisecond tick of 1250.
62//! let now = Instant::from_millis(1_250);
63//! let deadline = now + Duration::from_millis(500);
64//! assert_eq!(deadline.saturating_duration_since(now), Duration::from_millis(500));
65//!
66//! // Arithmetic saturates; the checked forms say when it would have.
67//! assert_eq!(now - deadline, Duration::ZERO);
68//! assert_eq!(Instant::ZERO.checked_sub(Duration::from_secs(1)), None);
69//!
70//! // A host's clock, through core::time::Duration.
71//! let elapsed = core::time::Duration::from_micros(2_500);
72//! assert_eq!(Duration::try_from(elapsed), Ok(Duration::from_micros(2_500)));
73//! ```
74
75#![no_std]
76#![cfg_attr(docsrs, feature(doc_cfg))]
77#![forbid(unsafe_code)]
78#![warn(missing_debug_implementations, clippy::must_use_candidate)]
79
80#[cfg(feature = "std")]
81extern crate std;
82
83mod convert;
84mod deadline;
85mod divide;
86mod duration;
87mod format;
88#[cfg(feature = "std")]
89mod host;
90mod instant;
91
92pub use convert::DurationTooLong;
93pub use deadline::earliest;
94pub use duration::Duration;
95#[cfg(feature = "std")]
96pub use host::HostClock;
97pub use instant::Instant;
98
99/// Nanoseconds in a microsecond.
100const NANOS_PER_MICRO: u64 = 1_000;
101/// Nanoseconds in a millisecond.
102const NANOS_PER_MILLI: u64 = 1_000_000;
103/// Nanoseconds in a second.
104const NANOS_PER_SEC: u64 = 1_000_000_000;
105
106/// The README's example, compiled and run as a doctest.
107#[cfg(doctest)]
108#[doc = include_str!("../README.md")]
109pub struct ReadmeDoctests;