polars_core/chunked_array/logical/
time.rs

1use polars_compute::cast::CastOptionsImpl;
2
3use super::*;
4use crate::prelude::*;
5
6pub type TimeChunked = Logical<TimeType, Int64Type>;
7
8impl From<Int64Chunked> for TimeChunked {
9    fn from(ca: Int64Chunked) -> Self {
10        TimeChunked::new_logical(ca)
11    }
12}
13
14impl Int64Chunked {
15    pub fn into_time(mut self) -> TimeChunked {
16        let mut null_count = 0;
17
18        // Invalid time values are replaced with `null` during the arrow cast. We utilize the
19        // validity coming from there to create the new TimeChunked.
20        let chunks = std::mem::take(&mut self.chunks)
21            .into_iter()
22            .map(|chunk| {
23                // We need to retain the PhysicalType underneath, but we should properly update the
24                // validity as that might change because Time is not valid for all values of Int64.
25                let casted = polars_compute::cast::cast(
26                    chunk.as_ref(),
27                    &ArrowDataType::Time64(ArrowTimeUnit::Nanosecond),
28                    CastOptionsImpl::default(),
29                )
30                .unwrap();
31                let validity = casted.validity();
32
33                match validity {
34                    None => chunk,
35                    Some(validity) => {
36                        null_count += validity.unset_bits();
37                        chunk.with_validity(Some(validity.clone()))
38                    },
39                }
40            })
41            .collect::<Vec<Box<dyn Array>>>();
42
43        debug_assert!(null_count >= self.null_count);
44
45        // @TODO: We throw away metadata here. That is mostly not needed.
46        // SAFETY: We calculated the null_count again. And we are taking the rest from the previous
47        // Int64Chunked.
48        let int64chunked =
49            unsafe { Self::new_with_dims(self.field.clone(), chunks, self.length, null_count) };
50
51        TimeChunked::new_logical(int64chunked)
52    }
53}
54
55impl LogicalType for TimeChunked {
56    fn dtype(&self) -> &'static DataType {
57        &DataType::Time
58    }
59
60    #[cfg(feature = "dtype-time")]
61    fn get_any_value(&self, i: usize) -> PolarsResult<AnyValue<'_>> {
62        self.0.get_any_value(i).map(|av| av.as_time())
63    }
64    unsafe fn get_any_value_unchecked(&self, i: usize) -> AnyValue<'_> {
65        self.0.get_any_value_unchecked(i).as_time()
66    }
67
68    fn cast_with_options(
69        &self,
70        dtype: &DataType,
71        cast_options: CastOptions,
72    ) -> PolarsResult<Series> {
73        use DataType::*;
74        match dtype {
75            Time => Ok(self.clone().into_series()),
76            #[cfg(feature = "dtype-duration")]
77            Duration(tu) => {
78                let out = self
79                    .0
80                    .cast_with_options(&DataType::Duration(TimeUnit::Nanoseconds), cast_options);
81                if !matches!(tu, TimeUnit::Nanoseconds) {
82                    out?.cast_with_options(dtype, cast_options)
83                } else {
84                    out
85                }
86            },
87            #[cfg(feature = "dtype-datetime")]
88            Datetime(_, _) => {
89                polars_bail!(
90                    InvalidOperation:
91                    "casting from {:?} to {:?} not supported; consider using `dt.combine`",
92                    self.dtype(), dtype
93                )
94            },
95            dt if dt.is_primitive_numeric() => self.0.cast_with_options(dtype, cast_options),
96            _ => {
97                polars_bail!(
98                    InvalidOperation:
99                    "casting from {:?} to {:?} not supported",
100                    self.dtype(), dtype
101                )
102            },
103        }
104    }
105}