avr-oxide 0.4.0

An extremely simple Rusty operating system for AVR microcontrollers
/* time.rs
 *
 * Developed by Tim Walls <tim.walls@snowgoons.com>
 * Copyright (c) All Rights Reserved, Tim Walls
 */
//! Simplified datastructures for dealing with time, meant to broadly
//! mirror the functionality provided by std::time where possible (and is
//! derived directly from that source.)
//!
//! Main difference with the 'real' crates is using smaller underlying
//! datatypes - partly to avoid 64bit maths (which is broken on AVR at the
//! time of writing)


// Imports ===================================================================

use ufmt::derive::uDebug;
use core::ops::{Add, AddAssign, Div, Mul, MulAssign, Sub, SubAssign};
use avr_oxide::panic_if_none;
use avr_oxide::private::delayq::Floored;
use oxide_macros::Persist;

// Declarations ==============================================================
const MILLIS_PER_SEC: u16 = 1000;

#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, uDebug, Persist )]
pub struct Duration {
  secs: u32,
  millis: u16,
}

// Code ======================================================================
impl Duration {
  pub const SECOND: Duration = Duration::from_secs(1);
  pub const MILLISECOND: Duration = Duration::from_millis(1);
  pub const ZERO: Duration = Duration::from_millis(0);
  pub const MAX: Duration = Duration::new(u32::MAX, MILLIS_PER_SEC - 1);

  pub const fn new(secs: u32, millis: u16) -> Duration {
    let secs = match secs.checked_add((millis / MILLIS_PER_SEC) as u32) {
      Some(secs) => secs,
      None => panic!()
    };
    let millis = millis % MILLIS_PER_SEC;
    Duration { secs, millis }
  }

  pub const fn from_secs(secs: u32) -> Duration {
    Duration { secs, millis: 0 }
  }

  pub const fn from_millis(millis: u32) -> Duration {
    Duration {
      secs: millis as u32 / MILLIS_PER_SEC as u32,
      millis: (millis % MILLIS_PER_SEC as u32) as u16,
    }
  }
  pub const fn is_zero(&self) -> bool {
    self.secs == 0 && self.millis == 0
  }
  pub const fn as_secs(&self) -> u32 {
    self.secs
  }
  pub const fn subsec_millis(&self) -> u16 {
    self.millis
  }
  pub const fn as_millis(&self) -> u64 {
    self.secs as u64 * MILLIS_PER_SEC as u64 +  self.millis as u64
  }
  /// Checked `Duration` addition. Computes `self + other`, returning [`None`]
  /// if overflow occurred.
  ///
  pub const fn checked_add(self, rhs: Duration) -> Option<Duration> {
    if let Some(mut secs) = self.secs.checked_add(rhs.secs) {
      let mut millis = self.millis + rhs.millis;
      if millis >= MILLIS_PER_SEC {
        millis -= MILLIS_PER_SEC;
        if let Some(new_secs) = secs.checked_add(1) {
          secs = new_secs;
        } else {
          return None;
        }
      }
      Some(Duration { secs, millis })
    } else {
      None
    }
  }

  /// Saturating `Duration` addition. Computes `self + other`, returning [`Duration::MAX`]
  /// if overflow occurred.
  ///
  pub const fn saturating_add(self, rhs: Duration) -> Duration {
    match self.checked_add(rhs) {
      Some(res) => res,
      None => Duration::MAX,
    }
  }

  /// Checked `Duration` subtraction. Computes `self - other`, returning [`None`]
  /// if the result would be negative or if overflow occurred.
  ///
  pub const fn checked_sub(self, rhs: Duration) -> Option<Duration> {
    if let Some(mut secs) = self.secs.checked_sub(rhs.secs) {
      let millis = if self.millis >= rhs.millis {
        self.millis - rhs.millis
      } else if let Some(sub_secs) = secs.checked_sub(1) {
        secs = sub_secs;
        self.millis + MILLIS_PER_SEC - rhs.millis
      } else {
        return None;
      };
      Some(Duration { secs, millis })
    } else {
      None
    }
  }

  /// Saturating `Duration` subtraction. Computes `self - other`, returning [`Duration::ZERO`]
  /// if the result would be negative or if overflow occurred.
  ///
  pub const fn saturating_sub(self, rhs: Duration) -> Duration {
    match self.checked_sub(rhs) {
      Some(res) => res,
      None => Duration::ZERO,
    }
  }

  /// Checked `Duration` multiplication. Computes `self * other`, returning
  /// [`None`] if overflow occurred.
  ///
  pub const fn checked_mul(self, rhs: u16) -> Option<Duration> {
    // Multiply milliseconds as u32, because it cannot overflow that way.
    let total_nanos = self.millis as u32 * rhs as u32;
    let extra_secs = total_nanos / (MILLIS_PER_SEC as u32);
    let millis = (total_nanos % (MILLIS_PER_SEC as u32)) as u16;
    if let Some(s) = self.secs.checked_mul(rhs as u32) {
      if let Some(secs) = s.checked_add(extra_secs) {
        return Some(Duration { secs, millis });
      }
    }
    None
  }

  /// Saturating `Duration` multiplication. Computes `self * other`, returning
  /// [`Duration::MAX`] if overflow occurred.
  ///
  pub const fn saturating_mul(self, rhs: u16) -> Duration {
    match self.checked_mul(rhs) {
      Some(res) => res,
      None => Duration::MAX,
    }
  }

  /// Checked `Duration` division. Computes `self / other`, returning [`None`]
  /// if `other == 0`.
  ///
  pub const fn checked_div(self, rhs: u16) -> Option<Duration> {
    if rhs != 0 {
      let secs = self.secs / (rhs as u32);
      let carry = self.secs - secs * (rhs as u32);
      let extra_millis = carry * (MILLIS_PER_SEC as u32) / (rhs as u32);
      let millis = self.millis / rhs + (extra_millis as u16);
      Some(Duration { secs, millis })
    } else {
      None
    }
  }
}

impl Add for Duration {
    type Output = Duration;

    fn add(self, rhs: Duration) -> Duration {
        panic_if_none!(self.checked_add(rhs), avr_oxide::oserror::OsError::Arithmetic)
    }
}
impl AddAssign for Duration {
    fn add_assign(&mut self, rhs: Duration) {
        *self = *self + rhs;
    }
}
impl Sub for Duration {
    type Output = Duration;

    fn sub(self, rhs: Duration) -> Duration {
        panic_if_none!(self.checked_sub(rhs), avr_oxide::oserror::OsError::Arithmetic)
    }
}
impl SubAssign for Duration {
    fn sub_assign(&mut self, rhs: Duration) {
        *self = *self - rhs;
    }
}
impl Mul<u16> for Duration {
    type Output = Duration;

    fn mul(self, rhs: u16) -> Duration {
        panic_if_none!(self.checked_mul(rhs), avr_oxide::oserror::OsError::Arithmetic)
    }
}
impl Mul<Duration> for u16 {
    type Output = Duration;

    fn mul(self, rhs: Duration) -> Duration {
        rhs * self
    }
}
impl MulAssign<u16> for Duration {
    fn mul_assign(&mut self, rhs: u16) {
        *self = *self * rhs;
    }
}
impl Div<u16> for Duration {
    type Output = Duration;

    fn div(self, rhs: u16) -> Duration {
        panic_if_none!(self.checked_div(rhs), avr_oxide::oserror::OsError::Arithmetic)
    }
}
impl Floored for Duration {
  fn floor() -> Self {
    Duration::ZERO
  }
}


// Tests =====================================================================
#[cfg(test)]
mod tests {
  use core::fmt::{Debug, Formatter};
  #[allow(unused_imports)]
  use super::*;


  #[test]
  fn duration_constructors() {
    assert_eq!(Duration::from_millis(0), Duration::ZERO);
    assert_eq!(Duration::from_secs(0), Duration::ZERO);
    assert_eq!(Duration::from_millis(MILLIS_PER_SEC as u32), Duration::from_secs(1));
    assert_eq!(Duration::from_secs(12).is_zero(), false);
    assert_eq!(Duration::from_secs(42).as_secs(), 42u32);
    assert_eq!(Duration::from_secs(42).as_millis(), 42000u64);
  }

  #[test]
  fn duration_arithmetic() {
    let mut first = Duration::from_secs(32);
    let second = Duration::from_secs(2);

    assert_eq!(first + second, Duration::from_secs(34));
    assert_eq!(first * 4u16, Duration::from_secs(128));
    assert_eq!(first / 2u16, Duration::from_secs(16));

    assert!(first > second);
    assert!(second < first);
    assert!(first != second);

    first += second;
    assert_eq!(first, Duration::from_secs(34));
  }

  #[test]
  fn test_32bit_increment(){
    let mut timestamp = Duration::from_secs(0);
    let one = Duration::from_secs(1);

    for i in 1..u32::MAX {
      timestamp += one;

      assert_eq!(timestamp.as_secs(), i);
    }
  }
}