vortex_array/arrays/datetime/
mod.rs

1#[cfg(test)]
2mod test;
3
4use std::sync::Arc;
5
6use vortex_dtype::datetime::{DATE_ID, TIME_ID, TIMESTAMP_ID, TemporalMetadata, TimeUnit};
7use vortex_dtype::{DType, ExtDType};
8use vortex_error::{VortexError, vortex_err, vortex_panic};
9
10use crate::arrays::ExtensionArray;
11use crate::variants::ExtensionArrayTrait;
12use crate::{Array, ArrayRef};
13
14/// An array wrapper for primitive values that have an associated temporal meaning.
15///
16/// This is a wrapper around ExtensionArrays containing numeric types, each of which corresponds to
17/// either a timestamp or julian date (both referenced to UNIX epoch), OR a time since midnight.
18///
19/// ## Arrow compatibility
20///
21/// TemporalArray can be created from Arrow arrays containing the following datatypes:
22/// * `Time32`
23/// * `Time64`
24/// * `Timestamp`
25/// * `Date32`
26/// * `Date64`
27///
28/// Anything that can be constructed and held in a `TemporalArray` can also be zero-copy converted
29/// back to the relevant Arrow datatype.
30#[derive(Clone, Debug)]
31pub struct TemporalArray {
32    /// The underlying Vortex extension array holding all the numeric values.
33    ext: ExtensionArray,
34
35    /// In-memory representation of the ExtMetadata that is held by the underlying extension array.
36    ///
37    /// We hold this directly to avoid needing to deserialize the metadata to access things like
38    /// timezone and TimeUnit of the underlying array.
39    temporal_metadata: TemporalMetadata,
40}
41
42macro_rules! assert_width {
43    ($width:ty, $array:expr) => {{
44        let DType::Primitive(ptype, _) = $array.dtype() else {
45            panic!("array must have primitive type");
46        };
47
48        assert_eq!(
49            <$width as vortex_dtype::NativePType>::PTYPE,
50            *ptype,
51            "invalid ptype {} for array, expected {}",
52            <$width as vortex_dtype::NativePType>::PTYPE,
53            *ptype
54        );
55    }};
56}
57
58impl TemporalArray {
59    /// Create a new `TemporalArray` holding either i32 day offsets, or i64 millisecond offsets
60    /// that are evenly divisible by the number of 86,400,000.
61    ///
62    /// This is equivalent to the data described by either of the `Date32` or `Date64` data types
63    /// from Arrow.
64    ///
65    /// # Panics
66    ///
67    /// If the time unit is milliseconds, and the array is not of primitive I64 type, it panics.
68    ///
69    /// If the time unit is days, and the array is not of primitive I32 type, it panics.
70    ///
71    /// If any other time unit is provided, it panics.
72    pub fn new_date(array: ArrayRef, time_unit: TimeUnit) -> Self {
73        match time_unit {
74            TimeUnit::D => {
75                assert_width!(i32, array);
76            }
77            TimeUnit::Ms => {
78                assert_width!(i64, array);
79            }
80            _ => vortex_panic!("invalid TimeUnit {time_unit} for vortex.date"),
81        };
82
83        let ext_dtype = ExtDType::new(
84            DATE_ID.clone(),
85            Arc::new(array.dtype().clone()),
86            Some(TemporalMetadata::Date(time_unit).into()),
87        );
88
89        Self {
90            ext: ExtensionArray::new(Arc::new(ext_dtype), array),
91            temporal_metadata: TemporalMetadata::Date(time_unit),
92        }
93    }
94
95    /// Create a new `TemporalArray` holding one of the following values:
96    ///
97    /// * `i32` values representing seconds since midnight
98    /// * `i32` values representing milliseconds since midnight
99    /// * `i64` values representing microseconds since midnight
100    /// * `i64` values representing nanoseconds since midnight
101    ///
102    /// Note, this is equivalent to the set of values represented by the Time32 or Time64 types
103    /// from Arrow.
104    ///
105    /// # Panics
106    ///
107    /// If the time unit is seconds, and the array is not of primitive I32 type, it panics.
108    ///
109    /// If the time unit is milliseconds, and the array is not of primitive I32 type, it panics.
110    ///
111    /// If the time unit is microseconds, and the array is not of primitive I64 type, it panics.
112    ///
113    /// If the time unit is nanoseconds, and the array is not of primitive I64 type, it panics.
114    pub fn new_time(array: ArrayRef, time_unit: TimeUnit) -> Self {
115        match time_unit {
116            TimeUnit::S | TimeUnit::Ms => assert_width!(i32, array),
117            TimeUnit::Us | TimeUnit::Ns => assert_width!(i64, array),
118            TimeUnit::D => vortex_panic!("invalid unit D for vortex.time data"),
119        }
120
121        let temporal_metadata = TemporalMetadata::Time(time_unit);
122        Self {
123            ext: ExtensionArray::new(
124                Arc::new(ExtDType::new(
125                    TIME_ID.clone(),
126                    Arc::new(array.dtype().clone()),
127                    Some(temporal_metadata.clone().into()),
128                )),
129                array,
130            ),
131            temporal_metadata,
132        }
133    }
134
135    /// Create a new `TemporalArray` holding Arrow spec compliant Timestamp data, with an
136    /// optional timezone.
137    ///
138    /// # Panics
139    ///
140    /// If `array` does not hold Primitive i64 data, the function will panic.
141    ///
142    /// If the time_unit is days, the function will panic.
143    pub fn new_timestamp(array: ArrayRef, time_unit: TimeUnit, time_zone: Option<String>) -> Self {
144        assert_width!(i64, array);
145
146        let temporal_metadata = TemporalMetadata::Timestamp(time_unit, time_zone);
147
148        Self {
149            ext: ExtensionArray::new(
150                Arc::new(ExtDType::new(
151                    TIMESTAMP_ID.clone(),
152                    Arc::new(array.dtype().clone()),
153                    Some(temporal_metadata.clone().into()),
154                )),
155                array,
156            ),
157            temporal_metadata,
158        }
159    }
160}
161
162impl TemporalArray {
163    /// Access the underlying temporal values in the underlying ExtensionArray storage.
164    ///
165    /// These values are to be interpreted based on the time unit and optional time-zone stored
166    /// in the TemporalMetadata.
167    pub fn temporal_values(&self) -> &ArrayRef {
168        self.ext.storage()
169    }
170
171    /// Retrieve the temporal metadata.
172    ///
173    /// The metadata is used to provide semantic meaning to the temporal values Array, for example
174    /// to understand the granularity of the samples and if they have an associated timezone.
175    pub fn temporal_metadata(&self) -> &TemporalMetadata {
176        &self.temporal_metadata
177    }
178
179    /// Retrieve the extension DType associated with the underlying array.
180    pub fn ext_dtype(&self) -> Arc<ExtDType> {
181        self.ext.ext_dtype().clone()
182    }
183
184    /// Retrieve the DType of the array. This will be a `DType::Extension` variant.
185    pub fn dtype(&self) -> &DType {
186        self.ext.dtype()
187    }
188}
189
190impl From<TemporalArray> for ArrayRef {
191    fn from(value: TemporalArray) -> Self {
192        value.ext.into_array()
193    }
194}
195
196impl TryFrom<ArrayRef> for TemporalArray {
197    type Error = VortexError;
198
199    /// Try to specialize a generic Vortex array as a TemporalArray.
200    ///
201    /// # Errors
202    ///
203    /// If the provided Array does not have `vortex.ext` encoding, an error will be returned.
204    ///
205    /// If the provided Array does not have recognized ExtMetadata corresponding to one of the known
206    /// `TemporalMetadata` variants, an error is returned.
207    fn try_from(value: ArrayRef) -> Result<Self, Self::Error> {
208        let ext = value
209            .as_any_arc()
210            .downcast::<ExtensionArray>()
211            .map_err(|_| vortex_err!("array must be an ExtensionArray"))?;
212        let ext = Arc::unwrap_or_clone(ext);
213        let temporal_metadata = TemporalMetadata::try_from(ext.ext_dtype().as_ref())?;
214
215        Ok(Self {
216            ext,
217            temporal_metadata,
218        })
219    }
220}
221
222// Conversions to/from ExtensionArray
223impl From<&TemporalArray> for ExtensionArray {
224    fn from(value: &TemporalArray) -> Self {
225        value.ext.clone()
226    }
227}
228
229impl From<TemporalArray> for ExtensionArray {
230    fn from(value: TemporalArray) -> Self {
231        value.ext
232    }
233}
234
235impl TryFrom<ExtensionArray> for TemporalArray {
236    type Error = VortexError;
237
238    fn try_from(ext: ExtensionArray) -> Result<Self, Self::Error> {
239        let temporal_metadata = TemporalMetadata::try_from(ext.ext_dtype().as_ref())?;
240        Ok(Self {
241            ext,
242            temporal_metadata,
243        })
244    }
245}