typed_arrow/bridge/
temporal.rs

1//! Temporal types: Date, Time, Duration, Timestamp (with/without timezone).
2
3use std::{marker::PhantomData, sync::Arc};
4
5use arrow_array::{
6    Array, PrimitiveArray,
7    builder::PrimitiveBuilder,
8    types::{
9        Date32Type, Date64Type, DurationMicrosecondType, DurationMillisecondType,
10        DurationNanosecondType, DurationSecondType, Time32MillisecondType, Time32SecondType,
11        Time64MicrosecondType, Time64NanosecondType, TimestampMicrosecondType,
12        TimestampMillisecondType, TimestampNanosecondType, TimestampSecondType,
13    },
14};
15use arrow_schema::{DataType, TimeUnit};
16
17use super::ArrowBinding;
18#[cfg(feature = "views")]
19use super::ArrowBindingView;
20
21// ---------- Timestamp units and bindings ----------
22
23/// Marker describing a timestamp unit.
24pub trait TimeUnitSpec {
25    /// Typed Arrow timestamp marker for this unit.
26    type Arrow: arrow_array::types::ArrowTimestampType;
27    /// The `arrow_schema::TimeUnit` of this marker.
28    fn unit() -> TimeUnit;
29}
30
31/// Seconds since epoch.
32pub enum Second {}
33impl TimeUnitSpec for Second {
34    type Arrow = TimestampSecondType;
35    fn unit() -> TimeUnit {
36        TimeUnit::Second
37    }
38}
39
40/// Milliseconds since epoch.
41pub enum Millisecond {}
42impl TimeUnitSpec for Millisecond {
43    type Arrow = TimestampMillisecondType;
44    fn unit() -> TimeUnit {
45        TimeUnit::Millisecond
46    }
47}
48
49/// Microseconds since epoch.
50pub enum Microsecond {}
51impl TimeUnitSpec for Microsecond {
52    type Arrow = TimestampMicrosecondType;
53    fn unit() -> TimeUnit {
54        TimeUnit::Microsecond
55    }
56}
57
58/// Nanoseconds since epoch.
59pub enum Nanosecond {}
60impl TimeUnitSpec for Nanosecond {
61    type Arrow = TimestampNanosecondType;
62    fn unit() -> TimeUnit {
63        TimeUnit::Nanosecond
64    }
65}
66
67/// Timestamp value (unit only, timezone = None).
68#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
69pub struct Timestamp<U: TimeUnitSpec>(i64, PhantomData<U>);
70impl<U: TimeUnitSpec> Timestamp<U> {
71    /// Construct a new timestamp from an epoch value in the unit `U`.
72    #[inline]
73    #[must_use]
74    pub fn new(value: i64) -> Self {
75        Self(value, PhantomData)
76    }
77    /// Return the inner epoch value.
78    #[inline]
79    #[must_use]
80    pub fn value(&self) -> i64 {
81        self.0
82    }
83    /// Consume and return the inner epoch value.
84    #[inline]
85    #[must_use]
86    pub fn into_value(self) -> i64 {
87        self.0
88    }
89}
90impl<U: TimeUnitSpec> ArrowBinding for Timestamp<U> {
91    type Builder = PrimitiveBuilder<U::Arrow>;
92    type Array = PrimitiveArray<U::Arrow>;
93    fn data_type() -> DataType {
94        DataType::Timestamp(U::unit(), None)
95    }
96    fn new_builder(capacity: usize) -> Self::Builder {
97        PrimitiveBuilder::<U::Arrow>::with_capacity(capacity)
98    }
99    fn append_value(b: &mut Self::Builder, v: &Self) {
100        b.append_value(v.0);
101    }
102    fn append_null(b: &mut Self::Builder) {
103        b.append_null();
104    }
105    fn finish(mut b: Self::Builder) -> Self::Array {
106        b.finish()
107    }
108}
109
110#[cfg(feature = "views")]
111impl<U: TimeUnitSpec + 'static> ArrowBindingView for Timestamp<U> {
112    type Array = PrimitiveArray<U::Arrow>;
113    type View<'a> = Timestamp<U>;
114
115    fn get_view(
116        array: &Self::Array,
117        index: usize,
118    ) -> Result<Self::View<'_>, crate::schema::ViewAccessError> {
119        if index >= array.len() {
120            return Err(crate::schema::ViewAccessError::OutOfBounds {
121                index,
122                len: array.len(),
123                field_name: None,
124            });
125        }
126        if array.is_null(index) {
127            return Err(crate::schema::ViewAccessError::UnexpectedNull {
128                index,
129                field_name: None,
130            });
131        }
132        Ok(Timestamp::new(array.value(index)))
133    }
134}
135
136/// Marker describing a timestamp timezone.
137pub trait TimeZoneSpec {
138    /// The optional timezone name for this marker.
139    const NAME: Option<&'static str>;
140}
141
142/// UTC timezone marker.
143pub enum Utc {}
144impl TimeZoneSpec for Utc {
145    const NAME: Option<&'static str> = Some("UTC");
146}
147
148/// Timestamp with time unit `U` and timezone marker `Z`.
149#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
150pub struct TimestampTz<U: TimeUnitSpec, Z: TimeZoneSpec>(i64, PhantomData<(U, Z)>);
151impl<U: TimeUnitSpec, Z: TimeZoneSpec> TimestampTz<U, Z> {
152    /// Construct a new timezone-aware timestamp from an epoch value in the unit `U`.
153    #[inline]
154    #[must_use]
155    pub fn new(value: i64) -> Self {
156        Self(value, PhantomData)
157    }
158    /// Return the inner epoch value.
159    #[inline]
160    #[must_use]
161    pub fn value(&self) -> i64 {
162        self.0
163    }
164    /// Consume and return the inner epoch value.
165    #[inline]
166    #[must_use]
167    pub fn into_value(self) -> i64 {
168        self.0
169    }
170}
171impl<U: TimeUnitSpec, Z: TimeZoneSpec> ArrowBinding for TimestampTz<U, Z> {
172    type Builder = PrimitiveBuilder<U::Arrow>;
173    type Array = PrimitiveArray<U::Arrow>;
174    fn data_type() -> DataType {
175        DataType::Timestamp(U::unit(), Z::NAME.map(Arc::<str>::from))
176    }
177    fn new_builder(capacity: usize) -> Self::Builder {
178        PrimitiveBuilder::<U::Arrow>::with_capacity(capacity)
179    }
180    fn append_value(b: &mut Self::Builder, v: &Self) {
181        b.append_value(v.0);
182    }
183    fn append_null(b: &mut Self::Builder) {
184        b.append_null();
185    }
186    fn finish(mut b: Self::Builder) -> Self::Array {
187        b.finish()
188    }
189}
190
191#[cfg(feature = "views")]
192impl<U: TimeUnitSpec + 'static, Z: TimeZoneSpec + 'static> ArrowBindingView for TimestampTz<U, Z> {
193    type Array = PrimitiveArray<U::Arrow>;
194    type View<'a> = TimestampTz<U, Z>;
195
196    fn get_view(
197        array: &Self::Array,
198        index: usize,
199    ) -> Result<Self::View<'_>, crate::schema::ViewAccessError> {
200        if index >= array.len() {
201            return Err(crate::schema::ViewAccessError::OutOfBounds {
202                index,
203                len: array.len(),
204                field_name: None,
205            });
206        }
207        if array.is_null(index) {
208            return Err(crate::schema::ViewAccessError::UnexpectedNull {
209                index,
210                field_name: None,
211            });
212        }
213        Ok(TimestampTz::new(array.value(index)))
214    }
215}
216
217// ---------- Date32 / Date64 ----------
218
219/// Days since UNIX epoch.
220#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
221pub struct Date32(i32);
222impl Date32 {
223    /// Construct a new `Date32` from days since UNIX epoch.
224    #[inline]
225    #[must_use]
226    pub fn new(value: i32) -> Self {
227        Self(value)
228    }
229    /// Return the days since UNIX epoch.
230    #[inline]
231    #[must_use]
232    pub fn value(&self) -> i32 {
233        self.0
234    }
235    /// Consume and return the days since UNIX epoch.
236    #[inline]
237    #[must_use]
238    pub fn into_value(self) -> i32 {
239        self.0
240    }
241}
242impl ArrowBinding for Date32 {
243    type Builder = PrimitiveBuilder<Date32Type>;
244    type Array = PrimitiveArray<Date32Type>;
245    fn data_type() -> DataType {
246        DataType::Date32
247    }
248    fn new_builder(capacity: usize) -> Self::Builder {
249        PrimitiveBuilder::<Date32Type>::with_capacity(capacity)
250    }
251    fn append_value(b: &mut Self::Builder, v: &Self) {
252        b.append_value(v.0);
253    }
254    fn append_null(b: &mut Self::Builder) {
255        b.append_null();
256    }
257    fn finish(mut b: Self::Builder) -> Self::Array {
258        b.finish()
259    }
260}
261
262#[cfg(feature = "views")]
263impl ArrowBindingView for Date32 {
264    type Array = PrimitiveArray<Date32Type>;
265    type View<'a> = Date32;
266
267    fn get_view(
268        array: &Self::Array,
269        index: usize,
270    ) -> Result<Self::View<'_>, crate::schema::ViewAccessError> {
271        if index >= array.len() {
272            return Err(crate::schema::ViewAccessError::OutOfBounds {
273                index,
274                len: array.len(),
275                field_name: None,
276            });
277        }
278        if array.is_null(index) {
279            return Err(crate::schema::ViewAccessError::UnexpectedNull {
280                index,
281                field_name: None,
282            });
283        }
284        Ok(Date32::new(array.value(index)))
285    }
286}
287
288/// Milliseconds since UNIX epoch.
289#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
290pub struct Date64(i64);
291impl Date64 {
292    /// Construct a new `Date64` from milliseconds since UNIX epoch.
293    #[inline]
294    #[must_use]
295    pub fn new(value: i64) -> Self {
296        Self(value)
297    }
298    /// Return the milliseconds since UNIX epoch.
299    #[inline]
300    #[must_use]
301    pub fn value(&self) -> i64 {
302        self.0
303    }
304    /// Consume and return the milliseconds since UNIX epoch.
305    #[inline]
306    #[must_use]
307    pub fn into_value(self) -> i64 {
308        self.0
309    }
310}
311impl ArrowBinding for Date64 {
312    type Builder = PrimitiveBuilder<Date64Type>;
313    type Array = PrimitiveArray<Date64Type>;
314    fn data_type() -> DataType {
315        DataType::Date64
316    }
317    fn new_builder(capacity: usize) -> Self::Builder {
318        PrimitiveBuilder::<Date64Type>::with_capacity(capacity)
319    }
320    fn append_value(b: &mut Self::Builder, v: &Self) {
321        b.append_value(v.0);
322    }
323    fn append_null(b: &mut Self::Builder) {
324        b.append_null();
325    }
326    fn finish(mut b: Self::Builder) -> Self::Array {
327        b.finish()
328    }
329}
330
331#[cfg(feature = "views")]
332impl ArrowBindingView for Date64 {
333    type Array = PrimitiveArray<Date64Type>;
334    type View<'a> = Date64;
335
336    fn get_view(
337        array: &Self::Array,
338        index: usize,
339    ) -> Result<Self::View<'_>, crate::schema::ViewAccessError> {
340        if index >= array.len() {
341            return Err(crate::schema::ViewAccessError::OutOfBounds {
342                index,
343                len: array.len(),
344                field_name: None,
345            });
346        }
347        if array.is_null(index) {
348            return Err(crate::schema::ViewAccessError::UnexpectedNull {
349                index,
350                field_name: None,
351            });
352        }
353        Ok(Date64::new(array.value(index)))
354    }
355}
356
357// ---------- Time32 / Time64 ----------
358
359/// Marker mapping for `Time32` units to Arrow time types.
360pub trait Time32UnitSpec {
361    type Arrow;
362    fn unit() -> TimeUnit;
363}
364impl Time32UnitSpec for Second {
365    type Arrow = Time32SecondType;
366    fn unit() -> TimeUnit {
367        TimeUnit::Second
368    }
369}
370impl Time32UnitSpec for Millisecond {
371    type Arrow = Time32MillisecondType;
372    fn unit() -> TimeUnit {
373        TimeUnit::Millisecond
374    }
375}
376
377/// Number of seconds/milliseconds since midnight.
378pub struct Time32<U: Time32UnitSpec>(i32, PhantomData<U>);
379impl<U: Time32UnitSpec> Time32<U> {
380    /// Construct a new `Time32` value from an `i32` count in unit `U`.
381    #[inline]
382    #[must_use]
383    pub fn new(value: i32) -> Self {
384        Self(value, PhantomData)
385    }
386    /// Return the inner value.
387    #[inline]
388    #[must_use]
389    pub fn value(&self) -> i32 {
390        self.0
391    }
392    /// Consume and return the inner value.
393    #[inline]
394    #[must_use]
395    pub fn into_value(self) -> i32 {
396        self.0
397    }
398}
399impl<U: Time32UnitSpec> ArrowBinding for Time32<U>
400where
401    U::Arrow: arrow_array::types::ArrowPrimitiveType<Native = i32>,
402{
403    type Builder = PrimitiveBuilder<U::Arrow>;
404    type Array = PrimitiveArray<U::Arrow>;
405    fn data_type() -> DataType {
406        DataType::Time32(U::unit())
407    }
408    fn new_builder(capacity: usize) -> Self::Builder {
409        PrimitiveBuilder::<U::Arrow>::with_capacity(capacity)
410    }
411    fn append_value(b: &mut Self::Builder, v: &Self) {
412        b.append_value(v.0 as <U::Arrow as arrow_array::types::ArrowPrimitiveType>::Native);
413    }
414    fn append_null(b: &mut Self::Builder) {
415        b.append_null();
416    }
417    fn finish(mut b: Self::Builder) -> Self::Array {
418        b.finish()
419    }
420}
421
422#[cfg(feature = "views")]
423impl<U: Time32UnitSpec + 'static> ArrowBindingView for Time32<U>
424where
425    U::Arrow: arrow_array::types::ArrowPrimitiveType<Native = i32>,
426{
427    type Array = PrimitiveArray<U::Arrow>;
428    type View<'a> = Time32<U>;
429
430    fn get_view(
431        array: &Self::Array,
432        index: usize,
433    ) -> Result<Self::View<'_>, crate::schema::ViewAccessError> {
434        if index >= array.len() {
435            return Err(crate::schema::ViewAccessError::OutOfBounds {
436                index,
437                len: array.len(),
438                field_name: None,
439            });
440        }
441        if array.is_null(index) {
442            return Err(crate::schema::ViewAccessError::UnexpectedNull {
443                index,
444                field_name: None,
445            });
446        }
447        Ok(Time32::new(array.value(index)))
448    }
449}
450
451/// Marker mapping for `Time64` units to Arrow time types.
452pub trait Time64UnitSpec {
453    type Arrow;
454    fn unit() -> TimeUnit;
455}
456impl Time64UnitSpec for Microsecond {
457    type Arrow = Time64MicrosecondType;
458    fn unit() -> TimeUnit {
459        TimeUnit::Microsecond
460    }
461}
462impl Time64UnitSpec for Nanosecond {
463    type Arrow = Time64NanosecondType;
464    fn unit() -> TimeUnit {
465        TimeUnit::Nanosecond
466    }
467}
468
469/// Number of microseconds/nanoseconds since midnight.
470pub struct Time64<U: Time64UnitSpec>(i64, PhantomData<U>);
471impl<U: Time64UnitSpec> Time64<U> {
472    /// Construct a new `Time64` value from an `i64` count in unit `U`.
473    #[inline]
474    #[must_use]
475    pub fn new(value: i64) -> Self {
476        Self(value, PhantomData)
477    }
478    /// Return the inner value.
479    #[inline]
480    #[must_use]
481    pub fn value(&self) -> i64 {
482        self.0
483    }
484    /// Consume and return the inner value.
485    #[inline]
486    #[must_use]
487    pub fn into_value(self) -> i64 {
488        self.0
489    }
490}
491impl<U: Time64UnitSpec> ArrowBinding for Time64<U>
492where
493    U::Arrow: arrow_array::types::ArrowPrimitiveType<Native = i64>,
494{
495    type Builder = PrimitiveBuilder<U::Arrow>;
496    type Array = PrimitiveArray<U::Arrow>;
497    fn data_type() -> DataType {
498        DataType::Time64(U::unit())
499    }
500    fn new_builder(capacity: usize) -> Self::Builder {
501        PrimitiveBuilder::<U::Arrow>::with_capacity(capacity)
502    }
503    fn append_value(b: &mut Self::Builder, v: &Self) {
504        b.append_value(v.0 as <U::Arrow as arrow_array::types::ArrowPrimitiveType>::Native);
505    }
506    fn append_null(b: &mut Self::Builder) {
507        b.append_null();
508    }
509    fn finish(mut b: Self::Builder) -> Self::Array {
510        b.finish()
511    }
512}
513
514#[cfg(feature = "views")]
515impl<U: Time64UnitSpec + 'static> ArrowBindingView for Time64<U>
516where
517    U::Arrow: arrow_array::types::ArrowPrimitiveType<Native = i64>,
518{
519    type Array = PrimitiveArray<U::Arrow>;
520    type View<'a> = Time64<U>;
521
522    fn get_view(
523        array: &Self::Array,
524        index: usize,
525    ) -> Result<Self::View<'_>, crate::schema::ViewAccessError> {
526        if index >= array.len() {
527            return Err(crate::schema::ViewAccessError::OutOfBounds {
528                index,
529                len: array.len(),
530                field_name: None,
531            });
532        }
533        if array.is_null(index) {
534            return Err(crate::schema::ViewAccessError::UnexpectedNull {
535                index,
536                field_name: None,
537            });
538        }
539        Ok(Time64::new(array.value(index)))
540    }
541}
542
543// ---------- Duration ----------
544
545/// Marker mapping for `Duration` units to Arrow duration types.
546pub trait DurationUnitSpec {
547    type Arrow;
548    fn unit() -> TimeUnit;
549}
550impl DurationUnitSpec for Second {
551    type Arrow = DurationSecondType;
552    fn unit() -> TimeUnit {
553        TimeUnit::Second
554    }
555}
556impl DurationUnitSpec for Millisecond {
557    type Arrow = DurationMillisecondType;
558    fn unit() -> TimeUnit {
559        TimeUnit::Millisecond
560    }
561}
562impl DurationUnitSpec for Microsecond {
563    type Arrow = DurationMicrosecondType;
564    fn unit() -> TimeUnit {
565        TimeUnit::Microsecond
566    }
567}
568impl DurationUnitSpec for Nanosecond {
569    type Arrow = DurationNanosecondType;
570    fn unit() -> TimeUnit {
571        TimeUnit::Nanosecond
572    }
573}
574
575/// Duration in the given unit.
576#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
577pub struct Duration<U: DurationUnitSpec>(i64, PhantomData<U>);
578impl<U: DurationUnitSpec> Duration<U> {
579    /// Construct a new duration from an `i64` count in unit `U`.
580    #[inline]
581    #[must_use]
582    pub fn new(value: i64) -> Self {
583        Self(value, PhantomData)
584    }
585    /// Return the inner value.
586    #[inline]
587    #[must_use]
588    pub fn value(&self) -> i64 {
589        self.0
590    }
591    /// Consume and return the inner value.
592    #[inline]
593    #[must_use]
594    pub fn into_value(self) -> i64 {
595        self.0
596    }
597}
598impl<U: DurationUnitSpec> ArrowBinding for Duration<U>
599where
600    U::Arrow: arrow_array::types::ArrowPrimitiveType<Native = i64>,
601{
602    type Builder = PrimitiveBuilder<U::Arrow>;
603    type Array = PrimitiveArray<U::Arrow>;
604    fn data_type() -> DataType {
605        DataType::Duration(U::unit())
606    }
607    fn new_builder(capacity: usize) -> Self::Builder {
608        PrimitiveBuilder::<U::Arrow>::with_capacity(capacity)
609    }
610    fn append_value(b: &mut Self::Builder, v: &Self) {
611        b.append_value(v.0);
612    }
613    fn append_null(b: &mut Self::Builder) {
614        b.append_null();
615    }
616    fn finish(mut b: Self::Builder) -> Self::Array {
617        b.finish()
618    }
619}
620
621#[cfg(feature = "views")]
622impl<U: DurationUnitSpec + 'static> ArrowBindingView for Duration<U>
623where
624    U::Arrow: arrow_array::types::ArrowPrimitiveType<Native = i64>,
625{
626    type Array = PrimitiveArray<U::Arrow>;
627    type View<'a> = Duration<U>;
628
629    fn get_view(
630        array: &Self::Array,
631        index: usize,
632    ) -> Result<Self::View<'_>, crate::schema::ViewAccessError> {
633        if index >= array.len() {
634            return Err(crate::schema::ViewAccessError::OutOfBounds {
635                index,
636                len: array.len(),
637                field_name: None,
638            });
639        }
640        if array.is_null(index) {
641            return Err(crate::schema::ViewAccessError::UnexpectedNull {
642                index,
643                field_name: None,
644            });
645        }
646        Ok(Duration::new(array.value(index)))
647    }
648}