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