tictoc 0.1.4

A time duration profiling library.
Documentation
#[cfg(all(feature = "localtime"))]
use chrono::{DateTime, Duration, Local, Utc};
#[cfg(not(feature = "localtime"))]
use chrono::{DateTime, Duration, Utc};
use std::{collections::HashMap, fmt};

const MARK: &'static str = "maxi_el_amor_de_mi_vida";

#[derive(Debug)]
pub enum TicTocError {
    TimerAlreadyExists,
    TimerNotExists,
    TimerUpdateError,
    TimerResultError,
}

impl fmt::Display for TicTocError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            &Self::TimerAlreadyExists => write!(f, "A timer with default key mark already exists."),
            &Self::TimerNotExists => write!(f, "Timer does not exists."),
            &Self::TimerUpdateError => write!(f, "Error updating timer."),
            &Self::TimerResultError => write!(f, "Cannot resolve Timer result."),
        }
    }
}

pub enum TimeUnits {
    NanoSeconds,
    MicroSeconds,
    MilliSeconds,
    Seconds,
    Minutes,
    Hours,
    Days,
    Weeks,
}

#[derive(Debug, Clone, Copy)]
struct Timer {
    start: DateTime<Utc>,
    end: Option<DateTime<Utc>>,
    diff: Option<Duration>,
    #[cfg(any(feature = "localtime"))]
    local_start: DateTime<Local>,
    #[cfg(any(feature = "localtime"))]
    local_end: Option<DateTime<Local>>,
    #[cfg(any(feature = "localtime"))]
    local_diff: Option<Duration>,
}

impl Timer {
    #[cfg(not(feature = "localtime"))]
    fn new() -> Self {
        Self {
            start: Utc::now(),
            end: None,
            diff: None,
        }
    }

    #[cfg(any(feature = "localtime"))]
    fn new() -> Self {
        Self {
            start: Utc::now(),
            end: None,
            diff: None,
            local_start: Local::now(),
            local_end: None,
            local_diff: None,
        }
    }

    #[cfg(not(feature = "localtime"))]
    fn _finish(&self) -> Self {
        Self {
            end: Some(Utc::now()),
            ..*self
        }
    }

    #[cfg(any(feature = "localtime"))]
    fn _finish(&self) -> Self {
        Self {
            end: Some(Utc::now()),
            local_end: Some(Local::now()),
            ..*self
        }
    }

    #[cfg(not(feature = "localtime"))]
    fn _diff(&self) -> Self {
        Self {
            diff: Some(self.end.unwrap().time() - self.start.time()),
            ..*self
        }
    }

    #[cfg(any(feature = "localtime"))]
    fn _diff(&self) -> Self {
        Self {
            diff: Some(self.end.unwrap().time() - self.start.time()),
            local_diff: Some(self.local_end.unwrap().time() - self.local_start.time()),
            ..*self
        }
    }

    #[cfg(not(feature = "localtime"))]
    fn finish(self) -> Self {
        self._finish()._diff()
    }

    #[cfg(any(feature = "localtime"))]
    fn finish(self) -> Self {
        self._finish()._diff()
    }

    fn get_duration(&self) -> Duration {
        self.diff.unwrap()
    }
    #[cfg(any(feature = "localtime"))]
    fn get_local_duration(&self) -> Duration {
        self.local_diff.unwrap()
    }
}

#[derive(Debug, Clone)]
pub struct TicToc<'a> {
    timers: HashMap<&'a str, Timer>,
}

impl<'a> TicToc<'a> {
    pub fn new() -> Self {
        Self {
            timers: HashMap::default(),
        }
    }

    fn _get_timer(&self, key: Option<&'a str>) -> Result<Timer, TicTocError> {
        let key = key.unwrap_or(MARK);
        match self.timers.get(key) {
            Some(t) => Ok(*t),
            None => Err(TicTocError::TimerNotExists),
        }
    }

    fn _update_timer(&mut self, key: Option<&'a str>, timer: Timer) -> Result<(), TicTocError> {
        let key = key.unwrap_or(MARK);
        match self.timers.insert(key, timer) {
            Some(_) => Ok(()),
            None => Err(TicTocError::TimerUpdateError),
        }
    }

    #[cfg(not(feature = "localtime"))]
    pub fn tic(&mut self, mark: Option<&'a str>) -> Result<DateTime<Utc>, TicTocError> {
        let mark: &str = match mark.is_some() {
            true => match self.timers.contains_key(mark.unwrap()) {
                false => mark.unwrap(),
                true => return Err(TicTocError::TimerAlreadyExists),
            },
            false => match self.timers.contains_key(MARK) {
                false => MARK,
                true => return Err(TicTocError::TimerAlreadyExists),
            },
        };
        let timer = Timer::new();
        let now = &timer.start;
        self.timers.insert(mark, timer);
        Ok(*now)
    }

    #[cfg(any(feature = "localtime"))]
    pub fn tic(&mut self, mark: Option<&'a str>) -> Result<DateTime<Local>, TicTocError> {
        let mark: &str = match mark.is_some() {
            true => match self.timers.contains_key(mark.unwrap()) {
                false => mark.unwrap(),
                true => return Err(TicTocError::TimerAlreadyExists),
            },
            false => match self.timers.contains_key(MARK) {
                false => MARK,
                true => return Err(TicTocError::TimerAlreadyExists),
            },
        };
        let timer = Timer::new();
        let now = &timer.local_start;
        self.timers.insert(mark, timer);
        Ok(*now)
    }

    #[cfg(not(feature = "localtime"))]
    pub fn toc(&mut self, mark: Option<&'a str>) -> Result<DateTime<Utc>, TicTocError> {
        let timer = self._get_timer(mark)?.finish();
        let now = &timer.end;
        self._update_timer(mark, timer)?;
        match now {
            Some(n) => Ok(n.to_owned()),
            None => Err(TicTocError::TimerUpdateError),
        }
    }

    #[cfg(any(feature = "localtime"))]
    pub fn toc(&mut self, mark: Option<&'a str>) -> Result<DateTime<Local>, TicTocError> {
        let timer = self._get_timer(mark)?.finish();
        let now = &timer.local_end;
        self._update_timer(mark, timer)?;
        match now {
            Some(n) => Ok(n.to_owned()),
            None => Err(TicTocError::TimerUpdateError),
        }
    }

    pub fn time(
        &mut self,
        mark: Option<&'a str>,
        unit: Option<TimeUnits>,
    ) -> Result<i64, TicTocError> {
        let unit = unit.unwrap_or(TimeUnits::MilliSeconds);
        let duration = self._get_timer(mark)?.get_duration();
        let result: Option<i64> = match unit {
            TimeUnits::NanoSeconds => duration.num_nanoseconds(),
            TimeUnits::MicroSeconds => duration.num_microseconds(),
            TimeUnits::MilliSeconds => Some(duration.num_milliseconds()),
            TimeUnits::Seconds => Some(duration.num_seconds()),
            TimeUnits::Minutes => Some(duration.num_minutes()),
            TimeUnits::Hours => Some(duration.num_hours()),
            TimeUnits::Days => Some(duration.num_days()),
            TimeUnits::Weeks => Some(duration.num_weeks()),
        };
        match result {
            Some(r) => Ok(r),
            None => Err(TicTocError::TimerResultError),
        }
    }

    #[cfg(any(feature = "localtime"))]
    pub fn local_time(
        &mut self,
        mark: Option<&'a str>,
        unit: Option<TimeUnits>,
    ) -> Result<i64, TicTocError> {
        let unit = unit.unwrap_or(TimeUnits::MilliSeconds);
        let duration = self._get_timer(mark)?.get_local_duration();
        let result: Option<i64> = match unit {
            TimeUnits::NanoSeconds => duration.num_nanoseconds(),
            TimeUnits::MicroSeconds => duration.num_microseconds(),
            TimeUnits::MilliSeconds => Some(duration.num_milliseconds()),
            TimeUnits::Seconds => Some(duration.num_seconds()),
            TimeUnits::Minutes => Some(duration.num_minutes()),
            TimeUnits::Hours => Some(duration.num_hours()),
            TimeUnits::Days => Some(duration.num_days()),
            TimeUnits::Weeks => Some(duration.num_weeks()),
        };
        match result {
            Some(r) => Ok(r),
            None => Err(TicTocError::TimerResultError),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[cfg(all(feature = "localtime"))]
    use chrono::{DateTime, Local};
    #[cfg(not(feature = "localtime"))]
    use chrono::{DateTime, Utc};

    #[test]
    fn default_timer_works() {
        let mut tictoc = TicToc::new();
        assert!(tictoc.tic(None).is_ok());
    }

    #[test]
    fn named_timer_works() {
        let mark = Some("test1");
        let mut tictoc = TicToc::new();
        assert!(tictoc.tic(mark).is_ok());
    }

    #[test]
    fn multiple_named_timers_works() {
        let mark = Some("test1");
        let mark2 = Some("test2");
        let mut tictoc = TicToc::new();
        let _ = tictoc.tic(mark).unwrap();
        assert!(tictoc.tic(mark2).is_ok());
    }

    #[test]
    fn timer_does_exists_works() {
        let mark = Some("test1");
        let mut tictoc = TicToc::new();
        let _ = tictoc.tic(mark).unwrap();
        assert!(tictoc.toc(mark).is_ok());
    }

    #[test]
    fn multiple_timer_with_default_and_named_works() {
        let mark = Some("test1");
        let mut tictoc = TicToc::new();
        let _ = tictoc.tic(None).unwrap();
        assert!(tictoc.tic(mark).is_ok());
    }

    #[test]
    fn result_comparison_works() {
        let mut tictoc = TicToc::new();
        let tic = tictoc.tic(None).unwrap();
        std::thread::sleep(std::time::Duration::from_millis(10));
        let toc = tictoc.toc(None).unwrap();
        let diff = tic - toc;
        assert_eq!(diff.num_milliseconds(), tictoc.time(None, None).unwrap());
    }

    #[test]
    fn multiple_result_comparison_works() {
        let mark = Some("test1");
        let mut tictoc = TicToc::new();
        let tic = tictoc.tic(None).unwrap();
        std::thread::sleep(std::time::Duration::from_millis(10));
        let toc = tictoc.toc(None).unwrap();
        let tic1 = tictoc.tic(mark).unwrap();
        std::thread::sleep(std::time::Duration::from_millis(10));
        let toc1 = tictoc.toc(mark).unwrap();
        let diff = tic - toc;
        let diff1 = tic1 - toc1;
        let result = tictoc.time(None, None).unwrap();
        let result1 = tictoc.time(mark, None).unwrap();
        assert_eq!(
            diff.num_milliseconds().eq(&result),
            diff1.num_milliseconds().eq(&result1)
        );
    }

    #[test]
    fn multiple_result_comparison_fail() {
        let mark = Some("test1");
        let mut tictoc = TicToc::new();

        let tic = tictoc.tic(None).unwrap();
        std::thread::sleep(std::time::Duration::from_millis(10));
        let toc = tictoc.toc(None).unwrap();
        let diff = tic - toc;

        let _ = tictoc.tic(mark).unwrap();
        std::thread::sleep(std::time::Duration::from_millis(10));
        #[cfg(not(feature = "localtime"))]
        let tic1: DateTime<Utc> = Utc::now();
        #[cfg(all(feature = "localtime"))]
        let tic1: DateTime<Local> = Local::now();
        std::thread::sleep(std::time::Duration::from_millis(10));
        let toc1 = tictoc.toc(mark).unwrap();
        let diff1 = tic1 - toc1;

        let result = tictoc.time(None, None).unwrap();
        let result1 = tictoc.time(mark, None).unwrap();
        assert_ne!(
            diff.num_milliseconds().eq(&result),
            diff1.num_milliseconds().eq(&result1)
        );
    }

    #[test]
    fn result_comparison_fail() {
        let mut tictoc = TicToc::new();
        let _ = tictoc.tic(None).unwrap();
        std::thread::sleep(std::time::Duration::from_millis(10));
        #[cfg(not(feature = "localtime"))]
        let tic: DateTime<Utc> = Utc::now();
        #[cfg(all(feature = "localtime"))]
        let tic: DateTime<Local> = Local::now();
        std::thread::sleep(std::time::Duration::from_millis(10));
        let toc = tictoc.toc(None).unwrap();
        let diff = tic - toc;
        assert_ne!(diff.num_milliseconds(), tictoc.time(None, None).unwrap());
    }

    #[test]
    fn timer_doesnt_exists_fail() {
        let mark = Some("test1");
        let mut tictoc = TicToc::new();
        assert!(tictoc.time(mark, None).is_err());
    }

    #[test]
    fn multiple_default_timers_fail() {
        let mut tictoc = TicToc::new();
        let _ = tictoc.tic(None).unwrap();
        assert!(tictoc.tic(None).is_err());
    }
    #[test]
    fn timer_already_exists_fails() {
        let mark = Some("test1");
        let mut tictoc = TicToc::new();
        let _ = tictoc.tic(mark).unwrap();
        assert!(tictoc.tic(mark).is_err());
    }
}