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}