vortex_array/extension/datetime/
date.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
23const EPOCH: jiff::civil::Date = jiff::civil::Date::constant(1970, 1, 1);
25
26#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
28pub struct Date;
29
30fn date_ptype(time_unit: &TimeUnit) -> Option<PType> {
31 match time_unit {
32 TimeUnit::Nanoseconds => None,
33 TimeUnit::Microseconds => None,
34 TimeUnit::Milliseconds => Some(PType::I64),
35 TimeUnit::Seconds => None,
36 TimeUnit::Days => Some(PType::I32),
37 }
38}
39
40impl Date {
41 pub fn try_new(time_unit: TimeUnit, nullability: Nullability) -> VortexResult<ExtDType<Self>> {
45 let ptype = date_ptype(&time_unit)
46 .ok_or_else(|| vortex_err!("Date type does not support time unit {}", time_unit))?;
47 ExtDType::try_new(time_unit, DType::Primitive(ptype, nullability))
48 }
49
50 pub fn new(time_unit: TimeUnit, nullability: Nullability) -> ExtDType<Self> {
56 Self::try_new(time_unit, nullability).vortex_expect("failed to create date dtype")
57 }
58}
59
60pub enum DateValue {
62 Days(i32),
64 Milliseconds(i64),
66}
67
68impl fmt::Display for DateValue {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 let date = match self {
71 DateValue::Days(days) => EPOCH + Span::new().days(*days),
72 DateValue::Milliseconds(ms) => EPOCH + Span::new().milliseconds(*ms),
73 };
74 write!(f, "{}", date)
75 }
76}
77
78impl ExtVTable for Date {
79 type Metadata = TimeUnit;
80 type NativeValue<'a> = DateValue;
81
82 fn id(&self) -> ExtId {
83 static ID: CachedId = CachedId::new("vortex.date");
84 *ID
85 }
86
87 fn serialize_metadata(&self, metadata: &Self::Metadata) -> VortexResult<Vec<u8>> {
88 Ok(vec![u8::from(*metadata)])
89 }
90
91 fn deserialize_metadata(&self, metadata: &[u8]) -> VortexResult<Self::Metadata> {
92 vortex_ensure!(!metadata.is_empty(), "Date metadata must not be empty");
93 let tag = metadata[0];
94 TimeUnit::try_from(tag)
95 }
96
97 fn validate_dtype(ext_dtype: &ExtDType<Self>) -> VortexResult<()> {
98 let metadata = ext_dtype.metadata();
99 let ptype = date_ptype(metadata)
100 .ok_or_else(|| vortex_err!("Date type does not support time unit {}", metadata))?;
101
102 vortex_ensure!(
103 ext_dtype.storage_dtype().as_ptype() == ptype,
104 "Date storage dtype for {} must be {}",
105 metadata,
106 ptype
107 );
108
109 Ok(())
110 }
111
112 fn unpack_native<'a>(
113 ext_dtype: &'a ExtDType<Self>,
114 storage_value: &'a ScalarValue,
115 ) -> VortexResult<Self::NativeValue<'a>> {
116 let metadata = ext_dtype.metadata();
117 match metadata {
118 TimeUnit::Milliseconds => Ok(DateValue::Milliseconds(
119 storage_value.as_primitive().cast::<i64>()?,
120 )),
121 TimeUnit::Days => Ok(DateValue::Days(storage_value.as_primitive().cast::<i32>()?)),
122 _ => vortex_bail!("Date type does not support time unit {}", metadata),
123 }
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use vortex_error::VortexResult;
130
131 use crate::dtype::DType;
132 use crate::dtype::Nullability::Nullable;
133 use crate::extension::datetime::Date;
134 use crate::extension::datetime::TimeUnit;
135 use crate::scalar::PValue;
136 use crate::scalar::Scalar;
137 use crate::scalar::ScalarValue;
138
139 #[test]
140 fn validate_date_scalar() -> VortexResult<()> {
141 let days_dtype = DType::Extension(Date::new(TimeUnit::Days, Nullable).erased());
142 Scalar::try_new(days_dtype, Some(ScalarValue::Primitive(PValue::I32(0))))?;
143
144 let ms_dtype = DType::Extension(Date::new(TimeUnit::Milliseconds, Nullable).erased());
145 Scalar::try_new(
146 ms_dtype,
147 Some(ScalarValue::Primitive(PValue::I64(86_400_000))),
148 )?;
149
150 Ok(())
151 }
152
153 #[test]
154 fn reject_date_with_overflowing_value() {
155 let dtype = DType::Extension(Date::new(TimeUnit::Days, Nullable).erased());
157 let result = Scalar::try_new(dtype, Some(ScalarValue::Primitive(PValue::I64(i64::MAX))));
158 assert!(result.is_err());
159 }
160
161 #[test]
162 fn display_date_scalar() {
163 let dtype = DType::Extension(Date::new(TimeUnit::Days, Nullable).erased());
164
165 let scalar = Scalar::new(dtype.clone(), Some(ScalarValue::Primitive(PValue::I32(0))));
166 assert_eq!(format!("{}", scalar.as_extension()), "1970-01-01");
167
168 let scalar = Scalar::new(dtype, Some(ScalarValue::Primitive(PValue::I32(365))));
169 assert_eq!(format!("{}", scalar.as_extension()), "1971-01-01");
170 }
171
172 #[test]
173 fn deserialize_empty_metadata_returns_error() {
174 use crate::dtype::extension::ExtVTable;
175
176 let vtable = Date;
177 assert!(vtable.deserialize_metadata(&[]).is_err());
178 }
179
180 #[test]
181 fn deserialize_invalid_tag_returns_error() {
182 use crate::dtype::extension::ExtVTable;
183
184 let vtable = Date;
185 assert!(vtable.deserialize_metadata(&[0xFF]).is_err());
187 }
188}