Skip to main content

minarrow/enums/collections/
temporal_array.rs

1// Copyright 2025 Peter Garfield Bower
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! # **TemporalArray Module** - *High-Level DateTimes Array Type for Unified Signature Dispatch*
16//!
17//! TemporalArray unifies all datetime-based arrays into a single enum for
18//! standardised temporal operations.
19//!   
20//! ## Features:
21//! - direct variant access
22//! - zero-cost casts when the type is known
23//! - lossless conversions between 32-bit and 64-bit datetime types.  
24//! - simplifies function signatures by accepting `impl Into<TemporalArray>`
25//! - centralises dispatch
26//! - preserves SIMD-aligned buffers across all temporal variants.
27
28use std::{
29    fmt::{Display, Formatter},
30    sync::Arc,
31};
32
33use crate::{Bitmask, DatetimeArray, MaskedArray};
34use crate::{
35    enums::{error::MinarrowError, shape_dim::ShapeDim},
36    traits::{concatenate::Concatenate, shape::Shape},
37};
38
39/// Temporal Array
40///
41/// Unified datetime array container
42///
43/// ## Purpose
44/// Exists to unify datetime operations,
45/// simplify API's and streamline user ergonomics.
46///
47/// ## Usage:
48/// - It is accessible from `Array` using `.dt()`,
49/// and provides typed variant access via for e.g.,
50/// `.dt32()`, so one can drill down to the required
51/// granularity via `myarr.dt().dt32()`
52/// - This streamlines function implementations *(at least for the `NumericArray`
53/// case where this pattern is the most useful)*,
54/// and, despite the additional `enum` layer,
55/// matching lanes in many real-world scenarios.
56/// This is because one can for e.g., unify a
57/// function signature with `impl Into<TemporalArray>`,
58/// and all of the subtypes, plus `Array` and `TemporalArray`,
59/// all qualify.
60/// - Additionally, you can then use one `Temporal` implementation
61/// on the enum dispatch arm for all `Temporal` variants, or,
62/// in many cases, for the entire datetime arm when they are the same.
63///
64/// ### Handling Times, Durations, etc.
65/// We use one Physical type to hold all datetime variants,
66/// i.e., the *Apache Arrow* types `DATE32`, `TIME32`, `DURATION` etc.,
67/// and the Logical type is stored on the `Field` as metadata, given they
68/// otherwise have the same underlying data representation. To treat
69/// them differently in API usage, you can use the `TimeUnit` and `IntervalUnit`,
70/// along with the `ArrowType` that is stored on the `Field` in `Minarrow`,
71/// and match on these for any desired behaviour. The `Field` is packaged together
72/// with `Array` *(which then drill-down accesses `TemporalArray` on the fly, or
73/// in dispatch routing scenarios)*.
74///
75/// ### Typecasting behaviour
76/// - If the enum already holds the given type *(which should be known at compile-time)*,
77/// then using accessors like `.dt32()` is zero-cost, as it transfers ownership.
78/// - If you want to keep the original, of course use `.clone()` beforehand.
79/// - If you use an accessor to a different base type, e.g., `.dt64()` when it's a
80/// `.dt32()` already in the enum, it will convert it. Therefore, be mindful
81/// of performance when this occurs.
82#[repr(C, align(64))]
83#[derive(PartialEq, Clone, Debug, Default)]
84pub enum TemporalArray {
85    // The datetimes are chunked by their common memory layout rather than logical type
86    // These can be casted to the relevant Arrow type at the FFI layer as needed
87    Datetime32(Arc<DatetimeArray<i32>>), // DATE32, TIME32, DURATION(s), DURATION(ms) (32-bit)
88    // DATE64, TIMESTAMP (ms/us/ns), DURATION (ms/us/ns), TIME64, DURATION(us), DURATION(ns)
89    Datetime64(Arc<DatetimeArray<i64>>),
90    #[default]
91    Null, // Default Marker for mem::take
92}
93
94impl TemporalArray {
95    /// Returns the logical length of the temporal array.
96    #[inline]
97    pub fn len(&self) -> usize {
98        match self {
99            TemporalArray::Datetime32(arr) => arr.len(),
100            TemporalArray::Datetime64(arr) => arr.len(),
101            TemporalArray::Null => 0,
102        }
103    }
104
105    /// Removes the rows in `[start, end)`, shifting later rows left.
106    /// A shared inner array is cloned first i.e. copy-on-write.
107    ///
108    /// # Panics
109    /// Panics if `start > end` or `end > len`.
110    pub fn delete_range(&mut self, start: usize, end: usize) {
111        match self {
112            TemporalArray::Datetime32(arr) => arr.delete_range(start, end),
113            TemporalArray::Datetime64(arr) => arr.delete_range(start, end),
114            TemporalArray::Null => {
115                assert!(
116                    start == 0 && end == 0,
117                    "TemporalArray::Null: delete_range out of bounds"
118                );
119            }
120        }
121    }
122
123    /// Returns the underlying null mask, if any.
124    #[inline]
125    pub fn null_mask(&self) -> Option<&Bitmask> {
126        match self {
127            TemporalArray::Datetime32(arr) => arr.null_mask.as_ref(),
128            TemporalArray::Datetime64(arr) => arr.null_mask.as_ref(),
129            TemporalArray::Null => None,
130        }
131    }
132
133    /// Returns true when the variant holds at least one null.
134    ///
135    /// Delegates to each inner array's `MaskedArray::has_nulls`; `Null` is
136    /// treated as empty (no elements means no nulls).
137    #[inline]
138    pub fn has_nulls(&self) -> bool {
139        match self {
140            TemporalArray::Datetime32(arr) => arr.has_nulls(),
141            TemporalArray::Datetime64(arr) => arr.has_nulls(),
142            TemporalArray::Null => false,
143        }
144    }
145
146    /// Appends all values (and null mask if present) from `other` into `self`.
147    ///
148    /// Panics if the two arrays are of different variants or incompatible types.
149    ///
150    /// This function uses copy-on-write semantics for arrays wrapped in `Arc`.
151    /// If `self` is the only owner of its data, appends are performed in place without copying.
152    /// If the array data is shared (`Arc` reference count > 1), the data is first cloned
153    /// (so the mutation does not affect other owners), and the append is then performed on the unique copy.
154    ///
155    /// This ensures that calling `append_array` never mutates data referenced elsewhere,
156    /// but also avoids unnecessary cloning when the data is uniquely owned.
157    pub fn append_array(&mut self, other: &Self) {
158        match (self, other) {
159            (TemporalArray::Datetime32(a), TemporalArray::Datetime32(b)) => {
160                Arc::make_mut(a).append_array(b)
161            }
162            (TemporalArray::Datetime64(a), TemporalArray::Datetime64(b)) => {
163                Arc::make_mut(a).append_array(b)
164            }
165            (TemporalArray::Null, TemporalArray::Null) => (),
166            (lhs, rhs) => panic!("Cannot append {:?} into {:?}", rhs, lhs),
167        }
168    }
169
170    pub fn append_range(&mut self, other: &Self, offset: usize, len: usize) -> Result<(), MinarrowError> {
171        match (self, other) {
172            (TemporalArray::Datetime32(a), TemporalArray::Datetime32(b)) => Arc::make_mut(a).append_range(b, offset, len),
173            (TemporalArray::Datetime64(a), TemporalArray::Datetime64(b)) => Arc::make_mut(a).append_range(b, offset, len),
174            (TemporalArray::Null, TemporalArray::Null) => Ok(()),
175            (lhs, rhs) => Err(MinarrowError::TypeError {
176                from: "TemporalArray",
177                to: "TemporalArray",
178                message: Some(format!("Cannot append_range {:?} into {:?}", rhs, lhs)),
179            }),
180        }
181    }
182
183    /// Inserts all values (and null mask if present) from `other` into `self` at the specified index.
184    ///
185    /// This is an **O(n)** operation.
186    ///
187    /// Returns an error if the two arrays are of different variants or incompatible types,
188    /// or if the index is out of bounds.
189    ///
190    /// This function uses copy-on-write semantics for arrays wrapped in `Arc`.
191    pub fn insert_rows(&mut self, index: usize, other: &Self) -> Result<(), MinarrowError> {
192        match (self, other) {
193            (TemporalArray::Datetime32(a), TemporalArray::Datetime32(b)) => {
194                Arc::make_mut(a).insert_rows(index, b)
195            }
196            (TemporalArray::Datetime64(a), TemporalArray::Datetime64(b)) => {
197                Arc::make_mut(a).insert_rows(index, b)
198            }
199            (TemporalArray::Null, TemporalArray::Null) => Ok(()),
200            (lhs, rhs) => Err(MinarrowError::TypeError {
201                from: "TemporalArray",
202                to: "TemporalArray",
203                message: Some(format!(
204                    "Cannot insert {} into {}: incompatible types",
205                    temporal_variant_name(rhs),
206                    temporal_variant_name(lhs)
207                )),
208            }),
209        }
210    }
211
212    /// Splits the TemporalArray at the specified index, consuming self and returning two arrays.
213    pub fn split(self, index: usize) -> Result<(Self, Self), MinarrowError> {
214        use std::sync::Arc;
215
216        match self {
217            TemporalArray::Datetime32(a) => {
218                let (left, right) = Arc::try_unwrap(a)
219                    .unwrap_or_else(|arc| (*arc).clone())
220                    .split(index)?;
221                Ok((
222                    TemporalArray::Datetime32(Arc::new(left)),
223                    TemporalArray::Datetime32(Arc::new(right)),
224                ))
225            }
226            TemporalArray::Datetime64(a) => {
227                let (left, right) = Arc::try_unwrap(a)
228                    .unwrap_or_else(|arc| (*arc).clone())
229                    .split(index)?;
230                Ok((
231                    TemporalArray::Datetime64(Arc::new(left)),
232                    TemporalArray::Datetime64(Arc::new(right)),
233                ))
234            }
235            TemporalArray::Null => Err(MinarrowError::IndexError(
236                "Cannot split Null array".to_string(),
237            )),
238        }
239    }
240
241    /// Returns the inner array as `Arc<DatetimeArray<i32>>`, casting when the variant differs.
242    ///
243    /// - The matching variant returns as a shared handle without copying data.
244    /// - Panics on failure. Consider the try variant for a safe alternative.
245    #[inline]
246    pub fn dt32(&self) -> Arc<DatetimeArray<i32>> {
247        self.try_dt32().unwrap()
248    }
249
250    /// Returns an Arc<DatetimeArray<i32>> (casting if needed).
251    ///
252    /// The matching variant returns as a shared handle without copying data.
253    pub fn try_dt32(&self) -> Result<Arc<DatetimeArray<i32>>, MinarrowError> {
254        match self {
255            TemporalArray::Datetime32(arr) => Ok(arr.clone()),
256            TemporalArray::Datetime64(arr) => Ok(Arc::new(DatetimeArray::<i32>::try_from(&**arr)?)),
257            TemporalArray::Null => Err(MinarrowError::NullError { message: None }),
258        }
259    }
260
261    /// Returns the inner array as `Arc<DatetimeArray<i64>>`, casting when the variant differs.
262    ///
263    /// - The matching variant returns as a shared handle without copying data.
264    /// - Panics on failure. Consider the try variant for a safe alternative.
265    #[inline]
266    pub fn dt64(&self) -> Arc<DatetimeArray<i64>> {
267        self.try_dt64().unwrap()
268    }
269
270    /// Returns an Arc<DatetimeArray<i64>> (casting if needed).
271    ///
272    /// The matching variant returns as a shared handle without copying data.
273    pub fn try_dt64(&self) -> Result<Arc<DatetimeArray<i64>>, MinarrowError> {
274        match self {
275            TemporalArray::Datetime64(arr) => Ok(arr.clone()),
276            TemporalArray::Datetime32(arr) => Ok(Arc::new(DatetimeArray::<i64>::from(&**arr))),
277            TemporalArray::Null => Err(MinarrowError::NullError { message: None }),
278        }
279    }
280}
281
282impl Shape for TemporalArray {
283    fn shape(&self) -> ShapeDim {
284        ShapeDim::Rank1(self.len())
285    }
286}
287
288impl Concatenate for TemporalArray {
289    fn concat(self, other: Self) -> Result<Self, MinarrowError> {
290        match (self, other) {
291            (TemporalArray::Datetime32(a), TemporalArray::Datetime32(b)) => {
292                let a = Arc::try_unwrap(a).unwrap_or_else(|arc| (*arc).clone());
293                let b = Arc::try_unwrap(b).unwrap_or_else(|arc| (*arc).clone());
294                Ok(TemporalArray::Datetime32(Arc::new(a.concat(b)?)))
295            }
296            (TemporalArray::Datetime64(a), TemporalArray::Datetime64(b)) => {
297                let a = Arc::try_unwrap(a).unwrap_or_else(|arc| (*arc).clone());
298                let b = Arc::try_unwrap(b).unwrap_or_else(|arc| (*arc).clone());
299                Ok(TemporalArray::Datetime64(Arc::new(a.concat(b)?)))
300            }
301            (TemporalArray::Null, TemporalArray::Null) => Ok(TemporalArray::Null),
302            (lhs, rhs) => Err(MinarrowError::IncompatibleTypeError {
303                from: "TemporalArray",
304                to: "TemporalArray",
305                message: Some(format!(
306                    "Cannot concatenate mismatched TemporalArray variants: {:?} and {:?}",
307                    temporal_variant_name(&lhs),
308                    temporal_variant_name(&rhs)
309                )),
310            }),
311        }
312    }
313}
314
315#[cfg(feature = "datetime_ops")]
316use crate::DatetimeOps;
317
318#[cfg(feature = "datetime_ops")]
319use crate::enums::time_units::TimeUnit;
320
321#[cfg(feature = "datetime_ops")]
322use time::Duration;
323
324#[cfg(feature = "datetime_ops")]
325use crate::structs::variants::{boolean::BooleanArray, integer::IntegerArray};
326
327#[cfg(feature = "datetime_ops")]
328impl DatetimeOps for TemporalArray {
329    // Component Extraction - delegate to inner variant, return directly
330
331    fn year(&self) -> IntegerArray<i32> {
332        match self {
333            TemporalArray::Datetime32(arr) => arr.year(),
334            TemporalArray::Datetime64(arr) => arr.year(),
335            TemporalArray::Null => IntegerArray::default(),
336        }
337    }
338
339    fn month(&self) -> IntegerArray<i32> {
340        match self {
341            TemporalArray::Datetime32(arr) => arr.month(),
342            TemporalArray::Datetime64(arr) => arr.month(),
343            TemporalArray::Null => IntegerArray::default(),
344        }
345    }
346
347    fn day(&self) -> IntegerArray<i32> {
348        match self {
349            TemporalArray::Datetime32(arr) => arr.day(),
350            TemporalArray::Datetime64(arr) => arr.day(),
351            TemporalArray::Null => IntegerArray::default(),
352        }
353    }
354
355    fn hour(&self) -> IntegerArray<i32> {
356        match self {
357            TemporalArray::Datetime32(arr) => arr.hour(),
358            TemporalArray::Datetime64(arr) => arr.hour(),
359            TemporalArray::Null => IntegerArray::default(),
360        }
361    }
362
363    fn minute(&self) -> IntegerArray<i32> {
364        match self {
365            TemporalArray::Datetime32(arr) => arr.minute(),
366            TemporalArray::Datetime64(arr) => arr.minute(),
367            TemporalArray::Null => IntegerArray::default(),
368        }
369    }
370
371    fn second(&self) -> IntegerArray<i32> {
372        match self {
373            TemporalArray::Datetime32(arr) => arr.second(),
374            TemporalArray::Datetime64(arr) => arr.second(),
375            TemporalArray::Null => IntegerArray::default(),
376        }
377    }
378
379    fn weekday(&self) -> IntegerArray<i32> {
380        match self {
381            TemporalArray::Datetime32(arr) => arr.weekday(),
382            TemporalArray::Datetime64(arr) => arr.weekday(),
383            TemporalArray::Null => IntegerArray::default(),
384        }
385    }
386
387    fn day_of_year(&self) -> IntegerArray<i32> {
388        match self {
389            TemporalArray::Datetime32(arr) => arr.day_of_year(),
390            TemporalArray::Datetime64(arr) => arr.day_of_year(),
391            TemporalArray::Null => IntegerArray::default(),
392        }
393    }
394
395    fn iso_week(&self) -> IntegerArray<i32> {
396        match self {
397            TemporalArray::Datetime32(arr) => arr.iso_week(),
398            TemporalArray::Datetime64(arr) => arr.iso_week(),
399            TemporalArray::Null => IntegerArray::default(),
400        }
401    }
402
403    fn quarter(&self) -> IntegerArray<i32> {
404        match self {
405            TemporalArray::Datetime32(arr) => arr.quarter(),
406            TemporalArray::Datetime64(arr) => arr.quarter(),
407            TemporalArray::Null => IntegerArray::default(),
408        }
409    }
410
411    fn week_of_year(&self) -> IntegerArray<i32> {
412        match self {
413            TemporalArray::Datetime32(arr) => arr.week_of_year(),
414            TemporalArray::Datetime64(arr) => arr.week_of_year(),
415            TemporalArray::Null => IntegerArray::default(),
416        }
417    }
418
419    fn is_leap_year(&self) -> BooleanArray<()> {
420        match self {
421            TemporalArray::Datetime32(arr) => arr.is_leap_year(),
422            TemporalArray::Datetime64(arr) => arr.is_leap_year(),
423            TemporalArray::Null => BooleanArray::default(),
424        }
425    }
426
427    // Arithmetic - delegate, wrap result back into enum variant
428
429    fn add_duration(&self, duration: Duration) -> Result<Self, MinarrowError> {
430        match self {
431            TemporalArray::Datetime32(arr) => Ok(TemporalArray::Datetime32(Arc::new(
432                arr.add_duration(duration)?,
433            ))),
434            TemporalArray::Datetime64(arr) => Ok(TemporalArray::Datetime64(Arc::new(
435                arr.add_duration(duration)?,
436            ))),
437            TemporalArray::Null => Err(MinarrowError::NullError { message: None }),
438        }
439    }
440
441    fn sub_duration(&self, duration: Duration) -> Result<Self, MinarrowError> {
442        match self {
443            TemporalArray::Datetime32(arr) => Ok(TemporalArray::Datetime32(Arc::new(
444                arr.sub_duration(duration)?,
445            ))),
446            TemporalArray::Datetime64(arr) => Ok(TemporalArray::Datetime64(Arc::new(
447                arr.sub_duration(duration)?,
448            ))),
449            TemporalArray::Null => Err(MinarrowError::NullError { message: None }),
450        }
451    }
452
453    fn add_days(&self, days: i64) -> Result<Self, MinarrowError> {
454        match self {
455            TemporalArray::Datetime32(arr) => {
456                Ok(TemporalArray::Datetime32(Arc::new(arr.add_days(days)?)))
457            }
458            TemporalArray::Datetime64(arr) => {
459                Ok(TemporalArray::Datetime64(Arc::new(arr.add_days(days)?)))
460            }
461            TemporalArray::Null => Err(MinarrowError::NullError { message: None }),
462        }
463    }
464
465    fn add_months(&self, months: i32) -> Result<Self, MinarrowError> {
466        match self {
467            TemporalArray::Datetime32(arr) => {
468                Ok(TemporalArray::Datetime32(Arc::new(arr.add_months(months)?)))
469            }
470            TemporalArray::Datetime64(arr) => {
471                Ok(TemporalArray::Datetime64(Arc::new(arr.add_months(months)?)))
472            }
473            TemporalArray::Null => Err(MinarrowError::NullError { message: None }),
474        }
475    }
476
477    fn add_years(&self, years: i32) -> Result<Self, MinarrowError> {
478        match self {
479            TemporalArray::Datetime32(arr) => {
480                Ok(TemporalArray::Datetime32(Arc::new(arr.add_years(years)?)))
481            }
482            TemporalArray::Datetime64(arr) => {
483                Ok(TemporalArray::Datetime64(Arc::new(arr.add_years(years)?)))
484            }
485            TemporalArray::Null => Err(MinarrowError::NullError { message: None }),
486        }
487    }
488
489    // Comparison - match on (self, other) tuple, verify same variant
490
491    fn diff(&self, other: &Self, unit: TimeUnit) -> Result<IntegerArray<i64>, MinarrowError> {
492        match (self, other) {
493            (TemporalArray::Datetime32(a), TemporalArray::Datetime32(b)) => a.diff(b, unit),
494            (TemporalArray::Datetime64(a), TemporalArray::Datetime64(b)) => a.diff(b, unit),
495            (TemporalArray::Null, _) | (_, TemporalArray::Null) => {
496                Err(MinarrowError::NullError { message: None })
497            }
498            _ => Err(MinarrowError::TypeError {
499                from: "TemporalArray",
500                to: "TemporalArray",
501                message: Some("Mismatched temporal variants".to_string()),
502            }),
503        }
504    }
505
506    fn abs_diff(&self, other: &Self, unit: TimeUnit) -> Result<IntegerArray<i64>, MinarrowError> {
507        match (self, other) {
508            (TemporalArray::Datetime32(a), TemporalArray::Datetime32(b)) => a.abs_diff(b, unit),
509            (TemporalArray::Datetime64(a), TemporalArray::Datetime64(b)) => a.abs_diff(b, unit),
510            (TemporalArray::Null, _) | (_, TemporalArray::Null) => {
511                Err(MinarrowError::NullError { message: None })
512            }
513            _ => Err(MinarrowError::TypeError {
514                from: "TemporalArray",
515                to: "TemporalArray",
516                message: Some("Mismatched temporal variants".to_string()),
517            }),
518        }
519    }
520
521    fn is_before(&self, other: &Self) -> Result<BooleanArray<()>, MinarrowError> {
522        match (self, other) {
523            (TemporalArray::Datetime32(a), TemporalArray::Datetime32(b)) => a.is_before(b),
524            (TemporalArray::Datetime64(a), TemporalArray::Datetime64(b)) => a.is_before(b),
525            (TemporalArray::Null, _) | (_, TemporalArray::Null) => {
526                Err(MinarrowError::NullError { message: None })
527            }
528            _ => Err(MinarrowError::TypeError {
529                from: "TemporalArray",
530                to: "TemporalArray",
531                message: Some("Mismatched temporal variants".to_string()),
532            }),
533        }
534    }
535
536    fn is_after(&self, other: &Self) -> Result<BooleanArray<()>, MinarrowError> {
537        match (self, other) {
538            (TemporalArray::Datetime32(a), TemporalArray::Datetime32(b)) => a.is_after(b),
539            (TemporalArray::Datetime64(a), TemporalArray::Datetime64(b)) => a.is_after(b),
540            (TemporalArray::Null, _) | (_, TemporalArray::Null) => {
541                Err(MinarrowError::NullError { message: None })
542            }
543            _ => Err(MinarrowError::TypeError {
544                from: "TemporalArray",
545                to: "TemporalArray",
546                message: Some("Mismatched temporal variants".to_string()),
547            }),
548        }
549    }
550
551    fn between(&self, start: &Self, end: &Self) -> Result<BooleanArray<()>, MinarrowError> {
552        match (self, start, end) {
553            (
554                TemporalArray::Datetime32(a),
555                TemporalArray::Datetime32(s),
556                TemporalArray::Datetime32(e),
557            ) => a.between(s, e),
558            (
559                TemporalArray::Datetime64(a),
560                TemporalArray::Datetime64(s),
561                TemporalArray::Datetime64(e),
562            ) => a.between(s, e),
563            (TemporalArray::Null, _, _)
564            | (_, TemporalArray::Null, _)
565            | (_, _, TemporalArray::Null) => Err(MinarrowError::NullError { message: None }),
566            _ => Err(MinarrowError::TypeError {
567                from: "TemporalArray",
568                to: "TemporalArray",
569                message: Some("Mismatched temporal variants".to_string()),
570            }),
571        }
572    }
573
574    // Truncation - delegate, wrap result back into enum variant
575
576    fn truncate(&self, unit: &str) -> Result<Self, MinarrowError> {
577        match self {
578            TemporalArray::Datetime32(arr) => {
579                Ok(TemporalArray::Datetime32(Arc::new(arr.truncate(unit)?)))
580            }
581            TemporalArray::Datetime64(arr) => {
582                Ok(TemporalArray::Datetime64(Arc::new(arr.truncate(unit)?)))
583            }
584            TemporalArray::Null => Err(MinarrowError::NullError { message: None }),
585        }
586    }
587
588    fn us(&self) -> Self {
589        match self {
590            TemporalArray::Datetime32(arr) => TemporalArray::Datetime32(Arc::new(arr.us())),
591            TemporalArray::Datetime64(arr) => TemporalArray::Datetime64(Arc::new(arr.us())),
592            TemporalArray::Null => TemporalArray::Null,
593        }
594    }
595
596    fn ms(&self) -> Self {
597        match self {
598            TemporalArray::Datetime32(arr) => TemporalArray::Datetime32(Arc::new(arr.ms())),
599            TemporalArray::Datetime64(arr) => TemporalArray::Datetime64(Arc::new(arr.ms())),
600            TemporalArray::Null => TemporalArray::Null,
601        }
602    }
603
604    fn sec(&self) -> Self {
605        match self {
606            TemporalArray::Datetime32(arr) => TemporalArray::Datetime32(Arc::new(arr.sec())),
607            TemporalArray::Datetime64(arr) => TemporalArray::Datetime64(Arc::new(arr.sec())),
608            TemporalArray::Null => TemporalArray::Null,
609        }
610    }
611
612    fn min(&self) -> Self {
613        match self {
614            TemporalArray::Datetime32(arr) => TemporalArray::Datetime32(Arc::new(arr.min())),
615            TemporalArray::Datetime64(arr) => TemporalArray::Datetime64(Arc::new(arr.min())),
616            TemporalArray::Null => TemporalArray::Null,
617        }
618    }
619
620    fn hr(&self) -> Self {
621        match self {
622            TemporalArray::Datetime32(arr) => TemporalArray::Datetime32(Arc::new(arr.hr())),
623            TemporalArray::Datetime64(arr) => TemporalArray::Datetime64(Arc::new(arr.hr())),
624            TemporalArray::Null => TemporalArray::Null,
625        }
626    }
627
628    fn week(&self) -> Self {
629        match self {
630            TemporalArray::Datetime32(arr) => TemporalArray::Datetime32(Arc::new(arr.week())),
631            TemporalArray::Datetime64(arr) => TemporalArray::Datetime64(Arc::new(arr.week())),
632            TemporalArray::Null => TemporalArray::Null,
633        }
634    }
635
636    // Type Casting
637
638    fn cast_time_unit(&self, new_unit: TimeUnit) -> Result<Self, MinarrowError> {
639        match self {
640            TemporalArray::Datetime32(arr) => Ok(TemporalArray::Datetime32(Arc::new(
641                arr.cast_time_unit(new_unit)?,
642            ))),
643            TemporalArray::Datetime64(arr) => Ok(TemporalArray::Datetime64(Arc::new(
644                arr.cast_time_unit(new_unit)?,
645            ))),
646            TemporalArray::Null => Err(MinarrowError::NullError { message: None }),
647        }
648    }
649}
650
651/// Helper function to get the variant name for error messages
652fn temporal_variant_name(arr: &TemporalArray) -> &'static str {
653    match arr {
654        TemporalArray::Datetime32(_) => "Datetime32",
655        TemporalArray::Datetime64(_) => "Datetime64",
656        TemporalArray::Null => "Null",
657    }
658}
659
660impl Display for TemporalArray {
661    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
662        match self {
663            TemporalArray::Datetime32(arr) => {
664                write_temporal_array_with_header(f, "Datetime32", arr.as_ref())
665            }
666            TemporalArray::Datetime64(arr) => {
667                write_temporal_array_with_header(f, "Datetime64", arr.as_ref())
668            }
669            TemporalArray::Null => writeln!(f, "TemporalArray::Null [0 values]"),
670        }
671    }
672}
673
674/// Writes the standard header, then delegates to the contained array's Display.
675fn write_temporal_array_with_header(
676    f: &mut Formatter<'_>,
677    dtype: &str,
678    arr: &(impl MaskedArray + Display + ?Sized),
679) -> std::fmt::Result {
680    writeln!(
681        f,
682        "TemporalArray [{dtype}] [{} values] (null count: {})",
683        arr.len(),
684        arr.null_count()
685    )?;
686    Display::fmt(arr, f)
687}