mod date;
mod duration;
mod span;
mod zone;
pub use self::date::Date;
pub use self::duration::Duration;
pub use self::span::Span;
pub use self::zone::{Zone, LOCAL, UTC};
use crate::prelude::*;
use chrono::{TimeZone, Timelike};
#[derive(Clone, Copy)]
pub struct Time {
inner: chrono::DateTime<chrono::Utc>,
zone: Zone,
}
macro_rules! in_zone {
($self:tt, |$var:ident| $expr:expr) => {
match $self.zone {
Zone::Local => {
let $var = $self.inner.with_timezone(&chrono::Local);
$expr
}
Zone::Tz(tz) => {
let $var = $self.inner.with_timezone(&tz);
$expr
}
}
};
}
impl Time {
pub const fn max_value() -> Time {
Time { inner: chrono::MAX_DATETIME, zone: LOCAL }
}
pub const fn min_value() -> Time {
Time { inner: chrono::MIN_DATETIME, zone: LOCAL }
}
pub fn now() -> Time {
Time { inner: chrono::Utc::now(), zone: LOCAL }
}
pub fn from_unix_ms(timestamp: i64) -> Self {
Self { inner: chrono::Utc.timestamp_millis(timestamp), zone: Zone::Local }
}
pub fn as_rfc3339(&self) -> impl Display {
match self.zone == UTC {
true => self.format("%FT%T%.fZ"),
false => self.format("%FT%T%.f%:z"),
}
}
pub fn date(&self) -> Date {
in_zone!(self, |t| t.date().naive_local().into())
}
pub fn elapsed(&self) -> Duration {
Self::now() - *self
}
pub fn format<'a>(&self, fmt: &'a str) -> impl Display + 'a {
in_zone!(self, |t| t.format(fmt))
}
pub fn hms(&self) -> (usize, usize, usize) {
in_zone!(self, |t| (t.hour() as usize, t.minute() as usize, t.second() as usize))
}
pub fn hour(&self) -> usize {
in_zone!(self, |t| t.hour() as usize)
}
pub fn minute(&self) -> usize {
in_zone!(self, |t| t.minute() as usize)
}
pub fn second(&self) -> usize {
in_zone!(self, |t| t.second() as usize)
}
pub fn start_of_day(&self) -> Time {
Time {
inner: in_zone!(self, |t| t.date().and_hms(0, 0, 0).with_timezone(&chrono::Utc)),
zone: self.zone,
}
}
pub fn start_of_hour(&self) -> Time {
Time {
inner: in_zone!(self, |t| { t.date().and_hms(t.hour(), 0, 0).with_timezone(&chrono::Utc) }),
zone: self.zone,
}
}
pub const fn to_local(&self) -> Self {
self.to_zone(LOCAL)
}
pub const fn to_utc(&self) -> Self {
self.to_zone(UTC)
}
pub const fn to_zone(&self, zone: Zone) -> Self {
Self { inner: self.inner, zone }
}
#[cfg(feature = "postgres")]
fn to_naive(&self) -> chrono::NaiveDateTime {
in_zone!(self, |t| t.naive_local())
}
}
impl PartialEq for Time {
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner
}
}
impl Add<Duration> for Time {
type Output = Self;
fn add(self, rhs: Duration) -> Self::Output {
let rhs: chrono::Duration = rhs.into();
Self { inner: self.inner + rhs, zone: self.zone }
}
}
impl Debug for Time {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "\"{}\"", self.format("%+"))
}
}
impl Display for Time {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.as_rfc3339().fmt(f)
}
}
impl Eq for Time {}
impl Hash for Time {
fn hash<H: Hasher>(&self, state: &mut H) {
self.inner.hash(state)
}
}
impl Ord for Time {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.inner.cmp(&other.inner)
}
}
impl PartialOrd for Time {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
self.inner.partial_cmp(&other.inner)
}
}
impl Sub<Duration> for Time {
type Output = Self;
fn sub(self, rhs: Duration) -> Self::Output {
let rhs: chrono::Duration = rhs.into();
Self { inner: self.inner - rhs, zone: self.zone }
}
}
impl Sub<Time> for Time {
type Output = Duration;
fn sub(self, rhs: Time) -> Self::Output {
(self.inner - rhs.inner).into()
}
}
cfg_if! {
if #[cfg(feature = "postgres")] {
use postgres_types as pg;
impl<'a> pg::FromSql<'a> for Time {
fn from_sql(ty: &pg::Type, raw: &'a [u8]) -> Result<Self, Box<dyn Error + Sync + Send>>{
Ok(Self { inner: pg::FromSql::from_sql(ty, raw)?, zone: Zone::Local })
}
fn accepts(ty: &pg::Type) -> bool {
ty.oid() == pg::Type::TIMESTAMPTZ.oid()
}
}
impl pg::ToSql for Time {
fn to_sql(&self, ty: &pg::Type, out: &mut bytes::BytesMut) -> Result<pg::IsNull, Box<dyn Error + Sync + Send>>
where
Self: Sized,
{
if ty.oid() == pg::Type::TIMESTAMP.oid() {
self.to_naive().to_sql(ty, out)
} else {
self.inner.to_sql(ty, out)
}
}
fn accepts(ty: &pg::Type) -> bool
where
Self: Sized,
{
ty.oid() == pg::Type::TIMESTAMP.oid() || ty.oid() == pg::Type::TIMESTAMPTZ.oid()
}
fn to_sql_checked(&self, ty: &pg::Type, out: &mut bytes::BytesMut) -> Result<pg::IsNull, Box<dyn Error + Sync + Send>> {
if ty.oid() == pg::Type::TIMESTAMP.oid() {
self.to_naive().to_sql_checked(ty, out)
} else {
self.inner.to_sql_checked(ty, out)
}
}
}
}
}