typed_arrow/bridge/
temporal.rs

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