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}