115 lines
2.9 KiB
Rust
115 lines
2.9 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
|
|
pub const MONTH_SLOTS_PER_YEAR: u32 = 12;
|
|
pub const PHASE_SLOTS_PER_MONTH: u32 = 28;
|
|
pub const TICKS_PER_PHASE: u32 = 180;
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
|
pub struct CalendarPoint {
|
|
pub year: u32,
|
|
pub month_slot: u32,
|
|
pub phase_slot: u32,
|
|
pub tick_slot: u32,
|
|
}
|
|
|
|
impl CalendarPoint {
|
|
pub fn validate(&self) -> Result<(), String> {
|
|
if self.month_slot >= MONTH_SLOTS_PER_YEAR {
|
|
return Err(format!(
|
|
"month_slot {} is out of range 0..{}",
|
|
self.month_slot,
|
|
MONTH_SLOTS_PER_YEAR - 1
|
|
));
|
|
}
|
|
if self.phase_slot >= PHASE_SLOTS_PER_MONTH {
|
|
return Err(format!(
|
|
"phase_slot {} is out of range 0..{}",
|
|
self.phase_slot,
|
|
PHASE_SLOTS_PER_MONTH - 1
|
|
));
|
|
}
|
|
if self.tick_slot >= TICKS_PER_PHASE {
|
|
return Err(format!(
|
|
"tick_slot {} is out of range 0..{}",
|
|
self.tick_slot,
|
|
TICKS_PER_PHASE - 1
|
|
));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn step_forward(&mut self) -> BoundaryEventKind {
|
|
self.tick_slot += 1;
|
|
if self.tick_slot < TICKS_PER_PHASE {
|
|
return BoundaryEventKind::Tick;
|
|
}
|
|
|
|
self.tick_slot = 0;
|
|
self.phase_slot += 1;
|
|
if self.phase_slot < PHASE_SLOTS_PER_MONTH {
|
|
return BoundaryEventKind::PhaseRollover;
|
|
}
|
|
|
|
self.phase_slot = 0;
|
|
self.month_slot += 1;
|
|
if self.month_slot < MONTH_SLOTS_PER_YEAR {
|
|
return BoundaryEventKind::MonthRollover;
|
|
}
|
|
|
|
self.month_slot = 0;
|
|
self.year += 1;
|
|
BoundaryEventKind::YearRollover
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum BoundaryEventKind {
|
|
Tick,
|
|
PhaseRollover,
|
|
MonthRollover,
|
|
YearRollover,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn validates_calendar_bounds() {
|
|
let point = CalendarPoint {
|
|
year: 1830,
|
|
month_slot: 0,
|
|
phase_slot: 0,
|
|
tick_slot: 0,
|
|
};
|
|
assert!(point.validate().is_ok());
|
|
|
|
let invalid = CalendarPoint {
|
|
month_slot: MONTH_SLOTS_PER_YEAR,
|
|
..point
|
|
};
|
|
assert!(invalid.validate().is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn steps_across_year_boundary() {
|
|
let mut point = CalendarPoint {
|
|
year: 1830,
|
|
month_slot: MONTH_SLOTS_PER_YEAR - 1,
|
|
phase_slot: PHASE_SLOTS_PER_MONTH - 1,
|
|
tick_slot: TICKS_PER_PHASE - 1,
|
|
};
|
|
|
|
let event = point.step_forward();
|
|
assert_eq!(event, BoundaryEventKind::YearRollover);
|
|
assert_eq!(
|
|
point,
|
|
CalendarPoint {
|
|
year: 1831,
|
|
month_slot: 0,
|
|
phase_slot: 0,
|
|
tick_slot: 0,
|
|
}
|
|
);
|
|
}
|
|
}
|