use core::fmt;
use linearize::{Linearize, LinearizeExt};
use std::{ops::Div, time::Duration};
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Linearize)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Resolution {
Day,
AmPm,
SixHour,
ThreeHour,
Hour,
ThirtyMinute,
FifteenMinute,
FiveMinute,
Minute,
ThirtySecond,
FifteenSecond,
FiveSecond,
Second,
FiveHundredMilli,
HundredMilli,
FiftyMilli,
TenMilli,
FiveMilli,
Millisecond,
}
impl fmt::Display for Resolution {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Resolution::Day => f.write_str("day"),
Resolution::AmPm => f.write_str("AM/PM"),
Resolution::SixHour => f.write_str("6h"),
Resolution::ThreeHour => f.write_str("3h"),
Resolution::Hour => f.write_str("hour"),
Resolution::ThirtyMinute => f.write_str("30m"),
Resolution::FifteenMinute => f.write_str("15m"),
Resolution::FiveMinute => f.write_str("5m"),
Resolution::Minute => f.write_str("minute"),
Resolution::ThirtySecond => f.write_str("30s"),
Resolution::FifteenSecond => f.write_str("15s"),
Resolution::FiveSecond => f.write_str("5s"),
Resolution::Second => f.write_str("second"),
Resolution::FiveHundredMilli => f.write_str("500ms"),
Resolution::HundredMilli => f.write_str("100ms"),
Resolution::FiftyMilli => f.write_str("50ms"),
Resolution::TenMilli => f.write_str("10ms"),
Resolution::FiveMilli => f.write_str("5ms"),
Resolution::Millisecond => f.write_str("millisecond"),
}
}
}
impl Resolution {
pub const fn width(self) -> std::time::Duration {
match self {
Resolution::Day => Duration::from_secs(24 * 60 * 60),
Resolution::AmPm => Duration::from_secs(12 * 60 * 60),
Resolution::SixHour => Duration::from_secs(6 * 60 * 60),
Resolution::ThreeHour => Duration::from_secs(3 * 60 * 60),
Resolution::Hour => Duration::from_secs(60 * 60),
Resolution::ThirtyMinute => Duration::from_secs(30 * 60),
Resolution::FifteenMinute => Duration::from_secs(15 * 60),
Resolution::FiveMinute => Duration::from_secs(5 * 60),
Resolution::Minute => Duration::from_secs(60),
Resolution::ThirtySecond => Duration::from_secs(30),
Resolution::FifteenSecond => Duration::from_secs(15),
Resolution::FiveSecond => Duration::from_secs(5),
Resolution::Second => Duration::from_secs(1),
Resolution::FiveHundredMilli => Duration::from_millis(500),
Resolution::HundredMilli => Duration::from_millis(100),
Resolution::FiftyMilli => Duration::from_millis(50),
Resolution::TenMilli => Duration::from_millis(10),
Resolution::FiveMilli => Duration::from_millis(5),
Resolution::Millisecond => Duration::from_millis(1),
}
}
}
impl From<Resolution> for std::time::Duration {
fn from(value: Resolution) -> Self {
value.width()
}
}
impl Resolution {
pub fn coarser(self) -> Option<Self> {
Resolution::from_linear(self.linearize().checked_sub(1)?)
}
pub fn finer(self) -> Option<Self> {
Resolution::from_linear(self.linearize().checked_add(1)?)
}
pub(crate) fn range(
from: Resolution,
to: Resolution,
) -> impl DoubleEndedIterator<Item = Resolution> {
let from = from.linearize();
let to = to.linearize();
Resolution::variants()
.skip(to + 1)
.take(from.saturating_sub(to))
.rev()
}
}
#[allow(clippy::suspicious_arithmetic_impl)]
impl Div for Resolution {
type Output = u32;
fn div(self, rhs: Self) -> Self::Output {
let mut ret = 1;
for res in Resolution::range(rhs, self) {
ret *= res.subdivision() as u32;
}
ret
}
}
impl Resolution {
pub(crate) fn subdivision(self) -> u8 {
match self {
Resolution::Day => 0,
Resolution::AmPm => 2,
Resolution::SixHour => 2,
Resolution::ThreeHour => 2,
Resolution::Hour => 3,
Resolution::ThirtyMinute => 2,
Resolution::FifteenMinute => 2,
Resolution::FiveMinute => 3,
Resolution::Minute => 5,
Resolution::ThirtySecond => 2,
Resolution::FifteenSecond => 2,
Resolution::FiveSecond => 3,
Resolution::Second => 5,
Resolution::FiveHundredMilli => 2,
Resolution::HundredMilli => 5,
Resolution::FiftyMilli => 2,
Resolution::TenMilli => 5,
Resolution::FiveMilli => 2,
Resolution::Millisecond => 5,
}
}
pub(crate) fn n_bits(self) -> u8 {
match self {
Resolution::Day => 0,
Resolution::AmPm => 1,
Resolution::SixHour => 1,
Resolution::ThreeHour => 1,
Resolution::Hour => 2,
Resolution::ThirtyMinute => 1,
Resolution::FifteenMinute => 1,
Resolution::FiveMinute => 2,
Resolution::Minute => 3,
Resolution::ThirtySecond => 1,
Resolution::FifteenSecond => 1,
Resolution::FiveSecond => 2,
Resolution::Second => 3,
Resolution::FiveHundredMilli => 1,
Resolution::HundredMilli => 3,
Resolution::FiftyMilli => 1,
Resolution::TenMilli => 3,
Resolution::FiveMilli => 1,
Resolution::Millisecond => 3,
}
}
pub(crate) fn trailing_zeros(self) -> u8 {
match self {
Resolution::Day => 31,
Resolution::AmPm => 30,
Resolution::SixHour => 29,
Resolution::ThreeHour => 28,
Resolution::Hour => 26,
Resolution::ThirtyMinute => 25,
Resolution::FifteenMinute => 24,
Resolution::FiveMinute => 22,
Resolution::Minute => 19,
Resolution::ThirtySecond => 18,
Resolution::FifteenSecond => 17,
Resolution::FiveSecond => 15,
Resolution::Second => 12,
Resolution::FiveHundredMilli => 11,
Resolution::HundredMilli => 8,
Resolution::FiftyMilli => 7,
Resolution::TenMilli => 4,
Resolution::FiveMilli => 3,
Resolution::Millisecond => 0,
}
}
pub(crate) fn from_trailing_zeros(x: u8) -> Self {
match x {
0 => Resolution::Millisecond,
3 => Resolution::FiveMilli,
4 => Resolution::TenMilli,
7 => Resolution::FiftyMilli,
8 => Resolution::HundredMilli,
11 => Resolution::FiveHundredMilli,
12 => Resolution::Second,
15 => Resolution::FiveSecond,
17 => Resolution::FifteenSecond,
18 => Resolution::ThirtySecond,
19 => Resolution::Minute,
22 => Resolution::FiveMinute,
24 => Resolution::FifteenMinute,
25 => Resolution::ThirtyMinute,
26 => Resolution::Hour,
28 => Resolution::ThreeHour,
29 => Resolution::SixHour,
30 => Resolution::AmPm,
31 => Resolution::Day,
_ => panic!(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_x_in_y() {
assert_eq!(Resolution::Minute / Resolution::Second, 60);
assert_eq!(Resolution::Hour / Resolution::Minute, 60);
assert_eq!(Resolution::Day / Resolution::Hour, 24);
}
#[test]
fn test_enough_bits() {
for res in Resolution::variants() {
let has = res.n_bits() as u32;
let required = if res.subdivision() == 0 {
0
} else if res.subdivision().is_power_of_two() {
(res.subdivision() as u32).ilog2()
} else {
(res.subdivision() as u32).ilog2() + 1
};
assert!(
has == required,
"{res:?}: {has} != log2({})={required}",
res.subdivision()
);
}
}
#[test]
fn test_mask() {
let mask =
|res: Resolution| -> u32 { !(u32::MAX << res.n_bits()) << (res.trailing_zeros() + 1) };
assert_eq!(mask(Resolution::Second), 0b1110_000000000000);
assert_eq!(mask(Resolution::FiveSecond), 0b110000_000000000000);
assert_eq!(mask(Resolution::FifteenSecond), 0b1000000_000000000000);
assert_eq!(mask(Resolution::ThirtySecond), 0b10000000_000000000000);
}
#[test]
fn test_range() {
assert_eq!(
Resolution::range(Resolution::Second, Resolution::Minute).collect::<Vec<_>>(),
vec![
Resolution::Second,
Resolution::FiveSecond,
Resolution::FifteenSecond,
Resolution::ThirtySecond,
]
);
}
#[test]
fn test_trailing_zeros() {
for res in Resolution::variants() {
assert_eq!(Resolution::from_trailing_zeros(res.trailing_zeros()), res)
}
}
#[test]
fn test_n_bits() {
for res in Resolution::variants() {
let n_bits = res.coarser().map_or(31, |x| x.trailing_zeros()) - res.trailing_zeros();
assert_eq!(res.n_bits(), n_bits, "{res:?}",)
}
}
#[test]
fn test_width() {
for (res1, res2) in Resolution::variants()
.rev()
.zip(Resolution::variants().rev().skip(1))
{
assert_eq!(
res1.width() * res1.subdivision() as u32,
res2.width(),
"{res1:?}"
)
}
}
}