Path: blob/main/crates/polars-plan/src/dsl/functions/temporal.rs
6940 views
use chrono::{Datelike, Timelike};12use super::*;34macro_rules! impl_unit_setter {5($fn_name:ident($field:ident)) => {6#[doc = concat!("Set the ", stringify!($field))]7pub fn $fn_name(mut self, n: Expr) -> Self {8self.$field = n.into();9self10}11};12}1314/// Arguments used by `datetime` in order to produce an [`Expr`] of Datetime15///16/// Construct a [`DatetimeArgs`] with `DatetimeArgs::new(y, m, d)`. This will set the other time units to `lit(0)`. You17/// can then set the other fields with the `with_*` methods, or use `with_hms` to set `hour`, `minute`, and `second` all18/// at once.19///20/// # Examples21/// ```22/// use polars_plan::prelude::*;23/// // construct a DatetimeArgs set to July 20, 1969 at 20:1724/// let args = DatetimeArgs::new(lit(1969), lit(7), lit(20)).with_hms(lit(20), lit(17), lit(0));25/// // or26/// let args = DatetimeArgs::new(lit(1969), lit(7), lit(20)).with_hour(lit(20)).with_minute(lit(17));27///28/// // construct a DatetimeArgs using existing columns29/// let args = DatetimeArgs::new(lit(2023), col("month"), col("day"));30/// ```31#[derive(Debug, Clone)]32pub struct DatetimeArgs {33pub year: Expr,34pub month: Expr,35pub day: Expr,36pub hour: Expr,37pub minute: Expr,38pub second: Expr,39pub microsecond: Expr,40pub time_unit: TimeUnit,41pub time_zone: Option<TimeZone>,42pub ambiguous: Expr,43}4445impl Default for DatetimeArgs {46fn default() -> Self {47Self {48year: lit(1970),49month: lit(1),50day: lit(1),51hour: lit(0),52minute: lit(0),53second: lit(0),54microsecond: lit(0),55time_unit: TimeUnit::Microseconds,56time_zone: None,57ambiguous: lit(String::from("raise")),58}59}60}6162impl DatetimeArgs {63/// Construct a new `DatetimeArgs` set to `year`, `month`, `day`64///65/// Other fields default to `lit(0)`. Use the `with_*` methods to set them.66pub fn new(year: Expr, month: Expr, day: Expr) -> Self {67Self {68year,69month,70day,71..Default::default()72}73}7475/// Set `hour`, `minute`, and `second`76///77/// Equivalent to78/// ```ignore79/// self.with_hour(hour)80/// .with_minute(minute)81/// .with_second(second)82/// ```83pub fn with_hms(self, hour: Expr, minute: Expr, second: Expr) -> Self {84Self {85hour,86minute,87second,88..self89}90}9192impl_unit_setter!(with_year(year));93impl_unit_setter!(with_month(month));94impl_unit_setter!(with_day(day));95impl_unit_setter!(with_hour(hour));96impl_unit_setter!(with_minute(minute));97impl_unit_setter!(with_second(second));98impl_unit_setter!(with_microsecond(microsecond));99100pub fn with_time_unit(self, time_unit: TimeUnit) -> Self {101Self { time_unit, ..self }102}103#[cfg(feature = "timezones")]104pub fn with_time_zone(self, time_zone: Option<TimeZone>) -> Self {105Self { time_zone, ..self }106}107#[cfg(feature = "timezones")]108pub fn with_ambiguous(self, ambiguous: Expr) -> Self {109Self { ambiguous, ..self }110}111112fn all_literal(&self) -> bool {113use Expr::*;114[115&self.year,116&self.month,117&self.day,118&self.hour,119&self.minute,120&self.second,121&self.microsecond,122]123.iter()124.all(|e| matches!(e, Literal(_)))125}126127fn as_literal(&self) -> Option<Expr> {128if self.time_zone.is_some() || !self.all_literal() {129return None;130};131let Expr::Literal(lv) = &self.year else {132unreachable!()133};134let year = lv.to_any_value()?.extract()?;135let Expr::Literal(lv) = &self.month else {136unreachable!()137};138let month = lv.to_any_value()?.extract()?;139let Expr::Literal(lv) = &self.day else {140unreachable!()141};142let day = lv.to_any_value()?.extract()?;143let Expr::Literal(lv) = &self.hour else {144unreachable!()145};146let hour = lv.to_any_value()?.extract()?;147let Expr::Literal(lv) = &self.minute else {148unreachable!()149};150let minute = lv.to_any_value()?.extract()?;151let Expr::Literal(lv) = &self.second else {152unreachable!()153};154let second = lv.to_any_value()?.extract()?;155let Expr::Literal(lv) = &self.microsecond else {156unreachable!()157};158let ms: u32 = lv.to_any_value()?.extract()?;159160let dt = chrono::NaiveDateTime::default()161.with_year(year)?162.with_month(month)?163.with_day(day)?164.with_hour(hour)?165.with_minute(minute)?166.with_second(second)?167.with_nanosecond(ms * 1000)?;168169let ts = match self.time_unit {170TimeUnit::Milliseconds => dt.and_utc().timestamp_millis(),171TimeUnit::Microseconds => dt.and_utc().timestamp_micros(),172TimeUnit::Nanoseconds => dt.and_utc().timestamp_nanos_opt()?,173};174175Some(176Expr::Literal(LiteralValue::Scalar(Scalar::new(177DataType::Datetime(self.time_unit, None),178AnyValue::Datetime(ts, self.time_unit, None),179)))180.alias(PlSmallStr::from_static("datetime")),181)182}183}184185/// Construct a column of `Datetime` from the provided [`DatetimeArgs`].186pub fn datetime(args: DatetimeArgs) -> Expr {187if let Some(e) = args.as_literal() {188return e;189}190191let year = args.year;192let month = args.month;193let day = args.day;194let hour = args.hour;195let minute = args.minute;196let second = args.second;197let microsecond = args.microsecond;198let time_unit = args.time_unit;199let time_zone = args.time_zone;200let ambiguous = args.ambiguous;201202let input = vec![203year,204month,205day,206hour,207minute,208second,209microsecond,210ambiguous,211];212213Expr::Alias(214Arc::new(Expr::Function {215input,216function: FunctionExpr::TemporalExpr(TemporalFunction::DatetimeFunction {217time_unit,218time_zone,219}),220}),221// TODO: follow left-hand rule in Polars 2.0.222PlSmallStr::from_static("datetime"),223)224}225226/// Arguments used by `duration` in order to produce an [`Expr`] of [`Duration`]227///228/// To construct a [`DurationArgs`], use struct literal syntax with `..Default::default()` to leave unspecified fields at229/// their default value of `lit(0)`, as demonstrated below.230///231/// ```232/// # use polars_plan::prelude::*;233/// let args = DurationArgs {234/// days: lit(5),235/// hours: col("num_hours"),236/// minutes: col("num_minutes"),237/// ..Default::default() // other fields are lit(0)238/// };239/// ```240/// If you prefer builder syntax, `with_*` methods are also available.241/// ```242/// # use polars_plan::prelude::*;243/// let args = DurationArgs::new().with_weeks(lit(42)).with_hours(lit(84));244/// ```245#[derive(Debug, Clone)]246pub struct DurationArgs {247pub weeks: Expr,248pub days: Expr,249pub hours: Expr,250pub minutes: Expr,251pub seconds: Expr,252pub milliseconds: Expr,253pub microseconds: Expr,254pub nanoseconds: Expr,255pub time_unit: TimeUnit,256}257258impl Default for DurationArgs {259fn default() -> Self {260Self {261weeks: lit(0),262days: lit(0),263hours: lit(0),264minutes: lit(0),265seconds: lit(0),266milliseconds: lit(0),267microseconds: lit(0),268nanoseconds: lit(0),269time_unit: TimeUnit::Microseconds,270}271}272}273274impl DurationArgs {275/// Create a new [`DurationArgs`] with all fields set to `lit(0)`. Use the `with_*` methods to set the fields.276pub fn new() -> Self {277Self::default()278}279280/// Set `hours`, `minutes`, and `seconds`281///282/// Equivalent to:283///284/// ```ignore285/// self.with_hours(hours)286/// .with_minutes(minutes)287/// .with_seconds(seconds)288/// ```289pub fn with_hms(self, hours: Expr, minutes: Expr, seconds: Expr) -> Self {290Self {291hours,292minutes,293seconds,294..self295}296}297298/// Set `milliseconds`, `microseconds`, and `nanoseconds`299///300/// Equivalent to301/// ```ignore302/// self.with_milliseconds(milliseconds)303/// .with_microseconds(microseconds)304/// .with_nanoseconds(nanoseconds)305/// ```306pub fn with_fractional_seconds(307self,308milliseconds: Expr,309microseconds: Expr,310nanoseconds: Expr,311) -> Self {312Self {313milliseconds,314microseconds,315nanoseconds,316..self317}318}319320impl_unit_setter!(with_weeks(weeks));321impl_unit_setter!(with_days(days));322impl_unit_setter!(with_hours(hours));323impl_unit_setter!(with_minutes(minutes));324impl_unit_setter!(with_seconds(seconds));325impl_unit_setter!(with_milliseconds(milliseconds));326impl_unit_setter!(with_microseconds(microseconds));327impl_unit_setter!(with_nanoseconds(nanoseconds));328329fn all_literal(&self) -> bool {330use Expr::*;331[332&self.weeks,333&self.days,334&self.hours,335&self.seconds,336&self.minutes,337&self.milliseconds,338&self.microseconds,339&self.nanoseconds,340]341.iter()342.all(|e| matches!(e, Literal(_)))343}344345fn as_literal(&self) -> Option<Expr> {346if !self.all_literal() {347return None;348};349let Expr::Literal(lv) = &self.weeks else {350unreachable!()351};352let weeks = lv.to_any_value()?.extract()?;353let Expr::Literal(lv) = &self.days else {354unreachable!()355};356let days = lv.to_any_value()?.extract()?;357let Expr::Literal(lv) = &self.hours else {358unreachable!()359};360let hours = lv.to_any_value()?.extract()?;361let Expr::Literal(lv) = &self.seconds else {362unreachable!()363};364let seconds = lv.to_any_value()?.extract()?;365let Expr::Literal(lv) = &self.minutes else {366unreachable!()367};368let minutes = lv.to_any_value()?.extract()?;369let Expr::Literal(lv) = &self.milliseconds else {370unreachable!()371};372let milliseconds = lv.to_any_value()?.extract()?;373let Expr::Literal(lv) = &self.microseconds else {374unreachable!()375};376let microseconds = lv.to_any_value()?.extract()?;377let Expr::Literal(lv) = &self.nanoseconds else {378unreachable!()379};380let nanoseconds = lv.to_any_value()?.extract()?;381382type D = chrono::Duration;383let delta = D::weeks(weeks)384+ D::days(days)385+ D::hours(hours)386+ D::seconds(seconds)387+ D::minutes(minutes)388+ D::milliseconds(milliseconds)389+ D::microseconds(microseconds)390+ D::nanoseconds(nanoseconds);391392let d = match self.time_unit {393TimeUnit::Milliseconds => delta.num_milliseconds(),394TimeUnit::Microseconds => delta.num_microseconds()?,395TimeUnit::Nanoseconds => delta.num_nanoseconds()?,396};397398Some(399Expr::Literal(LiteralValue::Scalar(Scalar::new(400DataType::Duration(self.time_unit),401AnyValue::Duration(d, self.time_unit),402)))403.alias(PlSmallStr::from_static("duration")),404)405}406}407408/// Construct a column of [`Duration`] from the provided [`DurationArgs`]409#[cfg(feature = "dtype-duration")]410pub fn duration(args: DurationArgs) -> Expr {411if let Some(e) = args.as_literal() {412return e;413}414Expr::Function {415input: vec![416args.weeks,417args.days,418args.hours,419args.minutes,420args.seconds,421args.milliseconds,422args.microseconds,423args.nanoseconds,424],425function: FunctionExpr::TemporalExpr(TemporalFunction::Duration(args.time_unit)),426}427}428429430