Path: blob/main/crates/polars-arrow/src/compute/temporal.rs
6939 views
// Licensed to the Apache Software Foundation (ASF) under one1// or more contributor license agreements. See the NOTICE file2// distributed with this work for additional information3// regarding copyright ownership. The ASF licenses this file4// to you under the Apache License, Version 2.0 (the5// "License"); you may not use this file except in compliance6// with the License. You may obtain a copy of the License at7//8// http://www.apache.org/licenses/LICENSE-2.09//10// Unless required by applicable law or agreed to in writing,11// software distributed under the License is distributed on an12// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY13// KIND, either express or implied. See the License for the14// specific language governing permissions and limitations15// under the License.1617//! Defines temporal kernels for time and date related functions.1819use chrono::{Datelike, Timelike};20use polars_error::PolarsResult;2122use super::arity::unary;23use crate::array::*;24use crate::datatypes::*;25use crate::temporal_conversions::*;26use crate::types::NativeType;2728// Create and implement a trait that converts chrono's `Weekday`29// type into `i8`30trait Int8Weekday: Datelike {31fn i8_weekday(&self) -> i8 {32self.weekday().number_from_monday().try_into().unwrap()33}34}3536impl Int8Weekday for chrono::NaiveDateTime {}37impl<T: chrono::TimeZone> Int8Weekday for chrono::DateTime<T> {}3839// Create and implement a trait that converts chrono's `IsoWeek`40// type into `i8`41trait Int8IsoWeek: Datelike {42fn i8_iso_week(&self) -> i8 {43self.iso_week().week().try_into().unwrap()44}45}4647impl Int8IsoWeek for chrono::NaiveDateTime {}48impl<T: chrono::TimeZone> Int8IsoWeek for chrono::DateTime<T> {}4950// Macro to avoid repetition in functions, that apply51// `chrono::Datelike` methods on Arrays52macro_rules! date_like {53($extract:ident, $array:ident, $dtype:path) => {54match $array.dtype().to_logical_type() {55ArrowDataType::Date32 | ArrowDataType::Date64 | ArrowDataType::Timestamp(_, None) => {56date_variants($array, $dtype, |x| x.$extract().try_into().unwrap())57},58ArrowDataType::Timestamp(time_unit, Some(timezone_str)) => {59let array = $array.as_any().downcast_ref().unwrap();6061if let Ok(timezone) = parse_offset(timezone_str.as_str()) {62Ok(extract_impl(array, *time_unit, timezone, |x| {63x.$extract().try_into().unwrap()64}))65} else {66chrono_tz(array, *time_unit, timezone_str.as_str(), |x| {67x.$extract().try_into().unwrap()68})69}70},71_ => unimplemented!(),72}73};74}7576/// Extracts the years of a temporal array as [`PrimitiveArray<i32>`].77pub fn year(array: &dyn Array) -> PolarsResult<PrimitiveArray<i32>> {78date_like!(year, array, ArrowDataType::Int32)79}8081/// Extracts the months of a temporal array as [`PrimitiveArray<i8>`].82///83/// Value ranges from 1 to 12.84pub fn month(array: &dyn Array) -> PolarsResult<PrimitiveArray<i8>> {85date_like!(month, array, ArrowDataType::Int8)86}8788/// Extracts the days of a temporal array as [`PrimitiveArray<i8>`].89///90/// Value ranges from 1 to 32 (Last day depends on month).91pub fn day(array: &dyn Array) -> PolarsResult<PrimitiveArray<i8>> {92date_like!(day, array, ArrowDataType::Int8)93}9495/// Extracts weekday of a temporal array as [`PrimitiveArray<i8>`].96///97/// Monday is 1, Tuesday is 2, ..., Sunday is 7.98pub fn weekday(array: &dyn Array) -> PolarsResult<PrimitiveArray<i8>> {99date_like!(i8_weekday, array, ArrowDataType::Int8)100}101102/// Extracts ISO week of a temporal array as [`PrimitiveArray<i8>`].103///104/// Value ranges from 1 to 53 (Last week depends on the year).105pub fn iso_week(array: &dyn Array) -> PolarsResult<PrimitiveArray<i8>> {106date_like!(i8_iso_week, array, ArrowDataType::Int8)107}108109// Macro to avoid repetition in functions, that apply110// `chrono::Timelike` methods on Arrays111macro_rules! time_like {112($extract:ident, $array:ident, $dtype:path) => {113match $array.dtype().to_logical_type() {114ArrowDataType::Date32 | ArrowDataType::Date64 | ArrowDataType::Timestamp(_, None) => {115date_variants($array, $dtype, |x| x.$extract().try_into().unwrap())116},117ArrowDataType::Time32(_) | ArrowDataType::Time64(_) => {118time_variants($array, ArrowDataType::UInt32, |x| {119x.$extract().try_into().unwrap()120})121},122ArrowDataType::Timestamp(time_unit, Some(timezone_str)) => {123let array = $array.as_any().downcast_ref().unwrap();124125if let Ok(timezone) = parse_offset(timezone_str.as_str()) {126Ok(extract_impl(array, *time_unit, timezone, |x| {127x.$extract().try_into().unwrap()128}))129} else {130chrono_tz(array, *time_unit, timezone_str.as_str(), |x| {131x.$extract().try_into().unwrap()132})133}134},135_ => unimplemented!(),136}137};138}139140/// Extracts the hours of a temporal array as [`PrimitiveArray<i8>`].141/// Value ranges from 0 to 23.142/// Use [`can_hour`] to check if this operation is supported for the target [`ArrowDataType`].143pub fn hour(array: &dyn Array) -> PolarsResult<PrimitiveArray<i8>> {144time_like!(hour, array, ArrowDataType::Int8)145}146147/// Extracts the minutes of a temporal array as [`PrimitiveArray<i8>`].148/// Value ranges from 0 to 59.149/// Use [`can_minute`] to check if this operation is supported for the target [`ArrowDataType`].150pub fn minute(array: &dyn Array) -> PolarsResult<PrimitiveArray<i8>> {151time_like!(minute, array, ArrowDataType::Int8)152}153154/// Extracts the seconds of a temporal array as [`PrimitiveArray<i8>`].155/// Value ranges from 0 to 59.156/// Use [`can_second`] to check if this operation is supported for the target [`ArrowDataType`].157pub fn second(array: &dyn Array) -> PolarsResult<PrimitiveArray<i8>> {158time_like!(second, array, ArrowDataType::Int8)159}160161/// Extracts the nanoseconds of a temporal array as [`PrimitiveArray<i32>`].162///163/// Value ranges from 0 to 1_999_999_999.164/// The range from 1_000_000_000 to 1_999_999_999 represents the leap second.165/// Use [`can_nanosecond`] to check if this operation is supported for the target [`ArrowDataType`].166pub fn nanosecond(array: &dyn Array) -> PolarsResult<PrimitiveArray<i32>> {167time_like!(nanosecond, array, ArrowDataType::Int32)168}169170fn date_variants<F, O>(171array: &dyn Array,172dtype: ArrowDataType,173op: F,174) -> PolarsResult<PrimitiveArray<O>>175where176O: NativeType,177F: Fn(chrono::NaiveDateTime) -> O,178{179match array.dtype().to_logical_type() {180ArrowDataType::Date32 => {181let array = array182.as_any()183.downcast_ref::<PrimitiveArray<i32>>()184.unwrap();185Ok(unary(array, |x| op(date32_to_datetime(x)), dtype))186},187ArrowDataType::Date64 => {188let array = array189.as_any()190.downcast_ref::<PrimitiveArray<i64>>()191.unwrap();192Ok(unary(array, |x| op(date64_to_datetime(x)), dtype))193},194ArrowDataType::Timestamp(time_unit, None) => {195let array = array196.as_any()197.downcast_ref::<PrimitiveArray<i64>>()198.unwrap();199let func = match time_unit {200TimeUnit::Second => timestamp_s_to_datetime,201TimeUnit::Millisecond => timestamp_ms_to_datetime,202TimeUnit::Microsecond => timestamp_us_to_datetime,203TimeUnit::Nanosecond => timestamp_ns_to_datetime,204};205Ok(PrimitiveArray::<O>::from_trusted_len_iter(206array.iter().map(|v| v.map(|x| op(func(*x)))),207))208},209_ => unreachable!(),210}211}212213fn time_variants<F, O>(214array: &dyn Array,215dtype: ArrowDataType,216op: F,217) -> PolarsResult<PrimitiveArray<O>>218where219O: NativeType,220F: Fn(chrono::NaiveTime) -> O,221{222match array.dtype().to_logical_type() {223ArrowDataType::Time32(TimeUnit::Second) => {224let array = array225.as_any()226.downcast_ref::<PrimitiveArray<i32>>()227.unwrap();228Ok(unary(array, |x| op(time32s_to_time(x)), dtype))229},230ArrowDataType::Time32(TimeUnit::Millisecond) => {231let array = array232.as_any()233.downcast_ref::<PrimitiveArray<i32>>()234.unwrap();235Ok(unary(array, |x| op(time32ms_to_time(x)), dtype))236},237ArrowDataType::Time64(TimeUnit::Microsecond) => {238let array = array239.as_any()240.downcast_ref::<PrimitiveArray<i64>>()241.unwrap();242Ok(unary(array, |x| op(time64us_to_time(x)), dtype))243},244ArrowDataType::Time64(TimeUnit::Nanosecond) => {245let array = array246.as_any()247.downcast_ref::<PrimitiveArray<i64>>()248.unwrap();249Ok(unary(array, |x| op(time64ns_to_time(x)), dtype))250},251_ => unreachable!(),252}253}254255#[cfg(feature = "chrono-tz")]256fn chrono_tz<F, O>(257array: &PrimitiveArray<i64>,258time_unit: TimeUnit,259timezone_str: &str,260op: F,261) -> PolarsResult<PrimitiveArray<O>>262where263O: NativeType,264F: Fn(chrono::DateTime<chrono_tz::Tz>) -> O,265{266let timezone = parse_offset_tz(timezone_str)?;267Ok(extract_impl(array, time_unit, timezone, op))268}269270#[cfg(not(feature = "chrono-tz"))]271fn chrono_tz<F, O>(272_: &PrimitiveArray<i64>,273_: TimeUnit,274timezone_str: &str,275_: F,276) -> PolarsResult<PrimitiveArray<O>>277where278O: NativeType,279F: Fn(chrono::DateTime<chrono::FixedOffset>) -> O,280{281panic!(282"timezone \"{}\" cannot be parsed (feature chrono-tz is not active)",283timezone_str284)285}286287fn extract_impl<T, A, F>(288array: &PrimitiveArray<i64>,289time_unit: TimeUnit,290timezone: T,291extract: F,292) -> PrimitiveArray<A>293where294T: chrono::TimeZone,295A: NativeType,296F: Fn(chrono::DateTime<T>) -> A,297{298match time_unit {299TimeUnit::Second => {300let op = |x| {301let datetime = timestamp_s_to_datetime(x);302let offset = timezone.offset_from_utc_datetime(&datetime);303extract(chrono::DateTime::<T>::from_naive_utc_and_offset(304datetime, offset,305))306};307unary(array, op, A::PRIMITIVE.into())308},309TimeUnit::Millisecond => {310let op = |x| {311let datetime = timestamp_ms_to_datetime(x);312let offset = timezone.offset_from_utc_datetime(&datetime);313extract(chrono::DateTime::<T>::from_naive_utc_and_offset(314datetime, offset,315))316};317unary(array, op, A::PRIMITIVE.into())318},319TimeUnit::Microsecond => {320let op = |x| {321let datetime = timestamp_us_to_datetime(x);322let offset = timezone.offset_from_utc_datetime(&datetime);323extract(chrono::DateTime::<T>::from_naive_utc_and_offset(324datetime, offset,325))326};327unary(array, op, A::PRIMITIVE.into())328},329TimeUnit::Nanosecond => {330let op = |x| {331let datetime = timestamp_ns_to_datetime(x);332let offset = timezone.offset_from_utc_datetime(&datetime);333extract(chrono::DateTime::<T>::from_naive_utc_and_offset(334datetime, offset,335))336};337unary(array, op, A::PRIMITIVE.into())338},339}340}341342343