diff --git a/apps/cassiopeia/src/time.rs b/apps/cassiopeia/src/time.rs new file mode 100644 index 00000000000..56804713033 --- /dev/null +++ b/apps/cassiopeia/src/time.rs @@ -0,0 +1,95 @@ +use chrono::{ + DateTime, FixedOffset as Offset, Local, NaiveDateTime, NaiveTime, TimeZone, Timelike, Utc, +}; + +/// A convenience wrapper around [DateTime][t] with fixed timezone +/// +/// [t]: chrono::DateTime +pub struct Time { + inner: DateTime, +} + +impl Time { + /// Get the current local time and pin it to a fixed Tz offset + pub fn now() -> Self { + let now = Local::now(); + Self { + inner: build_datetime(now.time()), + } + } + + #[cfg(test)] + pub(crate) fn fixed(hour: u32, min: u32, sec: u32) -> Self { + Self { + inner: build_datetime(NaiveTime::from_hms(hour, min, sec)), + } + } + + /// Return a new instance that is rounded to nearest 15 minutes + /// + /// It uses the internally provided offset to do rounding, meaning + /// that the timezone information will not change, even when + /// rounding values that were created in a different timezone. + pub fn round(&self) -> Self { + let naive = self.inner.time(); + + let (new_min, incr_hour) = match naive.minute() { + // 0-7 => (0, false) + m if m > 0 && m < 7 => (0, false), + // 7-22 => (15, false) + m if m >= 7 && m < 22 => (15, false), + // 22-37 => (30, false) + m if m >= 22 && m < 37 => (30, false), + // 37-52 => (45, false) + m if m >= 37 && m < 52 => (45, false), + // 52-59 => (0, true) + m if m >= 52 && m <= 59 => (0, true), + _ => unreachable!(), + }; + + let hour = naive.hour(); + let new = NaiveTime::from_hms(if incr_hour { hour + 1 } else { hour }, new_min, 0); + let offset = self.inner.offset(); + let date = self.inner.date(); + + Self { + inner: DateTime::from_utc(NaiveDateTime::new(date.naive_local(), new), *offset), + } + } + + pub fn hour(&self) -> u32 { + self.inner.hour() + } + + pub fn minute(&self) -> u32 { + self.inner.minute() + } + + pub fn second(&self) -> u32 { + self.inner.second() + } +} + +/// Build a DateTime with the current local fixed offset +fn build_datetime(nt: NaiveTime) -> DateTime { + let date = Utc::now().date().naive_local(); + let offset = Local.offset_from_utc_date(&date); + + DateTime::from_utc(NaiveDateTime::new(date, nt), offset) +} + +#[test] +fn simple() { + let t = Time::fixed(10, 44, 0); + let round = t.round(); + assert_eq!(round.minute(), 45); + + let t = Time::fixed(6, 8, 0); + let round = t.round(); + assert_eq!(round.minute(), 15); + + let t = Time::fixed(6, 55, 0); + let round = t.round(); + assert_eq!(round.minute(), 0); + assert_eq!(round.hour(), 7); +}