Path: blob/main/crates/polars-expr/src/dispatch/datetime.rs
7884 views
#[cfg(feature = "timezones")]1use arrow::legacy::time_zone::Tz;2use polars_core::error::{PolarsResult, polars_bail};3use polars_core::prelude::{4ArithmeticChunked, Column, DataType, IntoColumn, LogicalType, TimeUnit,5};6#[cfg(feature = "timezones")]7use polars_core::prelude::{NonExistent, StringChunked, TimeZone};8use polars_time::prelude::*;9use polars_time::replace_datetime;10use polars_time::series::TemporalMethods;1112pub(super) fn millennium(s: &Column) -> PolarsResult<Column> {13s.as_materialized_series()14.millennium()15.map(|ca| ca.into_column())16}17pub(super) fn century(s: &Column) -> PolarsResult<Column> {18s.as_materialized_series()19.century()20.map(|ca| ca.into_column())21}22pub(super) fn year(s: &Column) -> PolarsResult<Column> {23s.as_materialized_series().year().map(|ca| ca.into_column())24}25pub(super) fn is_leap_year(s: &Column) -> PolarsResult<Column> {26s.as_materialized_series()27.is_leap_year()28.map(|ca| ca.into_column())29}30pub(super) fn iso_year(s: &Column) -> PolarsResult<Column> {31s.as_materialized_series()32.iso_year()33.map(|ca| ca.into_column())34}35pub(super) fn month(s: &Column) -> PolarsResult<Column> {36s.as_materialized_series()37.month()38.map(|ca| ca.into_column())39}40pub(super) fn days_in_month(s: &Column) -> PolarsResult<Column> {41s.as_materialized_series()42.days_in_month()43.map(|ca| ca.into_column())44}45pub(super) fn quarter(s: &Column) -> PolarsResult<Column> {46s.as_materialized_series()47.quarter()48.map(|ca| ca.into_column())49}50pub(super) fn week(s: &Column) -> PolarsResult<Column> {51s.as_materialized_series().week().map(|ca| ca.into_column())52}53pub(super) fn weekday(s: &Column) -> PolarsResult<Column> {54s.as_materialized_series()55.weekday()56.map(|ca| ca.into_column())57}58pub(super) fn day(s: &Column) -> PolarsResult<Column> {59s.as_materialized_series().day().map(|ca| ca.into_column())60}61pub(super) fn ordinal_day(s: &Column) -> PolarsResult<Column> {62s.as_materialized_series()63.ordinal_day()64.map(|ca| ca.into_column())65}66pub(super) fn time(s: &Column) -> PolarsResult<Column> {67match s.dtype() {68#[cfg(feature = "timezones")]69DataType::Datetime(_, Some(_)) => polars_ops::prelude::replace_time_zone(70s.datetime().unwrap(),71None,72&StringChunked::from_iter(std::iter::once("raise")),73NonExistent::Raise,74)?75.cast(&DataType::Time)76.map(Column::from),77DataType::Datetime(_, _) => s78.datetime()79.unwrap()80.cast(&DataType::Time)81.map(Column::from),82DataType::Time => Ok(s.clone()),83dtype => polars_bail!(ComputeError: "expected Datetime or Time, got {}", dtype),84}85}86pub(super) fn date(s: &Column) -> PolarsResult<Column> {87match s.dtype() {88#[cfg(feature = "timezones")]89DataType::Datetime(_, Some(_)) => {90let mut out = {91polars_ops::chunked_array::replace_time_zone(92s.datetime().unwrap(),93None,94&StringChunked::from_iter(std::iter::once("raise")),95NonExistent::Raise,96)?97.cast(&DataType::Date)?98};99// `replace_time_zone` may unset sorted flag. But, we're only taking the date100// part of the result, so we can safely preserve the sorted flag here. We may101// need to make an exception if a time zone introduces a change which involves102// "going back in time" and repeating a day, but we're not aware of that ever103// having happened.104out.set_sorted_flag(s.is_sorted_flag());105Ok(out.into())106},107DataType::Datetime(_, _) => s108.datetime()109.unwrap()110.cast(&DataType::Date)111.map(Column::from),112DataType::Date => Ok(s.clone()),113dtype => polars_bail!(ComputeError: "expected Datetime or Date, got {}", dtype),114}115}116pub(super) fn datetime(s: &Column) -> PolarsResult<Column> {117match s.dtype() {118#[cfg(feature = "timezones")]119DataType::Datetime(tu, Some(_)) => polars_ops::chunked_array::replace_time_zone(120s.datetime().unwrap(),121None,122&StringChunked::from_iter(std::iter::once("raise")),123NonExistent::Raise,124)?125.cast(&DataType::Datetime(*tu, None))126.map(|x| x.into()),127DataType::Datetime(tu, _) => s128.datetime()129.unwrap()130.cast(&DataType::Datetime(*tu, None))131.map(Column::from),132dtype => polars_bail!(ComputeError: "expected Datetime, got {}", dtype),133}134}135pub(super) fn hour(s: &Column) -> PolarsResult<Column> {136s.as_materialized_series().hour().map(|ca| ca.into_column())137}138pub(super) fn minute(s: &Column) -> PolarsResult<Column> {139s.as_materialized_series()140.minute()141.map(|ca| ca.into_column())142}143pub(super) fn second(s: &Column) -> PolarsResult<Column> {144s.as_materialized_series()145.second()146.map(|ca| ca.into_column())147}148pub(super) fn millisecond(s: &Column) -> PolarsResult<Column> {149s.as_materialized_series()150.nanosecond()151.map(|ca| (ca.wrapping_trunc_div_scalar(1_000_000)).into_column())152}153pub(super) fn microsecond(s: &Column) -> PolarsResult<Column> {154s.as_materialized_series()155.nanosecond()156.map(|ca| (ca.wrapping_trunc_div_scalar(1_000)).into_column())157}158pub(super) fn nanosecond(s: &Column) -> PolarsResult<Column> {159s.as_materialized_series()160.nanosecond()161.map(|ca| ca.into_column())162}163#[cfg(feature = "dtype-duration")]164pub(super) fn total_days(s: &Column) -> PolarsResult<Column> {165use polars_time::prelude::DurationMethods;166167s.as_materialized_series()168.duration()169.map(|ca| ca.days().into_column())170}171#[cfg(feature = "dtype-duration")]172pub(super) fn total_days_fractional(s: &Column) -> PolarsResult<Column> {173use polars_time::prelude::DurationMethods;174175s.as_materialized_series()176.duration()177.map(|ca| ca.days_fractional().into_column())178}179#[cfg(feature = "dtype-duration")]180pub(super) fn total_hours(s: &Column) -> PolarsResult<Column> {181use polars_time::prelude::DurationMethods;182183s.as_materialized_series()184.duration()185.map(|ca| ca.hours().into_column())186}187#[cfg(feature = "dtype-duration")]188pub(super) fn total_hours_fractional(s: &Column) -> PolarsResult<Column> {189use polars_time::prelude::DurationMethods;190191s.as_materialized_series()192.duration()193.map(|ca| ca.hours_fractional().into_column())194}195#[cfg(feature = "dtype-duration")]196pub(super) fn total_minutes(s: &Column) -> PolarsResult<Column> {197use polars_time::prelude::DurationMethods;198199s.as_materialized_series()200.duration()201.map(|ca| ca.minutes().into_column())202}203#[cfg(feature = "dtype-duration")]204pub(super) fn total_minutes_fractional(s: &Column) -> PolarsResult<Column> {205use polars_time::prelude::DurationMethods;206207s.as_materialized_series()208.duration()209.map(|ca| ca.minutes_fractional().into_column())210}211#[cfg(feature = "dtype-duration")]212pub(super) fn total_seconds(s: &Column) -> PolarsResult<Column> {213use polars_time::prelude::DurationMethods;214215s.as_materialized_series()216.duration()217.map(|ca| ca.seconds().into_column())218}219#[cfg(feature = "dtype-duration")]220pub(super) fn total_seconds_fractional(s: &Column) -> PolarsResult<Column> {221s.as_materialized_series()222.duration()223.map(|ca| ca.seconds_fractional().into_column())224}225#[cfg(feature = "dtype-duration")]226pub(super) fn total_milliseconds(s: &Column) -> PolarsResult<Column> {227s.as_materialized_series()228.duration()229.map(|ca| ca.milliseconds().into_column())230}231#[cfg(feature = "dtype-duration")]232pub(super) fn total_milliseconds_fractional(s: &Column) -> PolarsResult<Column> {233s.as_materialized_series()234.duration()235.map(|ca| ca.milliseconds_fractional().into_column())236}237#[cfg(feature = "dtype-duration")]238pub(super) fn total_microseconds(s: &Column) -> PolarsResult<Column> {239s.as_materialized_series()240.duration()241.map(|ca| ca.microseconds().into_column())242}243#[cfg(feature = "dtype-duration")]244pub(super) fn total_microseconds_fractional(s: &Column) -> PolarsResult<Column> {245s.as_materialized_series()246.duration()247.map(|ca| ca.microseconds_fractional().into_column())248}249#[cfg(feature = "dtype-duration")]250pub(super) fn total_nanoseconds(s: &Column) -> PolarsResult<Column> {251s.as_materialized_series()252.duration()253.map(|ca| ca.nanoseconds().into_column())254}255#[cfg(feature = "dtype-duration")]256pub(super) fn total_nanoseconds_fractional(s: &Column) -> PolarsResult<Column> {257s.as_materialized_series()258.duration()259.map(|ca| ca.nanoseconds_fractional().into_column())260}261pub(super) fn timestamp(s: &Column, tu: TimeUnit) -> PolarsResult<Column> {262s.as_materialized_series()263.timestamp(tu)264.map(|ca| ca.into_column())265}266pub(super) fn to_string(s: &Column, format: &str) -> PolarsResult<Column> {267TemporalMethods::to_string(s.as_materialized_series(), format).map(Column::from)268}269270#[cfg(feature = "timezones")]271pub(super) fn convert_time_zone(s: &Column, time_zone: &TimeZone) -> PolarsResult<Column> {272match s.dtype() {273DataType::Datetime(_, _) => {274let mut ca = s.datetime()?.clone();275ca.set_time_zone(time_zone.clone())?;276Ok(ca.into_column())277},278dtype => polars_bail!(ComputeError: "expected Datetime, got {}", dtype),279}280}281pub(super) fn with_time_unit(s: &Column, tu: TimeUnit) -> PolarsResult<Column> {282match s.dtype() {283DataType::Datetime(_, _) => {284let mut ca = s.datetime()?.clone();285ca.set_time_unit(tu);286Ok(ca.into_column())287},288#[cfg(feature = "dtype-duration")]289DataType::Duration(_) => {290let mut ca = s.as_materialized_series().duration()?.clone();291ca.set_time_unit(tu);292Ok(ca.into_column())293},294dt => polars_bail!(ComputeError: "dtype `{}` has no time unit", dt),295}296}297pub(super) fn cast_time_unit(s: &Column, tu: TimeUnit) -> PolarsResult<Column> {298match s.dtype() {299DataType::Datetime(_, _) => {300let ca = s.datetime()?;301Ok(ca.cast_time_unit(tu).into_column())302},303#[cfg(feature = "dtype-duration")]304DataType::Duration(_) => {305let ca = s.as_materialized_series().duration()?;306Ok(ca.cast_time_unit(tu).into_column())307},308dt => polars_bail!(ComputeError: "dtype `{}` has no time unit", dt),309}310}311312pub(super) fn truncate(s: &[Column]) -> PolarsResult<Column> {313let time_series = &s[0];314let every = s[1].str()?;315316let mut out = match time_series.dtype() {317DataType::Datetime(_, tz) => match tz {318#[cfg(feature = "timezones")]319Some(tz) => time_series320.datetime()?321.truncate(tz.parse::<Tz>().ok().as_ref(), every)?322.into_column(),323_ => time_series.datetime()?.truncate(None, every)?.into_column(),324},325DataType::Date => time_series.date()?.truncate(None, every)?.into_column(),326dt => polars_bail!(opq = round, got = dt, expected = "date/datetime"),327};328out.set_sorted_flag(time_series.is_sorted_flag());329Ok(out)330}331332#[cfg(feature = "offset_by")]333pub(super) fn offset_by(s: &[Column]) -> PolarsResult<Column> {334use polars_time::impl_offset_by;335336impl_offset_by(s[0].as_materialized_series(), s[1].as_materialized_series()).map(Column::from)337}338339#[cfg(feature = "month_start")]340pub(super) fn month_start(s: &Column) -> PolarsResult<Column> {341Ok(match s.dtype() {342DataType::Datetime(_, tz) => match tz {343#[cfg(feature = "timezones")]344Some(tz) => s345.datetime()346.unwrap()347.month_start(tz.parse::<Tz>().ok().as_ref())?348.into_column(),349_ => s.datetime().unwrap().month_start(None)?.into_column(),350},351DataType::Date => s.date().unwrap().month_start(None)?.into_column(),352dt => polars_bail!(opq = month_start, got = dt, expected = "date/datetime"),353})354}355356#[cfg(feature = "month_end")]357pub(super) fn month_end(s: &Column) -> PolarsResult<Column> {358Ok(match s.dtype() {359DataType::Datetime(_, tz) => match tz {360#[cfg(feature = "timezones")]361Some(tz) => s362.datetime()363.unwrap()364.month_end(tz.parse::<Tz>().ok().as_ref())?365.into_column(),366_ => s.datetime().unwrap().month_end(None)?.into_column(),367},368DataType::Date => s.date().unwrap().month_end(None)?.into_column(),369dt => polars_bail!(opq = month_end, got = dt, expected = "date/datetime"),370})371}372373#[cfg(feature = "timezones")]374pub(super) fn base_utc_offset(s: &Column) -> PolarsResult<Column> {375match s.dtype() {376DataType::Datetime(time_unit, Some(tz)) => {377let tz = tz378.parse::<Tz>()379.expect("Time zone has already been validated");380Ok(polars_time::base_utc_offset(s.datetime().unwrap(), time_unit, &tz).into_column())381},382dt => polars_bail!(383opq = base_utc_offset,384got = dt,385expected = "time-zone-aware datetime"386),387}388}389#[cfg(feature = "timezones")]390pub(super) fn dst_offset(s: &Column) -> PolarsResult<Column> {391match s.dtype() {392DataType::Datetime(time_unit, Some(tz)) => {393let tz = tz394.parse::<Tz>()395.expect("Time zone has already been validated");396Ok(polars_time::dst_offset(s.datetime().unwrap(), time_unit, &tz).into_column())397},398dt => polars_bail!(399opq = dst_offset,400got = dt,401expected = "time-zone-aware datetime"402),403}404}405406pub(super) fn round(s: &[Column]) -> PolarsResult<Column> {407let time_series = &s[0];408let every = s[1].str()?;409410Ok(match time_series.dtype() {411DataType::Datetime(_, tz) => match tz {412#[cfg(feature = "timezones")]413Some(tz) => time_series414.datetime()415.unwrap()416.round(every, tz.parse::<Tz>().ok().as_ref())?417.into_column(),418_ => time_series419.datetime()420.unwrap()421.round(every, None)?422.into_column(),423},424DataType::Date => time_series425.date()426.unwrap()427.round(every, None)?428.into_column(),429dt => polars_bail!(opq = round, got = dt, expected = "date/datetime"),430})431}432433pub(super) fn replace(s: &[Column]) -> PolarsResult<Column> {434let time_series = &s[0];435let s_year = &s[1].strict_cast(&DataType::Int32)?;436let s_month = &s[2].strict_cast(&DataType::Int8)?;437let s_day = &s[3].strict_cast(&DataType::Int8)?;438let year = s_year.i32()?;439let month = s_month.i8()?;440let day = s_day.i8()?;441442match time_series.dtype() {443DataType::Datetime(_, _) => {444let s_hour = &s[4].strict_cast(&DataType::Int8)?;445let s_minute = &s[5].strict_cast(&DataType::Int8)?;446let s_second = &s[6].strict_cast(&DataType::Int8)?;447let s_microsecond = &s[7].strict_cast(&DataType::Int32)?;448let hour = s_hour.i8()?;449let minute = s_minute.i8()?;450let second = s_second.i8()?;451let nanosecond = &(s_microsecond.i32()? * 1_000);452let s_ambiguous = &s[8].strict_cast(&DataType::String)?;453let ambiguous = s_ambiguous.str()?;454455let out = replace_datetime(456time_series.datetime().unwrap(),457year,458month,459day,460hour,461minute,462second,463nanosecond,464ambiguous,465);466out.map(|s| s.into_column())467},468DataType::Date => {469let out = polars_time::replace_date(time_series.date().unwrap(), year, month, day);470out.map(|s| s.into_column())471},472dt => polars_bail!(opq = round, got = dt, expected = "date/datetime"),473}474}475476477