Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pola-rs
GitHub Repository: pola-rs/polars
Path: blob/main/crates/polars-arrow/src/compute/temporal.rs
6939 views
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements. See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership. The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License. You may obtain a copy of the License at
8
//
9
// http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied. See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
//! Defines temporal kernels for time and date related functions.
19
20
use chrono::{Datelike, Timelike};
21
use polars_error::PolarsResult;
22
23
use super::arity::unary;
24
use crate::array::*;
25
use crate::datatypes::*;
26
use crate::temporal_conversions::*;
27
use crate::types::NativeType;
28
29
// Create and implement a trait that converts chrono's `Weekday`
30
// type into `i8`
31
trait Int8Weekday: Datelike {
32
fn i8_weekday(&self) -> i8 {
33
self.weekday().number_from_monday().try_into().unwrap()
34
}
35
}
36
37
impl Int8Weekday for chrono::NaiveDateTime {}
38
impl<T: chrono::TimeZone> Int8Weekday for chrono::DateTime<T> {}
39
40
// Create and implement a trait that converts chrono's `IsoWeek`
41
// type into `i8`
42
trait Int8IsoWeek: Datelike {
43
fn i8_iso_week(&self) -> i8 {
44
self.iso_week().week().try_into().unwrap()
45
}
46
}
47
48
impl Int8IsoWeek for chrono::NaiveDateTime {}
49
impl<T: chrono::TimeZone> Int8IsoWeek for chrono::DateTime<T> {}
50
51
// Macro to avoid repetition in functions, that apply
52
// `chrono::Datelike` methods on Arrays
53
macro_rules! date_like {
54
($extract:ident, $array:ident, $dtype:path) => {
55
match $array.dtype().to_logical_type() {
56
ArrowDataType::Date32 | ArrowDataType::Date64 | ArrowDataType::Timestamp(_, None) => {
57
date_variants($array, $dtype, |x| x.$extract().try_into().unwrap())
58
},
59
ArrowDataType::Timestamp(time_unit, Some(timezone_str)) => {
60
let array = $array.as_any().downcast_ref().unwrap();
61
62
if let Ok(timezone) = parse_offset(timezone_str.as_str()) {
63
Ok(extract_impl(array, *time_unit, timezone, |x| {
64
x.$extract().try_into().unwrap()
65
}))
66
} else {
67
chrono_tz(array, *time_unit, timezone_str.as_str(), |x| {
68
x.$extract().try_into().unwrap()
69
})
70
}
71
},
72
_ => unimplemented!(),
73
}
74
};
75
}
76
77
/// Extracts the years of a temporal array as [`PrimitiveArray<i32>`].
78
pub fn year(array: &dyn Array) -> PolarsResult<PrimitiveArray<i32>> {
79
date_like!(year, array, ArrowDataType::Int32)
80
}
81
82
/// Extracts the months of a temporal array as [`PrimitiveArray<i8>`].
83
///
84
/// Value ranges from 1 to 12.
85
pub fn month(array: &dyn Array) -> PolarsResult<PrimitiveArray<i8>> {
86
date_like!(month, array, ArrowDataType::Int8)
87
}
88
89
/// Extracts the days of a temporal array as [`PrimitiveArray<i8>`].
90
///
91
/// Value ranges from 1 to 32 (Last day depends on month).
92
pub fn day(array: &dyn Array) -> PolarsResult<PrimitiveArray<i8>> {
93
date_like!(day, array, ArrowDataType::Int8)
94
}
95
96
/// Extracts weekday of a temporal array as [`PrimitiveArray<i8>`].
97
///
98
/// Monday is 1, Tuesday is 2, ..., Sunday is 7.
99
pub fn weekday(array: &dyn Array) -> PolarsResult<PrimitiveArray<i8>> {
100
date_like!(i8_weekday, array, ArrowDataType::Int8)
101
}
102
103
/// Extracts ISO week of a temporal array as [`PrimitiveArray<i8>`].
104
///
105
/// Value ranges from 1 to 53 (Last week depends on the year).
106
pub fn iso_week(array: &dyn Array) -> PolarsResult<PrimitiveArray<i8>> {
107
date_like!(i8_iso_week, array, ArrowDataType::Int8)
108
}
109
110
// Macro to avoid repetition in functions, that apply
111
// `chrono::Timelike` methods on Arrays
112
macro_rules! time_like {
113
($extract:ident, $array:ident, $dtype:path) => {
114
match $array.dtype().to_logical_type() {
115
ArrowDataType::Date32 | ArrowDataType::Date64 | ArrowDataType::Timestamp(_, None) => {
116
date_variants($array, $dtype, |x| x.$extract().try_into().unwrap())
117
},
118
ArrowDataType::Time32(_) | ArrowDataType::Time64(_) => {
119
time_variants($array, ArrowDataType::UInt32, |x| {
120
x.$extract().try_into().unwrap()
121
})
122
},
123
ArrowDataType::Timestamp(time_unit, Some(timezone_str)) => {
124
let array = $array.as_any().downcast_ref().unwrap();
125
126
if let Ok(timezone) = parse_offset(timezone_str.as_str()) {
127
Ok(extract_impl(array, *time_unit, timezone, |x| {
128
x.$extract().try_into().unwrap()
129
}))
130
} else {
131
chrono_tz(array, *time_unit, timezone_str.as_str(), |x| {
132
x.$extract().try_into().unwrap()
133
})
134
}
135
},
136
_ => unimplemented!(),
137
}
138
};
139
}
140
141
/// Extracts the hours of a temporal array as [`PrimitiveArray<i8>`].
142
/// Value ranges from 0 to 23.
143
/// Use [`can_hour`] to check if this operation is supported for the target [`ArrowDataType`].
144
pub fn hour(array: &dyn Array) -> PolarsResult<PrimitiveArray<i8>> {
145
time_like!(hour, array, ArrowDataType::Int8)
146
}
147
148
/// Extracts the minutes of a temporal array as [`PrimitiveArray<i8>`].
149
/// Value ranges from 0 to 59.
150
/// Use [`can_minute`] to check if this operation is supported for the target [`ArrowDataType`].
151
pub fn minute(array: &dyn Array) -> PolarsResult<PrimitiveArray<i8>> {
152
time_like!(minute, array, ArrowDataType::Int8)
153
}
154
155
/// Extracts the seconds of a temporal array as [`PrimitiveArray<i8>`].
156
/// Value ranges from 0 to 59.
157
/// Use [`can_second`] to check if this operation is supported for the target [`ArrowDataType`].
158
pub fn second(array: &dyn Array) -> PolarsResult<PrimitiveArray<i8>> {
159
time_like!(second, array, ArrowDataType::Int8)
160
}
161
162
/// Extracts the nanoseconds of a temporal array as [`PrimitiveArray<i32>`].
163
///
164
/// Value ranges from 0 to 1_999_999_999.
165
/// The range from 1_000_000_000 to 1_999_999_999 represents the leap second.
166
/// Use [`can_nanosecond`] to check if this operation is supported for the target [`ArrowDataType`].
167
pub fn nanosecond(array: &dyn Array) -> PolarsResult<PrimitiveArray<i32>> {
168
time_like!(nanosecond, array, ArrowDataType::Int32)
169
}
170
171
fn date_variants<F, O>(
172
array: &dyn Array,
173
dtype: ArrowDataType,
174
op: F,
175
) -> PolarsResult<PrimitiveArray<O>>
176
where
177
O: NativeType,
178
F: Fn(chrono::NaiveDateTime) -> O,
179
{
180
match array.dtype().to_logical_type() {
181
ArrowDataType::Date32 => {
182
let array = array
183
.as_any()
184
.downcast_ref::<PrimitiveArray<i32>>()
185
.unwrap();
186
Ok(unary(array, |x| op(date32_to_datetime(x)), dtype))
187
},
188
ArrowDataType::Date64 => {
189
let array = array
190
.as_any()
191
.downcast_ref::<PrimitiveArray<i64>>()
192
.unwrap();
193
Ok(unary(array, |x| op(date64_to_datetime(x)), dtype))
194
},
195
ArrowDataType::Timestamp(time_unit, None) => {
196
let array = array
197
.as_any()
198
.downcast_ref::<PrimitiveArray<i64>>()
199
.unwrap();
200
let func = match time_unit {
201
TimeUnit::Second => timestamp_s_to_datetime,
202
TimeUnit::Millisecond => timestamp_ms_to_datetime,
203
TimeUnit::Microsecond => timestamp_us_to_datetime,
204
TimeUnit::Nanosecond => timestamp_ns_to_datetime,
205
};
206
Ok(PrimitiveArray::<O>::from_trusted_len_iter(
207
array.iter().map(|v| v.map(|x| op(func(*x)))),
208
))
209
},
210
_ => unreachable!(),
211
}
212
}
213
214
fn time_variants<F, O>(
215
array: &dyn Array,
216
dtype: ArrowDataType,
217
op: F,
218
) -> PolarsResult<PrimitiveArray<O>>
219
where
220
O: NativeType,
221
F: Fn(chrono::NaiveTime) -> O,
222
{
223
match array.dtype().to_logical_type() {
224
ArrowDataType::Time32(TimeUnit::Second) => {
225
let array = array
226
.as_any()
227
.downcast_ref::<PrimitiveArray<i32>>()
228
.unwrap();
229
Ok(unary(array, |x| op(time32s_to_time(x)), dtype))
230
},
231
ArrowDataType::Time32(TimeUnit::Millisecond) => {
232
let array = array
233
.as_any()
234
.downcast_ref::<PrimitiveArray<i32>>()
235
.unwrap();
236
Ok(unary(array, |x| op(time32ms_to_time(x)), dtype))
237
},
238
ArrowDataType::Time64(TimeUnit::Microsecond) => {
239
let array = array
240
.as_any()
241
.downcast_ref::<PrimitiveArray<i64>>()
242
.unwrap();
243
Ok(unary(array, |x| op(time64us_to_time(x)), dtype))
244
},
245
ArrowDataType::Time64(TimeUnit::Nanosecond) => {
246
let array = array
247
.as_any()
248
.downcast_ref::<PrimitiveArray<i64>>()
249
.unwrap();
250
Ok(unary(array, |x| op(time64ns_to_time(x)), dtype))
251
},
252
_ => unreachable!(),
253
}
254
}
255
256
#[cfg(feature = "chrono-tz")]
257
fn chrono_tz<F, O>(
258
array: &PrimitiveArray<i64>,
259
time_unit: TimeUnit,
260
timezone_str: &str,
261
op: F,
262
) -> PolarsResult<PrimitiveArray<O>>
263
where
264
O: NativeType,
265
F: Fn(chrono::DateTime<chrono_tz::Tz>) -> O,
266
{
267
let timezone = parse_offset_tz(timezone_str)?;
268
Ok(extract_impl(array, time_unit, timezone, op))
269
}
270
271
#[cfg(not(feature = "chrono-tz"))]
272
fn chrono_tz<F, O>(
273
_: &PrimitiveArray<i64>,
274
_: TimeUnit,
275
timezone_str: &str,
276
_: F,
277
) -> PolarsResult<PrimitiveArray<O>>
278
where
279
O: NativeType,
280
F: Fn(chrono::DateTime<chrono::FixedOffset>) -> O,
281
{
282
panic!(
283
"timezone \"{}\" cannot be parsed (feature chrono-tz is not active)",
284
timezone_str
285
)
286
}
287
288
fn extract_impl<T, A, F>(
289
array: &PrimitiveArray<i64>,
290
time_unit: TimeUnit,
291
timezone: T,
292
extract: F,
293
) -> PrimitiveArray<A>
294
where
295
T: chrono::TimeZone,
296
A: NativeType,
297
F: Fn(chrono::DateTime<T>) -> A,
298
{
299
match time_unit {
300
TimeUnit::Second => {
301
let op = |x| {
302
let datetime = timestamp_s_to_datetime(x);
303
let offset = timezone.offset_from_utc_datetime(&datetime);
304
extract(chrono::DateTime::<T>::from_naive_utc_and_offset(
305
datetime, offset,
306
))
307
};
308
unary(array, op, A::PRIMITIVE.into())
309
},
310
TimeUnit::Millisecond => {
311
let op = |x| {
312
let datetime = timestamp_ms_to_datetime(x);
313
let offset = timezone.offset_from_utc_datetime(&datetime);
314
extract(chrono::DateTime::<T>::from_naive_utc_and_offset(
315
datetime, offset,
316
))
317
};
318
unary(array, op, A::PRIMITIVE.into())
319
},
320
TimeUnit::Microsecond => {
321
let op = |x| {
322
let datetime = timestamp_us_to_datetime(x);
323
let offset = timezone.offset_from_utc_datetime(&datetime);
324
extract(chrono::DateTime::<T>::from_naive_utc_and_offset(
325
datetime, offset,
326
))
327
};
328
unary(array, op, A::PRIMITIVE.into())
329
},
330
TimeUnit::Nanosecond => {
331
let op = |x| {
332
let datetime = timestamp_ns_to_datetime(x);
333
let offset = timezone.offset_from_utc_datetime(&datetime);
334
extract(chrono::DateTime::<T>::from_naive_utc_and_offset(
335
datetime, offset,
336
))
337
};
338
unary(array, op, A::PRIMITIVE.into())
339
},
340
}
341
}
342
343