You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
141 lines
3.7 KiB
141 lines
3.7 KiB
use crate::Date;
|
|
use chrono::{
|
|
DateTime, Duration, FixedOffset as Offset, Local, NaiveDateTime, NaiveTime, TimeZone, Timelike,
|
|
Utc,
|
|
};
|
|
use std::{cmp::Ordering, ops::Sub};
|
|
|
|
/// A convenience wrapper around [DateTime][t] with fixed timezone
|
|
///
|
|
/// [t]: chrono::DateTime
|
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
pub struct Time {
|
|
inner: DateTime<Offset>,
|
|
}
|
|
|
|
impl From<DateTime<Offset>> for Time {
|
|
fn from(inner: DateTime<Offset>) -> Self {
|
|
Self { inner }
|
|
}
|
|
}
|
|
|
|
impl<'t> Sub for &'t Time {
|
|
type Output = Duration;
|
|
|
|
fn sub(self, o: &'t Time) -> Self::Output {
|
|
self.inner - o.inner
|
|
}
|
|
}
|
|
|
|
impl ToString for Time {
|
|
fn to_string(&self) -> String {
|
|
format!("{}", self.inner.format("%Y-%m-%d %H:%M:%S%:z"))
|
|
}
|
|
}
|
|
|
|
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()
|
|
.with_second(0)
|
|
.and_then(|t| t.with_nanosecond(0))
|
|
.unwrap(),
|
|
),
|
|
}
|
|
}
|
|
|
|
/// Get the time that might be rounded to the next 15 minutes
|
|
pub(crate) fn rounded(r: bool) -> Self {
|
|
if r {
|
|
Time::now().round()
|
|
} else {
|
|
Time::now()
|
|
}
|
|
}
|
|
|
|
pub(crate) fn date(&self) -> chrono::Date<Offset> {
|
|
self.inner.date()
|
|
}
|
|
|
|
/// Check if a time stamp happened _after_ a date
|
|
pub fn after(&self, date: &Date) -> bool {
|
|
&Date::from(self.date()) > date
|
|
}
|
|
|
|
#[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<Offset> {
|
|
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);
|
|
}
|
|
|