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;
12
13use crate::dtype::DType;
14use crate::dtype::Nullability;
15use crate::dtype::PType;
16use crate::dtype::extension::ExtDType;
17use crate::dtype::extension::ExtId;
18use crate::dtype::extension::ExtVTable;
19use crate::extension::datetime::TimeUnit;
20use crate::scalar::ScalarValue;
21
22#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
24pub struct Time;
25
26fn time_ptype(time_unit: &TimeUnit) -> Option<PType> {
27 Some(match time_unit {
28 TimeUnit::Nanoseconds | TimeUnit::Microseconds => PType::I64,
29 TimeUnit::Milliseconds | TimeUnit::Seconds => PType::I32,
30 TimeUnit::Days => return None,
31 })
32}
33
34impl Time {
35 pub fn try_new(time_unit: TimeUnit, nullability: Nullability) -> VortexResult<ExtDType<Self>> {
39 let ptype = time_ptype(&time_unit)
40 .ok_or_else(|| vortex_err!("Time type does not support time unit {}", time_unit))?;
41 ExtDType::try_new(time_unit, DType::Primitive(ptype, nullability))
42 }
43
44 pub fn new(time_unit: TimeUnit, nullability: Nullability) -> ExtDType<Self> {
46 Self::try_new(time_unit, nullability).vortex_expect("failed to create time dtype")
47 }
48}
49
50pub enum TimeValue {
52 Seconds(i32),
54 Milliseconds(i32),
56 Microseconds(i64),
58 Nanoseconds(i64),
60}
61
62impl fmt::Display for TimeValue {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 let min = jiff::civil::Time::MIN;
65
66 let time = match self {
67 TimeValue::Seconds(s) => min + Span::new().seconds(*s),
68 TimeValue::Milliseconds(ms) => min + Span::new().milliseconds(*ms),
69 TimeValue::Microseconds(us) => min + Span::new().microseconds(*us),
70 TimeValue::Nanoseconds(ns) => min + Span::new().nanoseconds(*ns),
71 };
72
73 write!(f, "{}", time)
74 }
75}
76
77impl ExtVTable for Time {
78 type Metadata = TimeUnit;
79
80 type NativeValue<'a> = TimeValue;
81
82 fn id(&self) -> ExtId {
83 ExtId::new("vortex.time")
84 }
85
86 fn serialize_metadata(&self, metadata: &Self::Metadata) -> VortexResult<Vec<u8>> {
87 Ok(vec![u8::from(*metadata)])
88 }
89
90 fn deserialize_metadata(&self, data: &[u8]) -> VortexResult<Self::Metadata> {
91 vortex_ensure!(!data.is_empty(), "Time metadata must not be empty");
92 let tag = data[0];
93 TimeUnit::try_from(tag)
94 }
95
96 fn validate_dtype(ext_dtype: &ExtDType<Self>) -> VortexResult<()> {
97 let metadata = ext_dtype.metadata();
98 let ptype = time_ptype(metadata)
99 .ok_or_else(|| vortex_err!("Time type does not support time unit {}", metadata))?;
100
101 vortex_ensure!(
102 ext_dtype.storage_dtype().as_ptype() == ptype,
103 "Time storage dtype for {} must be {}",
104 metadata,
105 ptype
106 );
107
108 Ok(())
109 }
110
111 fn can_coerce_from(ext_dtype: &ExtDType<Self>, other: &DType) -> bool {
112 let DType::Extension(other_ext) = other else {
113 return false;
114 };
115 let Some(other_unit) = other_ext.metadata_opt::<Time>() else {
116 return false;
117 };
118 let our_unit = ext_dtype.metadata();
119 our_unit <= other_unit && (ext_dtype.storage_dtype().is_nullable() || !other.is_nullable())
120 }
121
122 fn least_supertype(ext_dtype: &ExtDType<Self>, other: &DType) -> Option<DType> {
123 let DType::Extension(other_ext) = other else {
124 return None;
125 };
126 let other_unit = other_ext.metadata_opt::<Time>()?;
127 let our_unit = ext_dtype.metadata();
128 let finest = (*our_unit).min(*other_unit);
129 let union_null = ext_dtype.storage_dtype().nullability() | other.nullability();
130 Some(DType::Extension(Time::new(finest, union_null).erased()))
131 }
132
133 fn unpack_native<'a>(
134 ext_dtype: &'a ExtDType<Self>,
135 storage_value: &'a ScalarValue,
136 ) -> VortexResult<Self::NativeValue<'a>> {
137 let length_of_time = storage_value.as_primitive().cast::<i64>()?;
138
139 let (span, value) = match *ext_dtype.metadata() {
140 TimeUnit::Seconds => {
141 let v = i32::try_from(length_of_time)
142 .map_err(|e| vortex_err!("Time seconds value out of i32 range: {e}"))?;
143 (Span::new().seconds(v), TimeValue::Seconds(v))
144 }
145 TimeUnit::Milliseconds => {
146 let v = i32::try_from(length_of_time)
147 .map_err(|e| vortex_err!("Time milliseconds value out of i32 range: {e}"))?;
148 (Span::new().milliseconds(v), TimeValue::Milliseconds(v))
149 }
150 TimeUnit::Microseconds => (
151 Span::new().microseconds(length_of_time),
152 TimeValue::Microseconds(length_of_time),
153 ),
154 TimeUnit::Nanoseconds => (
155 Span::new().nanoseconds(length_of_time),
156 TimeValue::Nanoseconds(length_of_time),
157 ),
158 d @ TimeUnit::Days => vortex_bail!("Time type does not support time unit {d}"),
159 };
160
161 jiff::civil::Time::MIN
163 .checked_add(span)
164 .map_err(|e| vortex_err!("Invalid time scalar: {}", e))?;
165
166 Ok(value)
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use vortex_error::VortexResult;
173
174 use crate::dtype::DType;
175 use crate::dtype::Nullability::Nullable;
176 use crate::extension::datetime::Time;
177 use crate::extension::datetime::TimeUnit;
178 use crate::scalar::PValue;
179 use crate::scalar::Scalar;
180 use crate::scalar::ScalarValue;
181
182 #[test]
183 fn validate_time_scalar() -> VortexResult<()> {
184 let dtype = DType::Extension(Time::new(TimeUnit::Seconds, Nullable).erased());
186 Scalar::try_new(dtype, Some(ScalarValue::Primitive(PValue::I32(3661))))?;
187
188 Ok(())
189 }
190
191 #[test]
192 fn reject_time_out_of_range() {
193 let dtype = DType::Extension(Time::new(TimeUnit::Seconds, Nullable).erased());
195 let result = Scalar::try_new(dtype, Some(ScalarValue::Primitive(PValue::I32(86400))));
196 assert!(result.is_err());
197 }
198
199 #[test]
200 fn display_time_scalar() {
201 let dtype = DType::Extension(Time::new(TimeUnit::Seconds, Nullable).erased());
202
203 let scalar = Scalar::new(
204 dtype.clone(),
205 Some(ScalarValue::Primitive(PValue::I32(3661))),
206 );
207 assert_eq!(format!("{}", scalar.as_extension()), "01:01:01");
208
209 let scalar = Scalar::new(dtype, Some(ScalarValue::Primitive(PValue::I32(0))));
210 assert_eq!(format!("{}", scalar.as_extension()), "00:00:00");
211 }
212
213 #[test]
214 fn least_supertype_time_units() {
215 use crate::dtype::Nullability::NonNullable;
216
217 let secs = DType::Extension(Time::new(TimeUnit::Seconds, NonNullable).erased());
218 let ns = DType::Extension(Time::new(TimeUnit::Nanoseconds, NonNullable).erased());
219 let expected = DType::Extension(Time::new(TimeUnit::Nanoseconds, NonNullable).erased());
220 assert_eq!(secs.least_supertype(&ns).unwrap(), expected);
221 assert_eq!(ns.least_supertype(&secs).unwrap(), expected);
222 }
223
224 #[test]
225 fn deserialize_empty_metadata_returns_error() {
226 use crate::dtype::extension::ExtVTable;
227
228 let vtable = Time;
229 assert!(vtable.deserialize_metadata(&[]).is_err());
230 }
231
232 #[test]
233 fn deserialize_invalid_tag_returns_error() {
234 use crate::dtype::extension::ExtVTable;
235
236 let vtable = Time;
237 assert!(vtable.deserialize_metadata(&[0xFF]).is_err());
239 }
240}