1use 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 );
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 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}