use crate::{AvroValue, JsonValue, error::Error};
use chrono::{Local, NaiveTime, Timelike, format::ParseError};
use serde::{Deserialize, Serialize, Serializer};
use std::{
fmt,
ops::{Add, AddAssign, Sub, SubAssign},
str::FromStr,
time::Duration,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deserialize)]
pub struct Time(NaiveTime);
impl Time {
#[inline]
pub fn try_new(hour: u32, minute: u32, second: u32) -> Result<Self, Error> {
NaiveTime::from_hms_opt(hour, minute, second)
.map(Self)
.ok_or_else(|| {
let message = format!(
"fail to create a time from hour: `{hour}`, minute: `{minute}`, second: `{second}`"
);
Error::new(message)
})
}
#[inline]
pub fn now() -> Self {
Self(Local::now().time())
}
#[inline]
pub fn midnight() -> Self {
Self(NaiveTime::default())
}
#[inline]
pub fn num_secs_from_midnight(&self) -> u32 {
self.0.num_seconds_from_midnight()
}
#[inline]
pub fn num_millis_from_midnight(&self) -> u32 {
self.0.num_seconds_from_midnight() * 1000 + self.0.nanosecond() / 1_000_000
}
#[inline]
pub fn num_micros_from_midnight(&self) -> u32 {
self.0.num_seconds_from_midnight() * 1_000_000 + self.0.nanosecond() / 1000
}
#[inline]
pub fn format(&self, fmt: &str) -> String {
format!("{}", self.0.format(fmt))
}
#[inline]
pub fn duration_since(&self, earlier: Time) -> Duration {
(self.0 - earlier.0).to_std().unwrap_or_default()
}
#[inline]
pub fn span_between(&self, other: Time) -> Duration {
let duration = if self > &other {
self.0 - other.0
} else {
other.0 - self.0
};
duration.to_std().unwrap_or_default()
}
#[inline]
pub fn span_between_now(&self) -> Duration {
self.span_between(Self::now())
}
#[inline]
pub fn span_before_now(&self) -> Option<Duration> {
let current = Self::now();
if self <= ¤t {
(current.0 - self.0).to_std().ok()
} else {
None
}
}
#[inline]
pub fn span_after_now(&self) -> Option<Duration> {
let current = Self::now();
if self >= ¤t {
(self.0 - current.0).to_std().ok()
} else {
None
}
}
#[inline]
pub fn hour(&self) -> u32 {
self.0.hour()
}
#[inline]
pub fn minute(&self) -> u32 {
self.0.minute()
}
#[inline]
pub fn second(&self) -> u32 {
self.0.second()
}
}
impl Default for Time {
#[inline]
fn default() -> Self {
Self::now()
}
}
impl fmt::Display for Time {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.format("%H:%M:%S%.f").fmt(f)
}
}
impl Serialize for Time {
#[inline]
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.to_string())
}
}
impl From<NaiveTime> for Time {
#[inline]
fn from(t: NaiveTime) -> Self {
Self(t)
}
}
impl From<Time> for NaiveTime {
#[inline]
fn from(t: Time) -> Self {
t.0
}
}
impl From<Time> for AvroValue {
#[inline]
fn from(t: Time) -> Self {
let micros = t.num_micros_from_midnight();
AvroValue::TimeMicros(micros.into())
}
}
impl From<Time> for JsonValue {
#[inline]
fn from(t: Time) -> Self {
JsonValue::String(t.to_string())
}
}
#[cfg(feature = "i18n")]
impl<'a> From<Time> for fluent::FluentValue<'a> {
#[inline]
fn from(t: Time) -> Self {
fluent::FluentValue::String(t.to_string().into())
}
}
impl FromStr for Time {
type Err = ParseError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse::<NaiveTime>().map(Self)
}
}
impl Add<Duration> for Time {
type Output = Self;
#[inline]
fn add(self, rhs: Duration) -> Self {
let duration = chrono::Duration::from_std(rhs).expect("Duration value is out of range");
Self(self.0 + duration)
}
}
impl AddAssign<Duration> for Time {
#[inline]
fn add_assign(&mut self, rhs: Duration) {
*self = *self + rhs;
}
}
impl Sub<Duration> for Time {
type Output = Self;
#[inline]
fn sub(self, rhs: Duration) -> Self {
let duration = chrono::Duration::from_std(rhs).expect("Duration value is out of range");
Self(self.0 - duration)
}
}
impl SubAssign<Duration> for Time {
#[inline]
fn sub_assign(&mut self, rhs: Duration) {
*self = *self - rhs;
}
}
#[cfg(feature = "sqlx")]
impl<DB> sqlx::Type<DB> for Time
where
DB: sqlx::Database,
NaiveTime: sqlx::Type<DB>,
{
#[inline]
fn type_info() -> <DB as sqlx::Database>::TypeInfo {
<NaiveTime as sqlx::Type<DB>>::type_info()
}
}
#[cfg(feature = "sqlx")]
impl<'r, DB> sqlx::Decode<'r, DB> for Time
where
DB: sqlx::Database,
NaiveTime: sqlx::Decode<'r, DB>,
{
#[inline]
fn decode(value: <DB as sqlx::Database>::ValueRef<'r>) -> Result<Self, crate::BoxError> {
<NaiveTime as sqlx::Decode<'r, DB>>::decode(value).map(|dt| dt.into())
}
}