Path: blob/main/crates/polars-plan/src/dsl/functions/temporal.rs
8446 views
use arrow::temporal_conversions::{NANOSECONDS, NANOSECONDS_IN_DAY};1use chrono::{Datelike, Timelike};2use polars_utils::array;34use super::*;56macro_rules! impl_unit_setter {7($fn_name:ident($field:ident)) => {8#[doc = concat!("Set the ", stringify!($field))]9pub fn $fn_name(mut self, n: Expr) -> Self {10self.$field = n.into();11self12}13};14}1516/// Arguments used by `datetime` in order to produce an [`Expr`] of Datetime17///18/// Construct a [`DatetimeArgs`] with `DatetimeArgs::new(y, m, d)`. This will set the other time units to `lit(0)`. You19/// can then set the other fields with the `with_*` methods, or use `with_hms` to set `hour`, `minute`, and `second` all20/// at once.21///22/// # Examples23/// ```24/// use polars_plan::prelude::*;25/// // construct a DatetimeArgs set to July 20, 1969 at 20:1726/// let args = DatetimeArgs::new(lit(1969), lit(7), lit(20)).with_hms(lit(20), lit(17), lit(0));27/// // or28/// let args = DatetimeArgs::new(lit(1969), lit(7), lit(20)).with_hour(lit(20)).with_minute(lit(17));29///30/// // construct a DatetimeArgs using existing columns31/// let args = DatetimeArgs::new(lit(2023), col("month"), col("day"));32/// ```33#[derive(Debug, Clone)]34pub struct DatetimeArgs {35pub year: Expr,36pub month: Expr,37pub day: Expr,38pub hour: Expr,39pub minute: Expr,40pub second: Expr,41pub microsecond: Expr,42pub time_unit: TimeUnit,43pub time_zone: Option<TimeZone>,44pub ambiguous: Expr,45}4647impl Default for DatetimeArgs {48fn default() -> Self {49Self {50year: lit(1970),51month: lit(1),52day: lit(1),53hour: lit(0),54minute: lit(0),55second: lit(0),56microsecond: lit(0),57time_unit: TimeUnit::Microseconds,58time_zone: None,59ambiguous: lit(String::from("raise")),60}61}62}6364impl DatetimeArgs {65/// Construct a new `DatetimeArgs` set to `year`, `month`, `day`66///67/// Other fields default to `lit(0)`. Use the `with_*` methods to set them.68pub fn new(year: Expr, month: Expr, day: Expr) -> Self {69Self {70year,71month,72day,73..Default::default()74}75}7677/// Set `hour`, `minute`, and `second`78///79/// Equivalent to80/// ```ignore81/// self.with_hour(hour)82/// .with_minute(minute)83/// .with_second(second)84/// ```85pub fn with_hms(self, hour: Expr, minute: Expr, second: Expr) -> Self {86Self {87hour,88minute,89second,90..self91}92}9394impl_unit_setter!(with_year(year));95impl_unit_setter!(with_month(month));96impl_unit_setter!(with_day(day));97impl_unit_setter!(with_hour(hour));98impl_unit_setter!(with_minute(minute));99impl_unit_setter!(with_second(second));100impl_unit_setter!(with_microsecond(microsecond));101102pub fn with_time_unit(self, time_unit: TimeUnit) -> Self {103Self { time_unit, ..self }104}105#[cfg(feature = "timezones")]106pub fn with_time_zone(self, time_zone: Option<TimeZone>) -> Self {107Self { time_zone, ..self }108}109#[cfg(feature = "timezones")]110pub fn with_ambiguous(self, ambiguous: Expr) -> Self {111Self { ambiguous, ..self }112}113114fn all_literal(&self) -> bool {115use Expr::*;116[117&self.year,118&self.month,119&self.day,120&self.hour,121&self.minute,122&self.second,123&self.microsecond,124]125.iter()126.all(|e| matches!(e, Literal(_)))127}128129fn as_literal(&self) -> Option<Expr> {130if self.time_zone.is_some() || !self.all_literal() {131return None;132};133let Expr::Literal(lv) = &self.year else {134unreachable!()135};136let year = lv.to_any_value()?.extract()?;137let Expr::Literal(lv) = &self.month else {138unreachable!()139};140let month = lv.to_any_value()?.extract()?;141let Expr::Literal(lv) = &self.day else {142unreachable!()143};144let day = lv.to_any_value()?.extract()?;145let Expr::Literal(lv) = &self.hour else {146unreachable!()147};148let hour = lv.to_any_value()?.extract()?;149let Expr::Literal(lv) = &self.minute else {150unreachable!()151};152let minute = lv.to_any_value()?.extract()?;153let Expr::Literal(lv) = &self.second else {154unreachable!()155};156let second = lv.to_any_value()?.extract()?;157let Expr::Literal(lv) = &self.microsecond else {158unreachable!()159};160let ms: u32 = lv.to_any_value()?.extract()?;161162let dt = chrono::NaiveDateTime::default()163.with_year(year)?164.with_month(month)?165.with_day(day)?166.with_hour(hour)?167.with_minute(minute)?168.with_second(second)?169.with_nanosecond(ms * 1000)?;170171let ts = match self.time_unit {172TimeUnit::Milliseconds => dt.and_utc().timestamp_millis(),173TimeUnit::Microseconds => dt.and_utc().timestamp_micros(),174TimeUnit::Nanoseconds => dt.and_utc().timestamp_nanos_opt()?,175};176177Some(178Expr::Literal(LiteralValue::Scalar(Scalar::new(179DataType::Datetime(self.time_unit, None),180AnyValue::Datetime(ts, self.time_unit, None),181)))182.alias(PlSmallStr::from_static("datetime")),183)184}185}186187/// Construct a column of `Datetime` from the provided [`DatetimeArgs`].188pub fn datetime(args: DatetimeArgs) -> Expr {189if let Some(e) = args.as_literal() {190return e;191}192193let year = args.year;194let month = args.month;195let day = args.day;196let hour = args.hour;197let minute = args.minute;198let second = args.second;199let microsecond = args.microsecond;200let time_unit = args.time_unit;201let time_zone = args.time_zone;202let ambiguous = args.ambiguous;203204let input = vec![205year,206month,207day,208hour,209minute,210second,211microsecond,212ambiguous,213];214215Expr::Alias(216Arc::new(Expr::Function {217input,218function: FunctionExpr::TemporalExpr(TemporalFunction::DatetimeFunction {219time_unit,220time_zone,221}),222}),223// TODO: follow left-hand rule in Polars 2.0.224PlSmallStr::from_static("datetime"),225)226}227228/// Arguments used by `duration` in order to produce an [`Expr`] of [`Duration`]229///230/// To construct a [`DurationArgs`], use struct literal syntax with `..Default::default()` to leave unspecified fields at231/// their default value of `lit(0)`, as demonstrated below.232///233/// ```234/// # use polars_plan::prelude::*;235/// let args = DurationArgs {236/// days: lit(5),237/// hours: col("num_hours"),238/// minutes: col("num_minutes"),239/// ..Default::default() // other fields are lit(0)240/// };241/// ```242/// If you prefer builder syntax, `with_*` methods are also available.243/// ```244/// # use polars_plan::prelude::*;245/// let args = DurationArgs::new().with_weeks(lit(42)).with_hours(lit(84));246/// ```247#[derive(Debug, Clone)]248pub struct DurationArgs {249pub weeks: Expr,250pub days: Expr,251pub hours: Expr,252pub minutes: Expr,253pub seconds: Expr,254pub milliseconds: Expr,255pub microseconds: Expr,256pub nanoseconds: Expr,257pub time_unit: TimeUnit,258}259260impl Default for DurationArgs {261fn default() -> Self {262Self {263weeks: lit(0),264days: lit(0),265hours: lit(0),266minutes: lit(0),267seconds: lit(0),268milliseconds: lit(0),269microseconds: lit(0),270nanoseconds: lit(0),271time_unit: TimeUnit::Microseconds,272}273}274}275276impl DurationArgs {277/// Create a new [`DurationArgs`] with all fields set to `lit(0)`. Use the `with_*` methods to set the fields.278pub fn new() -> Self {279Self::default()280}281282/// Set `hours`, `minutes`, and `seconds`283///284/// Equivalent to:285///286/// ```ignore287/// self.with_hours(hours)288/// .with_minutes(minutes)289/// .with_seconds(seconds)290/// ```291pub fn with_hms(self, hours: Expr, minutes: Expr, seconds: Expr) -> Self {292Self {293hours,294minutes,295seconds,296..self297}298}299300/// Set `milliseconds`, `microseconds`, and `nanoseconds`301///302/// Equivalent to303/// ```ignore304/// self.with_milliseconds(milliseconds)305/// .with_microseconds(microseconds)306/// .with_nanoseconds(nanoseconds)307/// ```308pub fn with_fractional_seconds(309self,310milliseconds: Expr,311microseconds: Expr,312nanoseconds: Expr,313) -> Self {314Self {315milliseconds,316microseconds,317nanoseconds,318..self319}320}321322impl_unit_setter!(with_weeks(weeks));323impl_unit_setter!(with_days(days));324impl_unit_setter!(with_hours(hours));325impl_unit_setter!(with_minutes(minutes));326impl_unit_setter!(with_seconds(seconds));327impl_unit_setter!(with_milliseconds(milliseconds));328impl_unit_setter!(with_microseconds(microseconds));329impl_unit_setter!(with_nanoseconds(nanoseconds));330331fn as_literal(&self) -> Option<Expr> {332use time_unit::convert_time_units;333334let extract_i64_as_i128 = |e: &Expr| {335let Expr::Literal(lv) = e else { return None };336let av = lv.to_any_value()?;337if !av.is_integer() {338return None;339};340av.extract::<i64>().map(i128::from)341};342let extract_f64 = |e: &Expr| {343let Expr::Literal(lv) = e else { return None };344let av = lv.to_any_value()?;345av.extract::<f64>()346};347let overflows_i64 = |e: &&Expr| {348let Expr::Literal(lv) = e else { return false };349let av = lv.to_any_value();350match av {351Some(AnyValue::UInt128(x)) => x > i64::MAX as u128,352Some(AnyValue::UInt64(x)) => x > i64::MAX as u64,353Some(AnyValue::Int128(x)) => x < i64::MIN as i128 || x > i64::MAX as i128,354_ => false,355}356};357358let fields = [359&self.weeks,360&self.days,361&self.hours,362&self.minutes,363&self.seconds,364&self.milliseconds,365&self.microseconds,366&self.nanoseconds,367];368369// go into the function expr implementation to throw an error370if fields.iter().any(overflows_i64) {371return None;372}373374let d = if let Some(fields) = array::try_map(fields, extract_i64_as_i128) {375let [w, d, h, m, s, ms, us, ns] = fields;376377let total_ns = w * 7 * NANOSECONDS_IN_DAY as i128378+ d * NANOSECONDS_IN_DAY as i128379+ h * 3600 * NANOSECONDS as i128380+ m * 60 * NANOSECONDS as i128381+ s * NANOSECONDS as i128382+ ms * 1_000_000383+ us * 1_000384+ ns;385386convert_time_units(total_ns, TimeUnit::Nanoseconds, self.time_unit) as i64387} else if let Some(fields) = array::try_map(fields, extract_f64) {388let [w, d, h, m, s, ms, us, ns] = fields;389390let total_ns = w * 7.0 * NANOSECONDS_IN_DAY as f64391+ d * NANOSECONDS_IN_DAY as f64392+ h * 3600.0 * NANOSECONDS as f64393+ m * 60.0 * NANOSECONDS as f64394+ s * NANOSECONDS as f64395+ ms * 1_000_000.0396+ us * 1_000.0397+ ns;398399convert_time_units(total_ns, TimeUnit::Nanoseconds, self.time_unit) as i64400} else {401return None;402};403404Some(405Expr::Literal(LiteralValue::Scalar(Scalar::new(406DataType::Duration(self.time_unit),407AnyValue::Duration(d, self.time_unit),408)))409.alias(PlSmallStr::from_static("duration")),410)411}412}413414/// Construct a column of [`Duration`] from the provided [`DurationArgs`]415#[cfg(feature = "dtype-duration")]416pub fn duration(args: DurationArgs) -> Expr {417if let Some(e) = args.as_literal() {418return e;419}420Expr::Function {421input: vec![422args.weeks,423args.days,424args.hours,425args.minutes,426args.seconds,427args.milliseconds,428args.microseconds,429args.nanoseconds,430],431function: FunctionExpr::TemporalExpr(TemporalFunction::Duration(args.time_unit)),432}433}434435436