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;
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,
}
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
}
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
}
}
pub const fn saturating_add(self, rhs: Duration) -> Duration {
match self.checked_add(rhs) {
Some(res) => res,
None => Duration::MAX,
}
}
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
}
}
pub const fn saturating_sub(self, rhs: Duration) -> Duration {
match self.checked_sub(rhs) {
Some(res) => res,
None => Duration::ZERO,
}
}
pub const fn checked_mul(self, rhs: u16) -> Option<Duration> {
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
}
pub const fn saturating_mul(self, rhs: u16) -> Duration {
match self.checked_mul(rhs) {
Some(res) => res,
None => Duration::MAX,
}
}
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
}
}
#[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);
}
}
}