Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pola-rs
GitHub Repository: pola-rs/polars
Path: blob/main/crates/polars-plan/src/dsl/functions/temporal.rs
8446 views
1
use arrow::temporal_conversions::{NANOSECONDS, NANOSECONDS_IN_DAY};
2
use chrono::{Datelike, Timelike};
3
use polars_utils::array;
4
5
use super::*;
6
7
macro_rules! impl_unit_setter {
8
($fn_name:ident($field:ident)) => {
9
#[doc = concat!("Set the ", stringify!($field))]
10
pub fn $fn_name(mut self, n: Expr) -> Self {
11
self.$field = n.into();
12
self
13
}
14
};
15
}
16
17
/// Arguments used by `datetime` in order to produce an [`Expr`] of Datetime
18
///
19
/// Construct a [`DatetimeArgs`] with `DatetimeArgs::new(y, m, d)`. This will set the other time units to `lit(0)`. You
20
/// can then set the other fields with the `with_*` methods, or use `with_hms` to set `hour`, `minute`, and `second` all
21
/// at once.
22
///
23
/// # Examples
24
/// ```
25
/// use polars_plan::prelude::*;
26
/// // construct a DatetimeArgs set to July 20, 1969 at 20:17
27
/// let args = DatetimeArgs::new(lit(1969), lit(7), lit(20)).with_hms(lit(20), lit(17), lit(0));
28
/// // or
29
/// let args = DatetimeArgs::new(lit(1969), lit(7), lit(20)).with_hour(lit(20)).with_minute(lit(17));
30
///
31
/// // construct a DatetimeArgs using existing columns
32
/// let args = DatetimeArgs::new(lit(2023), col("month"), col("day"));
33
/// ```
34
#[derive(Debug, Clone)]
35
pub struct DatetimeArgs {
36
pub year: Expr,
37
pub month: Expr,
38
pub day: Expr,
39
pub hour: Expr,
40
pub minute: Expr,
41
pub second: Expr,
42
pub microsecond: Expr,
43
pub time_unit: TimeUnit,
44
pub time_zone: Option<TimeZone>,
45
pub ambiguous: Expr,
46
}
47
48
impl Default for DatetimeArgs {
49
fn default() -> Self {
50
Self {
51
year: lit(1970),
52
month: lit(1),
53
day: lit(1),
54
hour: lit(0),
55
minute: lit(0),
56
second: lit(0),
57
microsecond: lit(0),
58
time_unit: TimeUnit::Microseconds,
59
time_zone: None,
60
ambiguous: lit(String::from("raise")),
61
}
62
}
63
}
64
65
impl DatetimeArgs {
66
/// Construct a new `DatetimeArgs` set to `year`, `month`, `day`
67
///
68
/// Other fields default to `lit(0)`. Use the `with_*` methods to set them.
69
pub fn new(year: Expr, month: Expr, day: Expr) -> Self {
70
Self {
71
year,
72
month,
73
day,
74
..Default::default()
75
}
76
}
77
78
/// Set `hour`, `minute`, and `second`
79
///
80
/// Equivalent to
81
/// ```ignore
82
/// self.with_hour(hour)
83
/// .with_minute(minute)
84
/// .with_second(second)
85
/// ```
86
pub fn with_hms(self, hour: Expr, minute: Expr, second: Expr) -> Self {
87
Self {
88
hour,
89
minute,
90
second,
91
..self
92
}
93
}
94
95
impl_unit_setter!(with_year(year));
96
impl_unit_setter!(with_month(month));
97
impl_unit_setter!(with_day(day));
98
impl_unit_setter!(with_hour(hour));
99
impl_unit_setter!(with_minute(minute));
100
impl_unit_setter!(with_second(second));
101
impl_unit_setter!(with_microsecond(microsecond));
102
103
pub fn with_time_unit(self, time_unit: TimeUnit) -> Self {
104
Self { time_unit, ..self }
105
}
106
#[cfg(feature = "timezones")]
107
pub fn with_time_zone(self, time_zone: Option<TimeZone>) -> Self {
108
Self { time_zone, ..self }
109
}
110
#[cfg(feature = "timezones")]
111
pub fn with_ambiguous(self, ambiguous: Expr) -> Self {
112
Self { ambiguous, ..self }
113
}
114
115
fn all_literal(&self) -> bool {
116
use Expr::*;
117
[
118
&self.year,
119
&self.month,
120
&self.day,
121
&self.hour,
122
&self.minute,
123
&self.second,
124
&self.microsecond,
125
]
126
.iter()
127
.all(|e| matches!(e, Literal(_)))
128
}
129
130
fn as_literal(&self) -> Option<Expr> {
131
if self.time_zone.is_some() || !self.all_literal() {
132
return None;
133
};
134
let Expr::Literal(lv) = &self.year else {
135
unreachable!()
136
};
137
let year = lv.to_any_value()?.extract()?;
138
let Expr::Literal(lv) = &self.month else {
139
unreachable!()
140
};
141
let month = lv.to_any_value()?.extract()?;
142
let Expr::Literal(lv) = &self.day else {
143
unreachable!()
144
};
145
let day = lv.to_any_value()?.extract()?;
146
let Expr::Literal(lv) = &self.hour else {
147
unreachable!()
148
};
149
let hour = lv.to_any_value()?.extract()?;
150
let Expr::Literal(lv) = &self.minute else {
151
unreachable!()
152
};
153
let minute = lv.to_any_value()?.extract()?;
154
let Expr::Literal(lv) = &self.second else {
155
unreachable!()
156
};
157
let second = lv.to_any_value()?.extract()?;
158
let Expr::Literal(lv) = &self.microsecond else {
159
unreachable!()
160
};
161
let ms: u32 = lv.to_any_value()?.extract()?;
162
163
let dt = chrono::NaiveDateTime::default()
164
.with_year(year)?
165
.with_month(month)?
166
.with_day(day)?
167
.with_hour(hour)?
168
.with_minute(minute)?
169
.with_second(second)?
170
.with_nanosecond(ms * 1000)?;
171
172
let ts = match self.time_unit {
173
TimeUnit::Milliseconds => dt.and_utc().timestamp_millis(),
174
TimeUnit::Microseconds => dt.and_utc().timestamp_micros(),
175
TimeUnit::Nanoseconds => dt.and_utc().timestamp_nanos_opt()?,
176
};
177
178
Some(
179
Expr::Literal(LiteralValue::Scalar(Scalar::new(
180
DataType::Datetime(self.time_unit, None),
181
AnyValue::Datetime(ts, self.time_unit, None),
182
)))
183
.alias(PlSmallStr::from_static("datetime")),
184
)
185
}
186
}
187
188
/// Construct a column of `Datetime` from the provided [`DatetimeArgs`].
189
pub fn datetime(args: DatetimeArgs) -> Expr {
190
if let Some(e) = args.as_literal() {
191
return e;
192
}
193
194
let year = args.year;
195
let month = args.month;
196
let day = args.day;
197
let hour = args.hour;
198
let minute = args.minute;
199
let second = args.second;
200
let microsecond = args.microsecond;
201
let time_unit = args.time_unit;
202
let time_zone = args.time_zone;
203
let ambiguous = args.ambiguous;
204
205
let input = vec![
206
year,
207
month,
208
day,
209
hour,
210
minute,
211
second,
212
microsecond,
213
ambiguous,
214
];
215
216
Expr::Alias(
217
Arc::new(Expr::Function {
218
input,
219
function: FunctionExpr::TemporalExpr(TemporalFunction::DatetimeFunction {
220
time_unit,
221
time_zone,
222
}),
223
}),
224
// TODO: follow left-hand rule in Polars 2.0.
225
PlSmallStr::from_static("datetime"),
226
)
227
}
228
229
/// Arguments used by `duration` in order to produce an [`Expr`] of [`Duration`]
230
///
231
/// To construct a [`DurationArgs`], use struct literal syntax with `..Default::default()` to leave unspecified fields at
232
/// their default value of `lit(0)`, as demonstrated below.
233
///
234
/// ```
235
/// # use polars_plan::prelude::*;
236
/// let args = DurationArgs {
237
/// days: lit(5),
238
/// hours: col("num_hours"),
239
/// minutes: col("num_minutes"),
240
/// ..Default::default() // other fields are lit(0)
241
/// };
242
/// ```
243
/// If you prefer builder syntax, `with_*` methods are also available.
244
/// ```
245
/// # use polars_plan::prelude::*;
246
/// let args = DurationArgs::new().with_weeks(lit(42)).with_hours(lit(84));
247
/// ```
248
#[derive(Debug, Clone)]
249
pub struct DurationArgs {
250
pub weeks: Expr,
251
pub days: Expr,
252
pub hours: Expr,
253
pub minutes: Expr,
254
pub seconds: Expr,
255
pub milliseconds: Expr,
256
pub microseconds: Expr,
257
pub nanoseconds: Expr,
258
pub time_unit: TimeUnit,
259
}
260
261
impl Default for DurationArgs {
262
fn default() -> Self {
263
Self {
264
weeks: lit(0),
265
days: lit(0),
266
hours: lit(0),
267
minutes: lit(0),
268
seconds: lit(0),
269
milliseconds: lit(0),
270
microseconds: lit(0),
271
nanoseconds: lit(0),
272
time_unit: TimeUnit::Microseconds,
273
}
274
}
275
}
276
277
impl DurationArgs {
278
/// Create a new [`DurationArgs`] with all fields set to `lit(0)`. Use the `with_*` methods to set the fields.
279
pub fn new() -> Self {
280
Self::default()
281
}
282
283
/// Set `hours`, `minutes`, and `seconds`
284
///
285
/// Equivalent to:
286
///
287
/// ```ignore
288
/// self.with_hours(hours)
289
/// .with_minutes(minutes)
290
/// .with_seconds(seconds)
291
/// ```
292
pub fn with_hms(self, hours: Expr, minutes: Expr, seconds: Expr) -> Self {
293
Self {
294
hours,
295
minutes,
296
seconds,
297
..self
298
}
299
}
300
301
/// Set `milliseconds`, `microseconds`, and `nanoseconds`
302
///
303
/// Equivalent to
304
/// ```ignore
305
/// self.with_milliseconds(milliseconds)
306
/// .with_microseconds(microseconds)
307
/// .with_nanoseconds(nanoseconds)
308
/// ```
309
pub fn with_fractional_seconds(
310
self,
311
milliseconds: Expr,
312
microseconds: Expr,
313
nanoseconds: Expr,
314
) -> Self {
315
Self {
316
milliseconds,
317
microseconds,
318
nanoseconds,
319
..self
320
}
321
}
322
323
impl_unit_setter!(with_weeks(weeks));
324
impl_unit_setter!(with_days(days));
325
impl_unit_setter!(with_hours(hours));
326
impl_unit_setter!(with_minutes(minutes));
327
impl_unit_setter!(with_seconds(seconds));
328
impl_unit_setter!(with_milliseconds(milliseconds));
329
impl_unit_setter!(with_microseconds(microseconds));
330
impl_unit_setter!(with_nanoseconds(nanoseconds));
331
332
fn as_literal(&self) -> Option<Expr> {
333
use time_unit::convert_time_units;
334
335
let extract_i64_as_i128 = |e: &Expr| {
336
let Expr::Literal(lv) = e else { return None };
337
let av = lv.to_any_value()?;
338
if !av.is_integer() {
339
return None;
340
};
341
av.extract::<i64>().map(i128::from)
342
};
343
let extract_f64 = |e: &Expr| {
344
let Expr::Literal(lv) = e else { return None };
345
let av = lv.to_any_value()?;
346
av.extract::<f64>()
347
};
348
let overflows_i64 = |e: &&Expr| {
349
let Expr::Literal(lv) = e else { return false };
350
let av = lv.to_any_value();
351
match av {
352
Some(AnyValue::UInt128(x)) => x > i64::MAX as u128,
353
Some(AnyValue::UInt64(x)) => x > i64::MAX as u64,
354
Some(AnyValue::Int128(x)) => x < i64::MIN as i128 || x > i64::MAX as i128,
355
_ => false,
356
}
357
};
358
359
let fields = [
360
&self.weeks,
361
&self.days,
362
&self.hours,
363
&self.minutes,
364
&self.seconds,
365
&self.milliseconds,
366
&self.microseconds,
367
&self.nanoseconds,
368
];
369
370
// go into the function expr implementation to throw an error
371
if fields.iter().any(overflows_i64) {
372
return None;
373
}
374
375
let d = if let Some(fields) = array::try_map(fields, extract_i64_as_i128) {
376
let [w, d, h, m, s, ms, us, ns] = fields;
377
378
let total_ns = w * 7 * NANOSECONDS_IN_DAY as i128
379
+ d * NANOSECONDS_IN_DAY as i128
380
+ h * 3600 * NANOSECONDS as i128
381
+ m * 60 * NANOSECONDS as i128
382
+ s * NANOSECONDS as i128
383
+ ms * 1_000_000
384
+ us * 1_000
385
+ ns;
386
387
convert_time_units(total_ns, TimeUnit::Nanoseconds, self.time_unit) as i64
388
} else if let Some(fields) = array::try_map(fields, extract_f64) {
389
let [w, d, h, m, s, ms, us, ns] = fields;
390
391
let total_ns = w * 7.0 * NANOSECONDS_IN_DAY as f64
392
+ d * NANOSECONDS_IN_DAY as f64
393
+ h * 3600.0 * NANOSECONDS as f64
394
+ m * 60.0 * NANOSECONDS as f64
395
+ s * NANOSECONDS as f64
396
+ ms * 1_000_000.0
397
+ us * 1_000.0
398
+ ns;
399
400
convert_time_units(total_ns, TimeUnit::Nanoseconds, self.time_unit) as i64
401
} else {
402
return None;
403
};
404
405
Some(
406
Expr::Literal(LiteralValue::Scalar(Scalar::new(
407
DataType::Duration(self.time_unit),
408
AnyValue::Duration(d, self.time_unit),
409
)))
410
.alias(PlSmallStr::from_static("duration")),
411
)
412
}
413
}
414
415
/// Construct a column of [`Duration`] from the provided [`DurationArgs`]
416
#[cfg(feature = "dtype-duration")]
417
pub fn duration(args: DurationArgs) -> Expr {
418
if let Some(e) = args.as_literal() {
419
return e;
420
}
421
Expr::Function {
422
input: vec![
423
args.weeks,
424
args.days,
425
args.hours,
426
args.minutes,
427
args.seconds,
428
args.milliseconds,
429
args.microseconds,
430
args.nanoseconds,
431
],
432
function: FunctionExpr::TemporalExpr(TemporalFunction::Duration(args.time_unit)),
433
}
434
}
435
436