Skip to main content

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//!
18//! # Arithmetic
19//!
20//! Nothing panics. The `+` and `-` operators saturate, at [`Instant::MAX`]
21//! or [`Duration::MAX`] and at zero, and an earlier instant less a later one
22//! is [`Duration::ZERO`]. The `checked_*` methods return `None` instead, for
23//! a caller that must tell.
24//!
25//! The unit constructors (`from_micros`, `from_millis`, `from_secs`)
26//! multiply, so they are exact or saturate. The unit accessors (`as_micros`,
27//! `as_millis`, `as_secs`) round toward zero. `min` and `max` are `const`.
28//!
29//! `Debug` prints nanoseconds with the unit: `Duration(1500ns)`,
30//! `Instant(1500ns)`.
31//!
32//! # No 64-bit division
33//!
34//! A 32-bit target has no instruction that divides a 64-bit value, so a
35//! division there links a routine from compiler-builtins, close to 1 KB,
36//! which the firmware images of rusty-bacnet don't allow. The unit accessors,
37//! [`Duration::checked_rem`], the conversion into `core::time::Duration` and
38//! `Debug` divide by binary long division instead: shifts, compares and
39//! subtractions, one step per bit of the quotient. Nothing else divides.
40//!
41//! # Features
42//!
43//! None, and no dependencies: the crate is `no_std` and needs no allocator.
44//!
45//! # Example
46//!
47//! ```
48//! use bacnet_time::{Duration, Instant};
49//!
50//! // A deadline 500 ms after a millisecond tick of 1250.
51//! let now = Instant::from_millis(1_250);
52//! let deadline = now + Duration::from_millis(500);
53//! assert_eq!(deadline.saturating_duration_since(now), Duration::from_millis(500));
54//!
55//! // Arithmetic saturates; the checked forms say when it would have.
56//! assert_eq!(now - deadline, Duration::ZERO);
57//! assert_eq!(Instant::ZERO.checked_sub(Duration::from_secs(1)), None);
58//!
59//! // A host's clock, through core::time::Duration.
60//! let elapsed = core::time::Duration::from_micros(2_500);
61//! assert_eq!(Duration::try_from(elapsed), Ok(Duration::from_micros(2_500)));
62//! ```
63
64#![no_std]
65#![cfg_attr(docsrs, feature(doc_cfg))]
66#![forbid(unsafe_code)]
67#![warn(missing_debug_implementations, clippy::must_use_candidate)]
68
69mod convert;
70mod divide;
71mod duration;
72mod format;
73mod instant;
74
75pub use convert::DurationTooLong;
76pub use duration::Duration;
77pub use instant::Instant;
78
79/// Nanoseconds in a microsecond.
80const NANOS_PER_MICRO: u64 = 1_000;
81/// Nanoseconds in a millisecond.
82const NANOS_PER_MILLI: u64 = 1_000_000;
83/// Nanoseconds in a second.
84const NANOS_PER_SEC: u64 = 1_000_000_000;
85
86/// The README's example, compiled and run as a doctest.
87#[cfg(doctest)]
88#[doc = include_str!("../README.md")]
89pub struct ReadmeDoctests;