vortex_array/extension/datetime/
time.rs1use std::fmt;
5
6use jiff::Span;
7use vortex_error::VortexExpect;
8use vortex_error::VortexResult;
9use vortex_error::vortex_bail;
10use vortex_error::vortex_ensure;
11use vortex_error::vortex_err;
12use vortex_session::registry::CachedId;
13
14use crate::dtype::DType;
15use crate::dtype::Nullability;
16use crate::dtype::PType;
17use crate::dtype::extension::ExtDType;
18use crate::dtype::extension::ExtId;
19use crate::dtype::extension::ExtVTable;
20use crate::extension::datetime::TimeUnit;
21use crate::scalar::ScalarValue;
22
23#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
25pub struct Time;
26
27fn time_ptype(time_unit: &TimeUnit) -> Option<PType> {
28 Some(match time_unit {
29 TimeUnit::Nanoseconds | TimeUnit::Microseconds => PType::I64,
30 TimeUnit::Milliseconds | TimeUnit::Seconds => PType::I32,
31 TimeUnit::Days => return None,
32 })
33}
34
35impl Time {
36 pub fn try_new(time_unit: TimeUnit, nullability: Nullability) -> VortexResult<ExtDType<Self>> {
40 let ptype = time_ptype(&time_unit)
41 .ok_or_else(|| vortex_err!("Time type does not support time unit {}", time_unit))?;
42 ExtDType::try_new(time_unit, DType::Primitive(ptype, nullability))
43 }
44
45 pub fn new(time_unit: TimeUnit, nullability: Nullability) -> ExtDType<Self> {
47 Self::try_new(time_unit, nullability).vortex_expect("failed to create time dtype")
48 }
49}
50
51pub enum TimeValue {
53 Seconds(i32),
55 Milliseconds(i32),
57 Microseconds(i64),
59 Nanoseconds(i64),
61}
62
63impl fmt::Display for TimeValue {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 let min = jiff::civil::Time::MIN;
66
67 let time = match self {
68 TimeValue::Seconds(s) => min + Span::new().seconds(*s),
69 TimeValue::Milliseconds(ms) => min + Span::new().milliseconds(*ms),
70 TimeValue::Microseconds(us) => min + Span::new().microseconds(*us),
71 TimeValue::Nanoseconds(ns) => min + Span::new().nanoseconds(*ns),
72 };
73
74 write!(f, "{}", time)
75 }
76}
77
78impl ExtVTable for Time {
79 type Metadata = TimeUnit;
80
81 type NativeValue<'a> = TimeValue;
82
83 fn id(&self) -> ExtId {
84 static ID: CachedId = CachedId::new("vortex.time");
85 *ID
86 }
87
88 fn serialize_metadata(&self, metadata: &Self::Metadata) -> VortexResult<Vec<u8>> {
89 Ok(vec![u8::from(*metadata)])
90 }
91
92 fn deserialize_metadata(&self, data: &[u8]) -> VortexResult<Self::Metadata> {
93 vortex_ensure!(!data.is_empty(), "Time metadata must not be empty");
94 let tag = data[0];
95 TimeUnit::try_from(tag)
96 }
97
98 fn validate_dtype(ext_dtype: &ExtDType<Self>) -> VortexResult<()> {
99 let metadata = ext_dtype.metadata();
100 let ptype = time_ptype(metadata)
101 .ok_or_else(|| vortex_err!("Time type does not support time unit {}", metadata))?;
102
103 vortex_ensure!(
104 ext_dtype.storage_dtype().as_ptype() == ptype,
105 "Time storage dtype for {} must be {}",
106 metadata,
107 ptype
108 );
109
110 Ok(())
111 }
112
113 fn unpack_native<'a>(
114 ext_dtype: &'a ExtDType<Self>,
115 storage_value: &'a ScalarValue,
116 ) -> VortexResult<Self::NativeValue<'a>> {
117 let length_of_time = storage_value.as_primitive().cast::<i64>()?;
118
119 let (span, value) = match *ext_dtype.metadata() {
120 TimeUnit::Seconds => {
121 let v = i32::try_from(length_of_time)
122 .map_err(|e| vortex_err!("Time seconds value out of i32 range: {e}"))?;
123 (Span::new().seconds(v), TimeValue::Seconds(v))
124 }
125 TimeUnit::Milliseconds => {
126 let v = i32::try_from(length_of_time)
127 .map_err(|e| vortex_err!("Time milliseconds value out of i32 range: {e}"))?;
128 (Span::new().milliseconds(v), TimeValue::Milliseconds(v))
129 }
130 TimeUnit::Microseconds => (
131 Span::new().microseconds(length_of_time),
132 TimeValue::Microseconds(length_of_time),
133 ),
134 TimeUnit::Nanoseconds => (
135 Span::new().nanoseconds(length_of_time),
136 TimeValue::Nanoseconds(length_of_time),
137 ),
138 d @ TimeUnit::Days => vortex_bail!("Time type does not support time unit {d}"),
139 };
140
141 jiff::civil::Time::MIN
143 .checked_add(span)
144 .map_err(|e| vortex_err!("Invalid time scalar: {}", e))?;
145
146 Ok(value)
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use vortex_error::VortexResult;
153
154 use crate::dtype::DType;
155 use crate::dtype::Nullability::Nullable;
156 use crate::extension::datetime::Time;
157 use crate::extension::datetime::TimeUnit;
158 use crate::scalar::PValue;
159 use crate::scalar::Scalar;
160 use crate::scalar::ScalarValue;
161
162 #[test]
163 fn validate_time_scalar() -> VortexResult<()> {
164 let dtype = DType::Extension(Time::new(TimeUnit::Seconds, Nullable).erased());
166 Scalar::try_new(dtype, Some(ScalarValue::Primitive(PValue::I32(3661))))?;
167
168 Ok(())
169 }
170
171 #[test]
172 fn reject_time_out_of_range() {
173 let dtype = DType::Extension(Time::new(TimeUnit::Seconds, Nullable).erased());
175 let result = Scalar::try_new(dtype, Some(ScalarValue::Primitive(PValue::I32(86400))));
176 assert!(result.is_err());
177 }
178
179 #[test]
180 fn display_time_scalar() {
181 let dtype = DType::Extension(Time::new(TimeUnit::Seconds, Nullable).erased());
182
183 let scalar = Scalar::new(
184 dtype.clone(),
185 Some(ScalarValue::Primitive(PValue::I32(3661))),
186 );
187 assert_eq!(format!("{}", scalar.as_extension()), "01:01:01");
188
189 let scalar = Scalar::new(dtype, Some(ScalarValue::Primitive(PValue::I32(0))));
190 assert_eq!(format!("{}", scalar.as_extension()), "00:00:00");
191 }
192
193 #[test]
194 fn deserialize_empty_metadata_returns_error() {
195 use crate::dtype::extension::ExtVTable;
196
197 let vtable = Time;
198 assert!(vtable.deserialize_metadata(&[]).is_err());
199 }
200
201 #[test]
202 fn deserialize_invalid_tag_returns_error() {
203 use crate::dtype::extension::ExtVTable;
204
205 let vtable = Time;
206 assert!(vtable.deserialize_metadata(&[0xFF]).is_err());
208 }
209}