partiql-value 0.14.0

PartiQL Values
Documentation
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::fmt::{Debug, Formatter};
use std::hash::Hash;
use std::num::NonZeroU8;
use time::{Duration, UtcOffset};

#[derive(Hash, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum DateTime {
    Date(time::Date),
    Time(time::Time),
    TimeWithTz(time::Time, time::UtcOffset),
    Timestamp(time::PrimitiveDateTime),
    TimestampWithTz(time::OffsetDateTime),
}

impl DateTime {
    #[must_use]
    pub fn from_system_now_utc() -> Self {
        DateTime::TimestampWithTz(time::OffsetDateTime::now_utc())
    }

    #[must_use]
    pub fn from_hms(hour: u8, minute: u8, second: u8) -> Self {
        DateTime::Time(time::Time::from_hms(hour, minute, second).expect("valid time value"))
    }

    #[must_use]
    pub fn from_hms_nano(hour: u8, minute: u8, second: u8, nanosecond: u32) -> Self {
        Self::from_hms_nano_offset(hour, minute, second, nanosecond, None)
    }

    #[must_use]
    pub fn from_hms_nano_tz(
        hour: u8,
        minute: u8,
        second: u8,
        nanosecond: u32,
        tz_hours: Option<i8>,
        tz_minutes: Option<i8>,
    ) -> Self {
        let offset = match (tz_hours, tz_minutes) {
            (Some(h), Some(m)) => Some(UtcOffset::from_hms(h, m, 0).expect("valid offset")),
            (None, Some(m)) => Some(UtcOffset::from_hms(0, m, 0).expect("valid offset")),
            (Some(h), None) => Some(UtcOffset::from_hms(h, 0, 0).expect("valid offset")),
            _ => None,
        };

        Self::from_hms_nano_offset(hour, minute, second, nanosecond, offset)
    }

    #[must_use]
    pub fn from_ymd(year: i32, month: NonZeroU8, day: u8) -> Self {
        let month: time::Month = month.get().try_into().expect("valid month");
        let date = time::Date::from_calendar_date(year, month, day).expect("valid ymd");
        DateTime::Date(date)
    }

    #[allow(clippy::too_many_arguments)]
    #[must_use]
    pub fn from_ymdhms_nano_offset_minutes(
        year: i32,
        month: NonZeroU8,
        day: u8,
        hour: u8,
        minute: u8,
        second: u8,
        nanosecond: u32,
        offset: Option<i32>,
    ) -> Self {
        let month: time::Month = month.get().try_into().expect("valid month");
        let date = time::Date::from_calendar_date(year, month, day).expect("valid ymd");
        let time = time_from_hms_nano(hour, minute, second, nanosecond);
        match offset {
            None => DateTime::Timestamp(date.with_time(time)),
            Some(o) => {
                let offset = UtcOffset::from_whole_seconds(o * 60).expect("offset in range");
                let date = date.with_time(time).assume_offset(offset);
                DateTime::TimestampWithTz(date)
            }
        }
    }

    fn from_hms_nano_offset(
        hour: u8,
        minute: u8,
        second: u8,
        nanosecond: u32,
        offset: Option<UtcOffset>,
    ) -> Self {
        let time = time_from_hms_nano(hour, minute, second, nanosecond);
        match offset {
            Some(offset) => DateTime::TimeWithTz(time, offset),
            None => DateTime::Time(time),
        }
    }
}

fn time_from_hms_nano(hour: u8, minute: u8, second: u8, nanosecond: u32) -> time::Time {
    time::Time::from_hms_nano(hour, minute, second, nanosecond).expect("valid time value")
}

impl Debug for DateTime {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            DateTime::Date(d) => {
                write!(f, "DATE '{d:?}'")
            }
            DateTime::Time(t) => {
                write!(f, "TIME '{t:?}'")
            }
            DateTime::TimeWithTz(t, tz) => {
                write!(f, "TIME WITH TIME ZONE '{t:?} {tz:?}'")
            }
            DateTime::Timestamp(dt) => {
                write!(f, "TIMESTAMP '{dt:?}'")
            }
            DateTime::TimestampWithTz(dt) => {
                write!(f, "TIMESTAMP WITH TIME ZONE '{dt:?}'")
            }
        }
    }
}

impl PartialOrd for DateTime {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for DateTime {
    #[inline]
    fn cmp(&self, other: &Self) -> Ordering {
        match (self, other) {
            (DateTime::Date(l), DateTime::Date(r)) => l.cmp(r),
            (DateTime::Date(_), _) => Ordering::Less,
            (_, DateTime::Date(_)) => Ordering::Greater,

            (DateTime::Time(l), DateTime::Time(r)) => l.cmp(r),
            (DateTime::Time(_), _) => Ordering::Less,
            (_, DateTime::Time(_)) => Ordering::Greater,

            (DateTime::TimeWithTz(l, lo), DateTime::TimeWithTz(r, ro)) => {
                let lod = Duration::new(i64::from(lo.whole_seconds()), 0);
                let rod = Duration::new(i64::from(ro.whole_seconds()), 0);
                let l_adjusted = *l + lod;
                let r_adjusted = *r + rod;
                l_adjusted.cmp(&r_adjusted)
            }
            (DateTime::TimeWithTz(_, _), _) => Ordering::Less,
            (_, DateTime::TimeWithTz(_, _)) => Ordering::Greater,

            (DateTime::Timestamp(l), DateTime::Timestamp(r)) => l.cmp(r),
            (DateTime::Timestamp(_), _) => Ordering::Less,
            (_, DateTime::Timestamp(_)) => Ordering::Greater,

            (DateTime::TimestampWithTz(l), DateTime::TimestampWithTz(r)) => l.cmp(r),
        }
    }
}