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