use crate::runtime::clock::{Clock, CycleTime};
use std::time::{Duration, Instant};
use time::OffsetDateTime;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Interval {
pub start: OffsetDateTime,
pub end: OffsetDateTime,
}
impl Interval {
pub fn new(start: OffsetDateTime, end: OffsetDateTime) -> Self {
assert!(start < end, "Interval start must be before end");
Self { start, end }
}
pub fn duration(&self) -> Duration {
(self.end - self.start).try_into().unwrap_or(Duration::ZERO)
}
pub fn contains(&self, time: OffsetDateTime) -> bool {
time >= self.start && time <= self.end
}
}
pub struct HistoricalClock {
interval: Interval,
time_step: Duration,
current_time: OffsetDateTime,
baseline_instant: Instant,
exhausted: bool,
}
impl HistoricalClock {
pub fn new(interval: Interval) -> Self {
Self::with_time_step(interval, Duration::from_micros(100))
}
pub fn with_time_step(interval: Interval, time_step: Duration) -> Self {
let duration_since_epoch = interval.start - OffsetDateTime::UNIX_EPOCH;
let baseline_instant = Instant::now() - duration_since_epoch;
Self {
current_time: interval.start,
interval,
time_step,
baseline_instant,
exhausted: false,
}
}
pub fn is_exhausted(&self) -> bool {
self.exhausted
}
pub fn current_time(&self) -> OffsetDateTime {
self.current_time
}
pub fn interval(&self) -> &Interval {
&self.interval
}
pub fn time_step(&self) -> Duration {
self.time_step
}
pub fn reset(&mut self) {
self.current_time = self.interval.start;
self.exhausted = false;
}
fn current_instant(&self) -> Instant {
let elapsed_in_interval = (self.current_time - self.interval.start)
.try_into()
.unwrap_or(Duration::ZERO);
self.baseline_instant + elapsed_in_interval
}
fn try_advance(&mut self) {
if self.exhausted {
return;
} else if self.current_time == self.interval.end {
self.exhausted = true;
return;
}
let next_time = self.current_time + self.time_step;
if next_time >= self.interval.end {
self.current_time = self.interval.end;
} else {
self.current_time = next_time;
}
}
}
impl Clock for HistoricalClock {
fn cycle_time(&mut self) -> CycleTime {
let result = CycleTime::new(self.current_instant(), self.current_time);
self.try_advance();
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_interval_creation() {
let start = OffsetDateTime::UNIX_EPOCH;
let end = OffsetDateTime::UNIX_EPOCH + Duration::from_secs(3600);
let interval = Interval::new(start, end);
assert_eq!(interval.start, start);
assert_eq!(interval.end, end);
assert_eq!(interval.duration(), Duration::from_secs(3600));
}
#[test]
#[should_panic(expected = "Interval start must be before end")]
fn test_interval_invalid_order() {
let start = OffsetDateTime::UNIX_EPOCH + Duration::from_secs(100);
let end = OffsetDateTime::UNIX_EPOCH;
Interval::new(start, end);
}
#[test]
fn test_interval_contains() {
let start = OffsetDateTime::UNIX_EPOCH;
let end = OffsetDateTime::UNIX_EPOCH + Duration::from_secs(100);
let interval = Interval::new(start, end);
assert!(interval.contains(start));
assert!(interval.contains(start + Duration::from_secs(50)));
assert!(interval.contains(end));
assert!(!interval.contains(end + Duration::from_secs(1)));
}
#[test]
fn test_historical_clock_creation() {
let start = OffsetDateTime::UNIX_EPOCH;
let end = OffsetDateTime::UNIX_EPOCH + Duration::from_secs(100);
let interval = Interval::new(start, end);
let clock = HistoricalClock::new(interval.clone());
assert_eq!(clock.interval(), &interval);
assert_eq!(clock.time_step(), Duration::from_micros(100));
assert_eq!(clock.current_time(), start);
assert!(!clock.is_exhausted());
}
#[test]
fn test_historical_clock_advance() {
let start = OffsetDateTime::UNIX_EPOCH;
let end = OffsetDateTime::UNIX_EPOCH + Duration::from_millis(1); let interval = Interval::new(start, end);
let mut clock = HistoricalClock::with_time_step(interval, Duration::from_micros(100));
let time1 = clock.cycle_time();
assert_eq!(time1.unix_time, start);
assert!(!clock.is_exhausted());
let time2 = clock.cycle_time();
assert_eq!(time2.unix_time, start + Duration::from_micros(100));
let mut call_count = 2;
while !clock.is_exhausted() && call_count < 100 {
clock.cycle_time();
call_count += 1;
}
assert!(clock.is_exhausted());
assert!(call_count < 100); }
#[test]
fn test_historical_clock_instant_consistency() {
let start = OffsetDateTime::UNIX_EPOCH;
let end = OffsetDateTime::UNIX_EPOCH + Duration::from_secs(1);
let interval = Interval::new(start, end);
let step = Duration::from_millis(100);
let mut clock = HistoricalClock::with_time_step(interval, step);
let time1 = clock.cycle_time();
let time2 = clock.cycle_time();
assert_eq!(time2.instant.duration_since(time1.instant), step);
assert_eq!(time2.unix_time - time1.unix_time, step);
}
#[test]
fn test_historical_clock_reset() {
let start = OffsetDateTime::UNIX_EPOCH;
let end = OffsetDateTime::UNIX_EPOCH + Duration::from_secs(1);
let interval = Interval::new(start, end);
let mut clock = HistoricalClock::new(interval);
clock.cycle_time();
clock.cycle_time();
clock.cycle_time();
assert_ne!(clock.current_time(), start);
clock.reset();
assert_eq!(clock.current_time(), start);
assert!(!clock.is_exhausted());
}
#[test]
fn test_historical_clock_exhaustion() {
let start = OffsetDateTime::UNIX_EPOCH;
let end = OffsetDateTime::UNIX_EPOCH + Duration::from_micros(250); let interval = Interval::new(start, end);
let step = Duration::from_micros(100);
let mut clock = HistoricalClock::with_time_step(interval, step);
let time1 = clock.cycle_time(); assert!(!clock.is_exhausted());
assert_eq!(time1.unix_time, start);
let time2 = clock.cycle_time(); assert!(!clock.is_exhausted());
assert_eq!(time2.unix_time, start + Duration::from_micros(100));
let time3 = clock.cycle_time(); assert!(!clock.is_exhausted()); assert_eq!(time3.unix_time, start + Duration::from_micros(200));
let time3 = clock.cycle_time();
assert!(clock.is_exhausted()); assert_eq!(time3.unix_time, start + Duration::from_micros(250)); }
#[test]
fn test_exhausted_clock_no_further_advance() {
let start = OffsetDateTime::UNIX_EPOCH;
let end = OffsetDateTime::UNIX_EPOCH + Duration::from_micros(100);
let interval = Interval::new(start, end);
let step = Duration::from_micros(200);
let mut clock = HistoricalClock::with_time_step(interval, step);
let time1 = clock.cycle_time();
assert!(!clock.is_exhausted());
let time2 = clock.cycle_time();
assert_ne!(time1.unix_time, time2.unix_time);
assert_ne!(time1.instant, time2.instant);
let time3 = clock.cycle_time();
assert_eq!(time2.unix_time, time3.unix_time);
assert_eq!(time2.instant, time3.instant);
}
}