Skip to main content

vortex_array/arrays/datetime/
mod.rs

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