use std::{
ops::{Deref, DerefMut},
sync::{LazyLock, RwLock},
};
use chrono::{Local, NaiveDateTime, NaiveTime, TimeDelta, Timelike, Utc};
use serde::{Deserialize, Serialize};
use crate::{
BaseFormat, GetInner,
error::{ErrorContext, SpanError, TimeError},
span::Span,
};
pub(crate) static BASE_TIME_FORMAT: BaseFormat<&'static str> =
LazyLock::new(|| RwLock::new("%H:%M:%S"));
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Clone, Copy, Serialize, Deserialize)]
pub enum TimeUnit {
Hour,
Minute,
Second,
}
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Clone, Serialize, Deserialize)]
pub struct Time {
pub(crate) time: NaiveTime,
#[serde(skip)]
#[serde(default = "base_time_format")]
pub(crate) format: String,
}
fn base_time_format() -> String {
BASE_TIME_FORMAT.get().to_string()
}
impl Default for Time {
fn default() -> Self {
Self::midnight()
}
}
impl std::fmt::Display for Time {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.time.format(&self.format))
}
}
impl Deref for Time {
type Target = NaiveTime;
fn deref(&self) -> &Self::Target {
&self.time
}
}
impl DerefMut for Time {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.time
}
}
impl Time {
pub fn time(&self) -> NaiveTime {
self.time
}
pub fn midnight() -> Self {
let time = NaiveTime::from_hms_opt(0, 0, 0).expect("Error Time midnight");
Self {
time,
format: BASE_TIME_FORMAT.get().to_string(),
}
}
}
impl Span<TimeUnit, u32> for Time {
fn new(hour: u32, minute: u32, second: u32) -> Result<Self, SpanError> {
let Some(time) = NaiveTime::from_hms_opt(hour, minute, second) else {
return Err(SpanError::InvalidTime(hour, minute, second)).err_ctx(TimeError);
};
Ok(Self {
time,
format: BASE_TIME_FORMAT.get().to_string(),
})
}
fn format(mut self, format: impl ToString) -> Self {
self.format = format.to_string();
self
}
fn default_format(mut self) -> Self {
self.format = BASE_TIME_FORMAT.get().to_string();
self
}
fn update(&self, unit: TimeUnit, value: i32) -> Result<Self, SpanError> {
let delta_time = match unit {
TimeUnit::Hour => TimeDelta::new(value as i64 * 60 * 60, 0),
TimeUnit::Minute => TimeDelta::new(value as i64 * 60, 0),
TimeUnit::Second => TimeDelta::new(value as i64, 0),
};
match delta_time {
Some(delta_time) => Ok(Self {
time: self.time + delta_time,
format: self.format.clone(),
}),
None => Err(SpanError::InvalidUpdate(format!(
"Cannot Add/Remove {value} {unit:?} to/from {self}"
)))
.err_ctx(TimeError),
}
}
fn next(&self, unit: TimeUnit) -> Result<Self, SpanError> {
self.update(unit, 1)
}
fn matches(&self, unit: TimeUnit, value: u32) -> bool {
match unit {
TimeUnit::Hour => self.time.hour() == value,
TimeUnit::Minute => self.time.minute() == value,
TimeUnit::Second => self.time.second() == value,
}
}
fn now() -> Result<Self, SpanError> {
let time = Local::now();
Self::new(time.hour(), time.minute(), time.second())
}
fn elapsed(&self, lhs: &Self) -> TimeDelta {
self.time.signed_duration_since(lhs.time)
}
fn unit_elapsed(&self, rhs: &Self, unit: TimeUnit) -> Result<i64, SpanError> {
Ok(match unit {
TimeUnit::Hour => self.time.signed_duration_since(rhs.time).num_hours(),
TimeUnit::Minute => self.time.signed_duration_since(rhs.time).num_minutes(),
TimeUnit::Second => self.time.signed_duration_since(rhs.time).num_seconds(),
})
}
fn is_in_future(&self) -> Result<bool, SpanError> {
let now = Self::now()?;
Ok(self.time > now.time)
}
fn clear_unit(&self, unit: TimeUnit) -> Result<Self, SpanError> {
let time = match unit {
TimeUnit::Hour => self.time.with_hour(0).ok_or(SpanError::ClearUnit(
"Error while setting hour to 0".to_string(),
)),
TimeUnit::Minute => self.time.with_minute(0).ok_or(SpanError::ClearUnit(
"Error while setting minute to 0".to_string(),
)),
TimeUnit::Second => self.time.with_second(0).ok_or(SpanError::ClearUnit(
"Error while setting second to 0".to_string(),
)),
}
.err_ctx(TimeError)?;
Ok(Self {
time,
format: self.format.clone(),
})
}
fn get_format(&self) -> String {
self.format.clone()
}
fn deserialize_with_format<'de, D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
Self: TryFrom<(String, String)>,
{
#[derive(Deserialize)]
struct Visitor {
time: String,
format: String,
}
let visitor: Visitor = Deserialize::deserialize(deserializer)?;
Self::try_from((visitor.time, visitor.format))
.map_err(|_| serde::de::Error::custom("Invalid time"))
}
}
impl From<NaiveDateTime> for Time {
fn from(datetime: NaiveDateTime) -> Self {
Self {
time: datetime.time(),
format: BASE_TIME_FORMAT.get().to_string(),
}
}
}
impl From<NaiveTime> for Time {
fn from(time: NaiveTime) -> Self {
Self {
time,
format: BASE_TIME_FORMAT.get().to_string(),
}
}
}
impl TryFrom<(String, String)> for Time {
type Error = SpanError;
fn try_from((time, format): (String, String)) -> Result<Self, Self::Error> {
let time = NaiveTime::parse_from_str(&time, &format)
.map_err(SpanError::ParseFromStr)
.err_ctx(TimeError)?;
Ok(Self { time, format })
}
}
impl TryFrom<(&str, &str)> for Time {
type Error = SpanError;
fn try_from((time, format): (&str, &str)) -> Result<Self, Self::Error> {
let time = NaiveTime::parse_from_str(time, format)
.map_err(SpanError::ParseFromStr)
.err_ctx(TimeError)?;
Ok(Self {
time,
format: format.to_string(),
})
}
}
impl TryFrom<String> for Time {
type Error = SpanError;
fn try_from(time: String) -> Result<Self, Self::Error> {
let time = NaiveTime::parse_from_str(&time, BASE_TIME_FORMAT.get())
.map_err(SpanError::ParseFromStr)
.err_ctx(TimeError)?;
Self::new(time.hour(), time.minute(), time.second())
}
}
impl TryFrom<&str> for Time {
type Error = SpanError;
fn try_from(time: &str) -> Result<Self, Self::Error> {
let time = NaiveTime::parse_from_str(time, BASE_TIME_FORMAT.get())
.map_err(SpanError::ParseFromStr)
.err_ctx(TimeError)?;
Self::new(time.hour(), time.minute(), time.second())
}
}
impl TryFrom<chrono::DateTime<Utc>> for Time {
type Error = SpanError;
fn try_from(value: chrono::DateTime<Utc>) -> Result<Self, Self::Error> {
Ok(value.naive_utc().into())
}
}
impl TryFrom<&Time> for chrono::DateTime<Utc> {
type Error = SpanError;
fn try_from(value: &Time) -> Result<Self, Self::Error> {
let date = value.time;
match Utc::now()
.with_hour(date.hour())
.and_then(|utc| utc.with_minute(date.minute()))
.and_then(|utc| utc.with_second(date.second()))
{
Some(utc) => Ok(utc),
None => Err(SpanError::InvalidUtc).err_ctx(TimeError),
}
}
}
#[cfg(test)]
pub mod test {
use super::*;
#[test]
fn time_add_overflow() -> Result<(), SpanError> {
let time = Time::new(0, 0, 0)?;
let new_time = time.update(TimeUnit::Hour, i32::MIN)?;
assert_eq!(new_time.to_string(), "16:00:00".to_string());
Ok(())
}
#[test]
fn time_add_one_hour() -> Result<(), SpanError> {
let time = Time::new(0, 0, 0)?;
let new_time = time.update(TimeUnit::Hour, 1)?;
assert_eq!(new_time.to_string(), "01:00:00".to_string());
Ok(())
}
#[test]
fn time_remove_one_hour() -> Result<(), SpanError> {
let time = Time::new(0, 0, 0)?;
let new_time = time.update(TimeUnit::Hour, -1)?;
assert_eq!(new_time.to_string(), "23:00:00".to_string());
Ok(())
}
#[test]
fn time_add_one_minute() -> Result<(), SpanError> {
let time = Time::new(0, 0, 0)?;
let new_time = time.update(TimeUnit::Minute, 1)?;
assert_eq!(new_time.to_string(), "00:01:00".to_string());
Ok(())
}
#[test]
fn time_remove_one_minute() -> Result<(), SpanError> {
let time = Time::new(0, 0, 0)?;
let new_time = time.update(TimeUnit::Minute, -1)?;
assert_eq!(new_time.to_string(), "23:59:00".to_string());
Ok(())
}
#[test]
fn time_add_one_second() -> Result<(), SpanError> {
let time = Time::new(0, 0, 0)?;
let new_time = time.update(TimeUnit::Second, 1)?;
assert_eq!(new_time.to_string(), "00:00:01".to_string());
Ok(())
}
#[test]
fn time_remove_one_second() -> Result<(), SpanError> {
let time = Time::new(0, 0, 0)?;
let new_time = time.update(TimeUnit::Second, -1)?;
assert_eq!(new_time.to_string(), "23:59:59".to_string());
Ok(())
}
#[test]
fn time_serialize() -> Result<(), SpanError> {
let time = Time::new(12, 21, 46)?;
let Ok(serialized) = serde_json::to_string(&time) else {
panic!("Error while serializing time");
};
assert_eq!(serialized, "{\"time\":\"12:21:46\"}".to_string());
Ok(())
}
#[test]
fn time_deserialize() -> Result<(), SpanError> {
let serialized = "{\"time\":\"12:21:46\"}".to_string();
let Ok(time) = serde_json::from_str::<Time>(&serialized) else {
panic!("Error while deserializing time");
};
assert_eq!(time.to_string(), "12:21:46".to_string());
assert_eq!(time.format, BASE_TIME_FORMAT.get().to_string());
Ok(())
}
#[test]
fn time_serialize_format() -> Result<(), SpanError> {
let time = Time::new(12, 21, 46)?.format("T%H_%M_%S");
let Ok(serialized) = serde_json::to_string(&time) else {
panic!("Error while serializing time");
};
assert_eq!(serialized, "{\"time\":\"12:21:46\"}".to_string());
Ok(())
}
#[test]
fn time_deserialize_format() -> Result<(), SpanError> {
#[derive(Deserialize)]
struct CustomFmt(#[serde(deserialize_with = "Time::deserialize_with_format")] Time);
let serialized = "{\"time\":\"12.21.46\",\"format\":\"%H.%M.%S\"}".to_string();
let Ok(CustomFmt(time)) = serde_json::from_str::<CustomFmt>(&serialized) else {
panic!("Error while deserializing time");
};
assert_eq!(
time.format("T%H_%M_%S").to_string(),
"T12_21_46".to_string()
);
Ok(())
}
#[test]
fn time_serialize_in_struct() -> Result<(), SpanError> {
#[derive(Serialize)]
struct Test {
begin_at: Time,
}
let test = Test {
begin_at: Time::new(23, 10, 9)?,
};
let Ok(serialized) = serde_json::to_string(&test) else {
panic!("Error while serializing time");
};
assert_eq!(
serialized,
"{\"begin_at\":{\"time\":\"23:10:09\"}}".to_string()
);
Ok(())
}
#[test]
fn time_deserialize_in_struct() -> Result<(), SpanError> {
#[derive(Deserialize)]
struct Test {
begin_at: Time,
}
let serialized = "{\"begin_at\":{\"time\":\"23:10:09\"}}".to_string();
let Ok(test) = serde_json::from_str::<Test>(&serialized) else {
panic!("Error while deserializing time");
};
assert_eq!(test.begin_at.to_string(), "23:10:09".to_string());
assert_eq!(test.begin_at.format, BASE_TIME_FORMAT.get().to_string());
Ok(())
}
#[test]
fn time_default_equal_midnight() -> Result<(), SpanError> {
let time_built = Time::new(0, 0, 0)?;
let midnight = Time::midnight();
let default = Time::default();
assert_eq!(time_built.to_string(), midnight.to_string());
assert_eq!(time_built.format, midnight.format);
assert_eq!(time_built.to_string(), default.to_string());
assert_eq!(time_built.format, default.format);
assert_eq!(midnight.to_string(), default.to_string());
assert_eq!(midnight.format, default.format);
Ok(())
}
#[test]
fn next_second() -> Result<(), SpanError> {
let mut time = Time::new(4, 23, 12)?;
time = time.next(TimeUnit::Second)?;
assert_eq!(time.to_string(), "04:23:13".to_string());
Ok(())
}
#[test]
fn next_minute() -> Result<(), SpanError> {
let mut time = Time::new(11, 3, 22)?;
time = time.next(TimeUnit::Minute)?;
assert_eq!(time.to_string(), "11:04:22".to_string());
Ok(())
}
#[test]
fn next_hour_on_midnight() -> Result<(), SpanError> {
let mut time = Time::new(23, 59, 34)?;
time = time.next(TimeUnit::Hour)?;
assert_eq!(time.to_string(), "00:59:34".to_string());
Ok(())
}
#[test]
fn matches_every_unit_in_time() -> Result<(), SpanError> {
let time = Time::new(5, 23, 18)?;
assert!(time.matches(TimeUnit::Hour, 5));
assert!(time.matches(TimeUnit::Minute, 23));
assert!(time.matches(TimeUnit::Second, 18));
Ok(())
}
#[test]
fn elapsed_three_minute() -> Result<(), SpanError> {
let time = Time::new(0, 3, 0)?;
let lhs = Time::new(0, 0, 0)?;
assert_eq!(time.elapsed(&lhs), TimeDelta::try_minutes(3).unwrap());
Ok(())
}
#[test]
fn elapsed_seconds() -> Result<(), SpanError> {
let time = Time::new(1, 21, 0)?;
let lhs = Time::new(0, 0, 0)?;
assert_eq!(time.elapsed(&lhs), TimeDelta::try_seconds(4860).unwrap());
Ok(())
}
#[test]
fn elapsed_multiple_units() -> Result<(), SpanError> {
let time = Time::new(1, 1, 1)?;
let lhs = Time::new(0, 0, 0)?;
assert_eq!(
time.elapsed(&lhs),
TimeDelta::try_hours(1)
.unwrap()
.checked_add(&TimeDelta::try_minutes(1).unwrap())
.unwrap()
.checked_add(&TimeDelta::try_seconds(1).unwrap())
.unwrap()
);
Ok(())
}
#[test]
fn unit_elapsed() -> Result<(), SpanError> {
let time = Time::new(1, 34, 45)?;
let rhs = Time::new(0, 0, 0)?;
let hours_in_between = time.unit_elapsed(&rhs, TimeUnit::Hour)?;
let minutes_in_between = time.unit_elapsed(&rhs, TimeUnit::Minute)?;
let seconds_in_between = time.unit_elapsed(&rhs, TimeUnit::Second)?;
assert_eq!(hours_in_between, 1);
assert_eq!(minutes_in_between, hours_in_between * 60 + 34);
assert_eq!(seconds_in_between, minutes_in_between * 60 + 45);
Ok(())
}
#[test]
fn midnight() -> Result<(), SpanError> {
let time = Time::midnight();
assert_eq!(time.to_string(), "00:00:00".to_string());
Ok(())
}
}
#[cfg(all(feature = "time", feature = "datetime"))]
mod datetime_into_time {
use crate::GetInner;
impl From<crate::datetime::DateTime> for crate::time::Time {
fn from(value: crate::datetime::DateTime) -> Self {
let time = value.datetime().time();
Self {
time,
format: crate::time::BASE_TIME_FORMAT.get().to_string(),
}
}
}
#[cfg(test)]
mod test {
use crate::span::Span;
#[test]
fn datetime_into_time() -> Result<(), crate::error::SpanError> {
let datetime = crate::datetime::DateTime::new(2021, 10, 10)?.with_time(12, 34, 56)?;
let time = crate::time::Time::from(datetime);
assert_eq!(time.to_string(), "12:34:56".to_string());
Ok(())
}
#[test]
#[ignore]
fn datetime_into_time_wrong_format() -> Result<(), crate::error::SpanError> {
crate::builder::SpanBuilder::builder()
.datetime_format("%Y-%m-%d %H:%M:%S")
.time_format("%H_%M_%S")
.build();
let datetime = crate::datetime::DateTime::new(2021, 10, 10)?.with_time(12, 34, 56)?;
let time = crate::time::Time::from(datetime);
assert_eq!(time.to_string(), "12_34_56".to_string());
Ok(())
}
}
}