summaryrefslogtreecommitdiff
path: root/src/time.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/time.rs')
-rw-r--r--src/time.rs460
1 files changed, 460 insertions, 0 deletions
diff --git a/src/time.rs b/src/time.rs
new file mode 100644
index 0000000..e6904af
--- /dev/null
+++ b/src/time.rs
@@ -0,0 +1,460 @@
+/*! Time structures.
+
+The `time` module contains structures used to represent both
+absolute and relative time.
+
+ - [Instant] is used to represent absolute time.
+ - [Duration] is used to represent relative time.
+
+[Instant]: struct.Instant.html
+[Duration]: struct.Duration.html
+*/
+
+use core::{fmt, ops};
+
+/// A representation of an absolute time value.
+///
+/// The `Instant` type is a wrapper around a `i64` value that
+/// represents a number of microseconds, monotonically increasing
+/// since an arbitrary moment in time, such as system startup.
+///
+/// * A value of `0` is inherently arbitrary.
+/// * A value less than `0` indicates a time before the starting
+/// point.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Instant {
+ micros: i64,
+}
+
+impl Instant {
+ pub const ZERO: Instant = Instant::from_micros_const(0);
+
+ /// Create a new `Instant` from a number of microseconds.
+ pub fn from_micros<T: Into<i64>>(micros: T) -> Instant {
+ Instant {
+ micros: micros.into(),
+ }
+ }
+
+ pub const fn from_micros_const(micros: i64) -> Instant {
+ Instant { micros }
+ }
+
+ /// Create a new `Instant` from a number of milliseconds.
+ pub fn from_millis<T: Into<i64>>(millis: T) -> Instant {
+ Instant {
+ micros: millis.into() * 1000,
+ }
+ }
+
+ /// Create a new `Instant` from a number of milliseconds.
+ pub const fn from_millis_const(millis: i64) -> Instant {
+ Instant {
+ micros: millis * 1000,
+ }
+ }
+
+ /// Create a new `Instant` from a number of seconds.
+ pub fn from_secs<T: Into<i64>>(secs: T) -> Instant {
+ Instant {
+ micros: secs.into() * 1000000,
+ }
+ }
+
+ /// Create a new `Instant` from the current [std::time::SystemTime].
+ ///
+ /// See [std::time::SystemTime::now]
+ ///
+ /// [std::time::SystemTime]: https://doc.rust-lang.org/std/time/struct.SystemTime.html
+ /// [std::time::SystemTime::now]: https://doc.rust-lang.org/std/time/struct.SystemTime.html#method.now
+ #[cfg(feature = "std")]
+ pub fn now() -> Instant {
+ Self::from(::std::time::SystemTime::now())
+ }
+
+ /// The fractional number of milliseconds that have passed
+ /// since the beginning of time.
+ pub const fn millis(&self) -> i64 {
+ self.micros % 1000000 / 1000
+ }
+
+ /// The fractional number of microseconds that have passed
+ /// since the beginning of time.
+ pub const fn micros(&self) -> i64 {
+ self.micros % 1000000
+ }
+
+ /// The number of whole seconds that have passed since the
+ /// beginning of time.
+ pub const fn secs(&self) -> i64 {
+ self.micros / 1000000
+ }
+
+ /// The total number of milliseconds that have passed since
+ /// the beginning of time.
+ pub const fn total_millis(&self) -> i64 {
+ self.micros / 1000
+ }
+ /// The total number of milliseconds that have passed since
+ /// the beginning of time.
+ pub const fn total_micros(&self) -> i64 {
+ self.micros
+ }
+}
+
+#[cfg(feature = "std")]
+impl From<::std::time::Instant> for Instant {
+ fn from(other: ::std::time::Instant) -> Instant {
+ let elapsed = other.elapsed();
+ Instant::from_micros((elapsed.as_secs() * 1_000000) as i64 + elapsed.subsec_micros() as i64)
+ }
+}
+
+#[cfg(feature = "std")]
+impl From<::std::time::SystemTime> for Instant {
+ fn from(other: ::std::time::SystemTime) -> Instant {
+ let n = other
+ .duration_since(::std::time::UNIX_EPOCH)
+ .expect("start time must not be before the unix epoch");
+ Self::from_micros(n.as_secs() as i64 * 1000000 + n.subsec_micros() as i64)
+ }
+}
+
+#[cfg(feature = "std")]
+impl From<Instant> for ::std::time::SystemTime {
+ fn from(val: Instant) -> Self {
+ ::std::time::UNIX_EPOCH + ::std::time::Duration::from_micros(val.micros as u64)
+ }
+}
+
+impl fmt::Display for Instant {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}.{:0>3}s", self.secs(), self.millis())
+ }
+}
+
+#[cfg(feature = "defmt")]
+impl defmt::Format for Instant {
+ fn format(&self, f: defmt::Formatter) {
+ defmt::write!(f, "{}.{:03}s", self.secs(), self.millis());
+ }
+}
+
+impl ops::Add<Duration> for Instant {
+ type Output = Instant;
+
+ fn add(self, rhs: Duration) -> Instant {
+ Instant::from_micros(self.micros + rhs.total_micros() as i64)
+ }
+}
+
+impl ops::AddAssign<Duration> for Instant {
+ fn add_assign(&mut self, rhs: Duration) {
+ self.micros += rhs.total_micros() as i64;
+ }
+}
+
+impl ops::Sub<Duration> for Instant {
+ type Output = Instant;
+
+ fn sub(self, rhs: Duration) -> Instant {
+ Instant::from_micros(self.micros - rhs.total_micros() as i64)
+ }
+}
+
+impl ops::SubAssign<Duration> for Instant {
+ fn sub_assign(&mut self, rhs: Duration) {
+ self.micros -= rhs.total_micros() as i64;
+ }
+}
+
+impl ops::Sub<Instant> for Instant {
+ type Output = Duration;
+
+ fn sub(self, rhs: Instant) -> Duration {
+ Duration::from_micros((self.micros - rhs.micros).unsigned_abs())
+ }
+}
+
+/// A relative amount of time.
+#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Duration {
+ micros: u64,
+}
+
+impl Duration {
+ pub const ZERO: Duration = Duration::from_micros(0);
+ /// The longest possible duration we can encode.
+ pub const MAX: Duration = Duration::from_micros(u64::MAX);
+ /// Create a new `Duration` from a number of microseconds.
+ pub const fn from_micros(micros: u64) -> Duration {
+ Duration { micros }
+ }
+
+ /// Create a new `Duration` from a number of milliseconds.
+ pub const fn from_millis(millis: u64) -> Duration {
+ Duration {
+ micros: millis * 1000,
+ }
+ }
+
+ /// Create a new `Instant` from a number of seconds.
+ pub const fn from_secs(secs: u64) -> Duration {
+ Duration {
+ micros: secs * 1000000,
+ }
+ }
+
+ /// The fractional number of milliseconds in this `Duration`.
+ pub const fn millis(&self) -> u64 {
+ self.micros / 1000 % 1000
+ }
+
+ /// The fractional number of milliseconds in this `Duration`.
+ pub const fn micros(&self) -> u64 {
+ self.micros % 1000000
+ }
+
+ /// The number of whole seconds in this `Duration`.
+ pub const fn secs(&self) -> u64 {
+ self.micros / 1000000
+ }
+
+ /// The total number of milliseconds in this `Duration`.
+ pub const fn total_millis(&self) -> u64 {
+ self.micros / 1000
+ }
+
+ /// The total number of microseconds in this `Duration`.
+ pub const fn total_micros(&self) -> u64 {
+ self.micros
+ }
+}
+
+impl fmt::Display for Duration {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}.{:03}s", self.secs(), self.millis())
+ }
+}
+
+#[cfg(feature = "defmt")]
+impl defmt::Format for Duration {
+ fn format(&self, f: defmt::Formatter) {
+ defmt::write!(f, "{}.{:03}s", self.secs(), self.millis());
+ }
+}
+
+impl ops::Add<Duration> for Duration {
+ type Output = Duration;
+
+ fn add(self, rhs: Duration) -> Duration {
+ Duration::from_micros(self.micros + rhs.total_micros())
+ }
+}
+
+impl ops::AddAssign<Duration> for Duration {
+ fn add_assign(&mut self, rhs: Duration) {
+ self.micros += rhs.total_micros();
+ }
+}
+
+impl ops::Sub<Duration> for Duration {
+ type Output = Duration;
+
+ fn sub(self, rhs: Duration) -> Duration {
+ Duration::from_micros(
+ self.micros
+ .checked_sub(rhs.total_micros())
+ .expect("overflow when subtracting durations"),
+ )
+ }
+}
+
+impl ops::SubAssign<Duration> for Duration {
+ fn sub_assign(&mut self, rhs: Duration) {
+ self.micros = self
+ .micros
+ .checked_sub(rhs.total_micros())
+ .expect("overflow when subtracting durations");
+ }
+}
+
+impl ops::Mul<u32> for Duration {
+ type Output = Duration;
+
+ fn mul(self, rhs: u32) -> Duration {
+ Duration::from_micros(self.micros * rhs as u64)
+ }
+}
+
+impl ops::MulAssign<u32> for Duration {
+ fn mul_assign(&mut self, rhs: u32) {
+ self.micros *= rhs as u64;
+ }
+}
+
+impl ops::Div<u32> for Duration {
+ type Output = Duration;
+
+ fn div(self, rhs: u32) -> Duration {
+ Duration::from_micros(self.micros / rhs as u64)
+ }
+}
+
+impl ops::DivAssign<u32> for Duration {
+ fn div_assign(&mut self, rhs: u32) {
+ self.micros /= rhs as u64;
+ }
+}
+
+impl ops::Shl<u32> for Duration {
+ type Output = Duration;
+
+ fn shl(self, rhs: u32) -> Duration {
+ Duration::from_micros(self.micros << rhs)
+ }
+}
+
+impl ops::ShlAssign<u32> for Duration {
+ fn shl_assign(&mut self, rhs: u32) {
+ self.micros <<= rhs;
+ }
+}
+
+impl ops::Shr<u32> for Duration {
+ type Output = Duration;
+
+ fn shr(self, rhs: u32) -> Duration {
+ Duration::from_micros(self.micros >> rhs)
+ }
+}
+
+impl ops::ShrAssign<u32> for Duration {
+ fn shr_assign(&mut self, rhs: u32) {
+ self.micros >>= rhs;
+ }
+}
+
+impl From<::core::time::Duration> for Duration {
+ fn from(other: ::core::time::Duration) -> Duration {
+ Duration::from_micros(other.as_secs() * 1000000 + other.subsec_micros() as u64)
+ }
+}
+
+impl From<Duration> for ::core::time::Duration {
+ fn from(val: Duration) -> Self {
+ ::core::time::Duration::from_micros(val.total_micros())
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_instant_ops() {
+ // std::ops::Add
+ assert_eq!(
+ Instant::from_millis(4) + Duration::from_millis(6),
+ Instant::from_millis(10)
+ );
+ // std::ops::Sub
+ assert_eq!(
+ Instant::from_millis(7) - Duration::from_millis(5),
+ Instant::from_millis(2)
+ );
+ }
+
+ #[test]
+ fn test_instant_getters() {
+ let instant = Instant::from_millis(5674);
+ assert_eq!(instant.secs(), 5);
+ assert_eq!(instant.millis(), 674);
+ assert_eq!(instant.total_millis(), 5674);
+ }
+
+ #[test]
+ fn test_instant_display() {
+ assert_eq!(format!("{}", Instant::from_millis(74)), "0.074s");
+ assert_eq!(format!("{}", Instant::from_millis(5674)), "5.674s");
+ assert_eq!(format!("{}", Instant::from_millis(5000)), "5.000s");
+ }
+
+ #[test]
+ #[cfg(feature = "std")]
+ fn test_instant_conversions() {
+ let mut epoc: ::std::time::SystemTime = Instant::from_millis(0).into();
+ assert_eq!(
+ Instant::from(::std::time::UNIX_EPOCH),
+ Instant::from_millis(0)
+ );
+ assert_eq!(epoc, ::std::time::UNIX_EPOCH);
+ epoc = Instant::from_millis(2085955200i64 * 1000).into();
+ assert_eq!(
+ epoc,
+ ::std::time::UNIX_EPOCH + ::std::time::Duration::from_secs(2085955200)
+ );
+ }
+
+ #[test]
+ fn test_duration_ops() {
+ // std::ops::Add
+ assert_eq!(
+ Duration::from_millis(40) + Duration::from_millis(2),
+ Duration::from_millis(42)
+ );
+ // std::ops::Sub
+ assert_eq!(
+ Duration::from_millis(555) - Duration::from_millis(42),
+ Duration::from_millis(513)
+ );
+ // std::ops::Mul
+ assert_eq!(Duration::from_millis(13) * 22, Duration::from_millis(286));
+ // std::ops::Div
+ assert_eq!(Duration::from_millis(53) / 4, Duration::from_micros(13250));
+ }
+
+ #[test]
+ fn test_duration_assign_ops() {
+ let mut duration = Duration::from_millis(4735);
+ duration += Duration::from_millis(1733);
+ assert_eq!(duration, Duration::from_millis(6468));
+ duration -= Duration::from_millis(1234);
+ assert_eq!(duration, Duration::from_millis(5234));
+ duration *= 4;
+ assert_eq!(duration, Duration::from_millis(20936));
+ duration /= 5;
+ assert_eq!(duration, Duration::from_micros(4187200));
+ }
+
+ #[test]
+ #[should_panic(expected = "overflow when subtracting durations")]
+ fn test_sub_from_zero_overflow() {
+ let _ = Duration::from_millis(0) - Duration::from_millis(1);
+ }
+
+ #[test]
+ #[should_panic(expected = "attempt to divide by zero")]
+ fn test_div_by_zero() {
+ let _ = Duration::from_millis(4) / 0;
+ }
+
+ #[test]
+ fn test_duration_getters() {
+ let instant = Duration::from_millis(4934);
+ assert_eq!(instant.secs(), 4);
+ assert_eq!(instant.millis(), 934);
+ assert_eq!(instant.total_millis(), 4934);
+ }
+
+ #[test]
+ fn test_duration_conversions() {
+ let mut std_duration = ::core::time::Duration::from_millis(4934);
+ let duration: Duration = std_duration.into();
+ assert_eq!(duration, Duration::from_millis(4934));
+ assert_eq!(Duration::from(std_duration), Duration::from_millis(4934));
+
+ std_duration = duration.into();
+ assert_eq!(std_duration, ::core::time::Duration::from_millis(4934));
+ }
+}