pgx/datum/
time.rs

1/*
2Portions Copyright 2019-2021 ZomboDB, LLC.
3Portions Copyright 2021-2022 Technology Concepts & Design, Inc. <support@tcdi.com>
4
5All rights reserved.
6
7Use of this source code is governed by the MIT license that can be found in the LICENSE file.
8*/
9
10use crate::{pg_sys, FromDatum, FromTimeError, IntoDatum};
11use pgx_sql_entity_graph::metadata::{
12    ArgumentError, Returns, ReturnsError, SqlMapping, SqlTranslatable,
13};
14
15const MINS_PER_HOUR: u64 = 60;
16const SEC_PER_MIN: u64 = 60;
17pub(crate) const USECS_PER_SEC: u64 = 1_000_000;
18pub(crate) const USECS_PER_MINUTE: u64 = USECS_PER_SEC * SEC_PER_MIN;
19pub(crate) const USECS_PER_HOUR: u64 = USECS_PER_MINUTE * MINS_PER_HOUR;
20pub(crate) const USECS_PER_DAY: u64 = USECS_PER_HOUR * 24;
21
22#[derive(Debug, Clone, PartialEq)]
23#[repr(transparent)]
24pub struct Time(pub u64 /* Microseconds since midnight */);
25impl FromDatum for Time {
26    #[inline]
27    unsafe fn from_polymorphic_datum(
28        datum: pg_sys::Datum,
29        is_null: bool,
30        _typoid: pg_sys::Oid,
31    ) -> Option<Time> {
32        if is_null {
33            None
34        } else {
35            Some(Time(datum.value() as _))
36        }
37    }
38}
39
40impl IntoDatum for Time {
41    #[inline]
42    fn into_datum(self) -> Option<pg_sys::Datum> {
43        let datum = pg_sys::Datum::try_from(self.0).unwrap();
44
45        Some(datum)
46    }
47
48    fn type_oid() -> pg_sys::Oid {
49        pg_sys::TIMEOID
50    }
51}
52
53impl Time {
54    pub const ALLBALLS: Self = Time(0);
55
56    pub fn from_hms_micro(h: u8, m: u8, s: u8, micro: u32) -> Result<Time, FromTimeError> {
57        match (h, m, s, micro) {
58            (24, 0, 0, 0) => Ok(Time(u64::from(h) * USECS_PER_HOUR)),
59            (24.., _, _, _) => Err(FromTimeError::HoursOutOfBounds),
60            (_, 60.., _, _) => Err(FromTimeError::MinutesOutOfBounds),
61            (_, _, 60.., _) => Err(FromTimeError::SecondsOutOfBounds),
62            (0..=23, 0..=59, 0..=59, _) => {
63                let t = u64::from(h) * USECS_PER_HOUR
64                    + u64::from(m) * USECS_PER_MINUTE
65                    + u64::from(s) * USECS_PER_SEC
66                    + u64::from(micro);
67                if t > USECS_PER_DAY {
68                    Err(FromTimeError::MicrosOutOfBounds)
69                } else {
70                    Ok(Time(t))
71                }
72            }
73        }
74    }
75
76    /// To hours, minutes, seconds, and microseconds.
77    pub fn to_hms_micro(self) -> (u8, u8, u8, u32) {
78        let mut time = self.0;
79        let hour = time / USECS_PER_HOUR;
80        time -= hour * USECS_PER_HOUR;
81
82        let min = time / USECS_PER_MINUTE;
83        time -= min * USECS_PER_MINUTE;
84
85        let sec = time / USECS_PER_SEC;
86        time -= sec * USECS_PER_SEC;
87
88        let hour = u8::try_from(hour).unwrap();
89        let min = u8::try_from(min).unwrap();
90        let sec = u8::try_from(sec).unwrap();
91        let micro = u32::try_from(time).unwrap();
92        (hour, min, sec, micro)
93    }
94}
95
96#[cfg(feature = "time-crate")]
97mod with_time_crate {
98    impl TryFrom<time::Time> for crate::Time {
99        type Error = crate::FromTimeError;
100        fn try_from(t: time::Time) -> Result<crate::Time, Self::Error> {
101            let (h, m, s, micro) = t.as_hms_micro();
102            Self::from_hms_micro(h, m, s, micro)
103        }
104    }
105}
106
107impl serde::Serialize for Time {
108    fn serialize<S>(
109        &self,
110        serializer: S,
111    ) -> std::result::Result<<S as serde::Serializer>::Ok, <S as serde::Serializer>::Error>
112    where
113        S: serde::Serializer,
114    {
115        let cstr: Option<&core::ffi::CStr> = unsafe {
116            crate::direct_function_call(pg_sys::time_out, vec![self.clone().into_datum()])
117        };
118        serializer.serialize_str(cstr.and_then(|c| c.to_str().ok()).unwrap())
119    }
120}
121
122unsafe impl SqlTranslatable for Time {
123    fn argument_sql() -> Result<SqlMapping, ArgumentError> {
124        Ok(SqlMapping::literal("time"))
125    }
126    fn return_sql() -> Result<Returns, ReturnsError> {
127        Ok(Returns::One(SqlMapping::literal("time")))
128    }
129}