Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pola-rs
GitHub Repository: pola-rs/polars
Path: blob/main/crates/polars-time/src/series/mod.rs
6939 views
1
use std::ops::Div;
2
3
use arrow::temporal_conversions::{MICROSECONDS_IN_DAY, MILLISECONDS_IN_DAY, NANOSECONDS_IN_DAY};
4
use polars_core::prelude::arity::unary_elementwise_values;
5
use polars_core::prelude::*;
6
7
use crate::chunkedarray::*;
8
9
pub trait AsSeries {
10
fn as_series(&self) -> &Series;
11
}
12
13
impl AsSeries for Series {
14
fn as_series(&self) -> &Series {
15
self
16
}
17
}
18
19
pub trait TemporalMethods: AsSeries {
20
/// Extract hour from underlying NaiveDateTime representation.
21
/// Returns the hour number from 0 to 23.
22
fn hour(&self) -> PolarsResult<Int8Chunked> {
23
let s = self.as_series();
24
match s.dtype() {
25
#[cfg(feature = "dtype-datetime")]
26
DataType::Datetime(_, _) => s.datetime().map(|ca| ca.hour()),
27
#[cfg(feature = "dtype-time")]
28
DataType::Time => s.time().map(|ca| ca.hour()),
29
dt => polars_bail!(opq = hour, dt),
30
}
31
}
32
33
/// Extract minute from underlying NaiveDateTime representation.
34
/// Returns the minute number from 0 to 59.
35
fn minute(&self) -> PolarsResult<Int8Chunked> {
36
let s = self.as_series();
37
match s.dtype() {
38
#[cfg(feature = "dtype-datetime")]
39
DataType::Datetime(_, _) => s.datetime().map(|ca| ca.minute()),
40
#[cfg(feature = "dtype-time")]
41
DataType::Time => s.time().map(|ca| ca.minute()),
42
dt => polars_bail!(opq = minute, dt),
43
}
44
}
45
46
/// Extract second from underlying NaiveDateTime representation.
47
/// Returns the second number from 0 to 59.
48
fn second(&self) -> PolarsResult<Int8Chunked> {
49
let s = self.as_series();
50
match s.dtype() {
51
#[cfg(feature = "dtype-datetime")]
52
DataType::Datetime(_, _) => s.datetime().map(|ca| ca.second()),
53
#[cfg(feature = "dtype-time")]
54
DataType::Time => s.time().map(|ca| ca.second()),
55
dt => polars_bail!(opq = second, dt),
56
}
57
}
58
59
/// Returns the number of nanoseconds since the whole non-leap second.
60
/// The range from 1,000,000,000 to 1,999,999,999 represents the leap second.
61
fn nanosecond(&self) -> PolarsResult<Int32Chunked> {
62
let s = self.as_series();
63
match s.dtype() {
64
#[cfg(feature = "dtype-datetime")]
65
DataType::Datetime(_, _) => s.datetime().map(|ca| ca.nanosecond()),
66
#[cfg(feature = "dtype-time")]
67
DataType::Time => s.time().map(|ca| ca.nanosecond()),
68
dt => polars_bail!(opq = nanosecond, dt),
69
}
70
}
71
72
/// Extract day from underlying NaiveDateTime representation.
73
/// Returns the day of month starting from 1.
74
///
75
/// The return value ranges from 1 to 31. (The last day of month differs by months.)
76
fn day(&self) -> PolarsResult<Int8Chunked> {
77
let s = self.as_series();
78
match s.dtype() {
79
#[cfg(feature = "dtype-date")]
80
DataType::Date => s.date().map(|ca| ca.day()),
81
#[cfg(feature = "dtype-datetime")]
82
DataType::Datetime(_, _) => s.datetime().map(|ca| ca.day()),
83
dt => polars_bail!(opq = day, dt),
84
}
85
}
86
/// Returns the ISO weekday number where monday = 1 and sunday = 7
87
fn weekday(&self) -> PolarsResult<Int8Chunked> {
88
let s = self.as_series();
89
match s.dtype() {
90
#[cfg(feature = "dtype-date")]
91
DataType::Date => s.date().map(|ca| {
92
// Closed formula to find weekday, no need to go via Chrono.
93
// The 4 comes from the fact that 1970-01-01 was a Thursday.
94
// We do an extra `+ 7` then `% 7` to ensure the result is non-negative.
95
unary_elementwise_values(ca.physical(), |t| (((t - 4) % 7 + 7) % 7 + 1) as i8)
96
}),
97
#[cfg(feature = "dtype-datetime")]
98
DataType::Datetime(time_unit, time_zone) => s.datetime().map(|ca| {
99
match time_zone.as_deref().map(|x| x.as_str()) {
100
Some("UTC") | None => {
101
// fastpath!
102
// Same idea as above, but we need to subtract 1 for dates
103
// before 1970-01-01 with non-zero sub-daily components.
104
let divisor = match time_unit {
105
TimeUnit::Milliseconds => MILLISECONDS_IN_DAY,
106
TimeUnit::Microseconds => MICROSECONDS_IN_DAY,
107
TimeUnit::Nanoseconds => NANOSECONDS_IN_DAY,
108
};
109
unary_elementwise_values(ca.physical(), |t| {
110
let t = t / divisor - ((t < 0 && t % divisor != 0) as i64);
111
(((t - 4) % 7 + 7) % 7 + 1) as i8
112
})
113
},
114
_ => ca.weekday(),
115
}
116
}),
117
dt => polars_bail!(opq = weekday, dt),
118
}
119
}
120
121
/// Returns the ISO week number starting from 1.
122
/// The return value ranges from 1 to 53. (The last week of year differs by years.)
123
fn week(&self) -> PolarsResult<Int8Chunked> {
124
let s = self.as_series();
125
match s.dtype() {
126
#[cfg(feature = "dtype-date")]
127
DataType::Date => s.date().map(|ca| ca.week()),
128
#[cfg(feature = "dtype-datetime")]
129
DataType::Datetime(_, _) => s.datetime().map(|ca| ca.week()),
130
dt => polars_bail!(opq = week, dt),
131
}
132
}
133
134
/// Returns the day of year starting from 1.
135
///
136
/// The return value ranges from 1 to 366. (The last day of year differs by years.)
137
fn ordinal_day(&self) -> PolarsResult<Int16Chunked> {
138
let s = self.as_series();
139
match s.dtype() {
140
#[cfg(feature = "dtype-date")]
141
DataType::Date => s.date().map(|ca| ca.ordinal()),
142
#[cfg(feature = "dtype-datetime")]
143
DataType::Datetime(_, _) => s.datetime().map(|ca| ca.ordinal()),
144
dt => polars_bail!(opq = ordinal_day, dt),
145
}
146
}
147
148
/// Calculate the millennium from the underlying NaiveDateTime representation.
149
fn millennium(&self) -> PolarsResult<Int32Chunked> {
150
let s = self.as_series();
151
match s.dtype() {
152
// note: adjust by one for the years on the <n>000 boundaries.
153
// (2000 is the end of the 2nd millennium; 2001 is the beginning of the 3rd).
154
#[cfg(feature = "dtype-date")]
155
DataType::Date => s.date().map(|ca| (ca.year() - 1i32).div(1000f64) + 1),
156
#[cfg(feature = "dtype-datetime")]
157
DataType::Datetime(_, _) => s.datetime().map(|ca| (ca.year() - 1i32).div(1000f64) + 1),
158
dt => polars_bail!(opq = century, dt),
159
}
160
}
161
162
/// Calculate the millennium from the underlying NaiveDateTime representation.
163
fn century(&self) -> PolarsResult<Int32Chunked> {
164
let s = self.as_series();
165
match s.dtype() {
166
// note: adjust by one for years on the <nn>00 boundaries.
167
// (1900 is the end of the 19th century; 1901 is the beginning of the 20th).
168
#[cfg(feature = "dtype-date")]
169
DataType::Date => s.date().map(|ca| (ca.year() - 1i32).div(100f64) + 1),
170
#[cfg(feature = "dtype-datetime")]
171
DataType::Datetime(_, _) => s.datetime().map(|ca| (ca.year() - 1i32).div(100f64) + 1),
172
dt => polars_bail!(opq = century, dt),
173
}
174
}
175
176
/// Extract year from underlying NaiveDateTime representation.
177
/// Returns the year number in the calendar date.
178
fn year(&self) -> PolarsResult<Int32Chunked> {
179
let s = self.as_series();
180
match s.dtype() {
181
#[cfg(feature = "dtype-date")]
182
DataType::Date => s.date().map(|ca| ca.year()),
183
#[cfg(feature = "dtype-datetime")]
184
DataType::Datetime(_, _) => s.datetime().map(|ca| ca.year()),
185
dt => polars_bail!(opq = year, dt),
186
}
187
}
188
189
fn iso_year(&self) -> PolarsResult<Int32Chunked> {
190
let s = self.as_series();
191
match s.dtype() {
192
#[cfg(feature = "dtype-date")]
193
DataType::Date => s.date().map(|ca| ca.iso_year()),
194
#[cfg(feature = "dtype-datetime")]
195
DataType::Datetime(_, _) => s.datetime().map(|ca| ca.iso_year()),
196
dt => polars_bail!(opq = iso_year, dt),
197
}
198
}
199
200
/// Extract ordinal year from underlying NaiveDateTime representation.
201
/// Returns the year number in the calendar date.
202
fn ordinal_year(&self) -> PolarsResult<Int32Chunked> {
203
let s = self.as_series();
204
match s.dtype() {
205
#[cfg(feature = "dtype-date")]
206
DataType::Date => s.date().map(|ca| ca.year()),
207
#[cfg(feature = "dtype-datetime")]
208
DataType::Datetime(_, _) => s.datetime().map(|ca| ca.year()),
209
dt => polars_bail!(opq = ordinal_year, dt),
210
}
211
}
212
213
/// Extract year from underlying NaiveDateTime representation.
214
/// Returns whether the year is a leap year.
215
fn is_leap_year(&self) -> PolarsResult<BooleanChunked> {
216
let s = self.as_series();
217
match s.dtype() {
218
#[cfg(feature = "dtype-date")]
219
DataType::Date => s.date().map(|ca| ca.is_leap_year()),
220
#[cfg(feature = "dtype-datetime")]
221
DataType::Datetime(_, _) => s.datetime().map(|ca| ca.is_leap_year()),
222
dt => polars_bail!(opq = is_leap_year, dt),
223
}
224
}
225
226
/// Extract quarter from underlying NaiveDateTime representation.
227
/// Quarters range from 1 to 4.
228
fn quarter(&self) -> PolarsResult<Int8Chunked> {
229
let s = self.as_series();
230
match s.dtype() {
231
#[cfg(feature = "dtype-date")]
232
DataType::Date => s.date().map(|ca| ca.quarter()),
233
#[cfg(feature = "dtype-datetime")]
234
DataType::Datetime(_, _) => s.datetime().map(|ca| ca.quarter()),
235
dt => polars_bail!(opq = quarter, dt),
236
}
237
}
238
239
/// Extract month from underlying NaiveDateTime representation.
240
/// Returns the month number starting from 1.
241
///
242
/// The return value ranges from 1 to 12.
243
fn month(&self) -> PolarsResult<Int8Chunked> {
244
let s = self.as_series();
245
match s.dtype() {
246
#[cfg(feature = "dtype-date")]
247
DataType::Date => s.date().map(|ca| ca.month()),
248
#[cfg(feature = "dtype-datetime")]
249
DataType::Datetime(_, _) => s.datetime().map(|ca| ca.month()),
250
dt => polars_bail!(opq = month, dt),
251
}
252
}
253
254
/// Returns the number of days in the month of the underlying NaiveDateTime
255
/// representation.
256
fn days_in_month(&self) -> PolarsResult<Int8Chunked> {
257
let s = self.as_series();
258
match s.dtype() {
259
#[cfg(feature = "dtype-date")]
260
DataType::Date => s.date().map(|ca| ca.days_in_month()),
261
#[cfg(feature = "dtype-datetime")]
262
DataType::Datetime(_, _) => s.datetime().map(|ca| ca.days_in_month()),
263
dt => polars_bail!(opq = days_in_month, dt),
264
}
265
}
266
267
/// Convert Time into String with the given format.
268
/// See [chrono strftime/strptime](https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html).
269
fn to_string(&self, format: &str) -> PolarsResult<Series> {
270
let s = self.as_series();
271
match s.dtype() {
272
#[cfg(feature = "dtype-datetime")]
273
DataType::Datetime(_, _) => {
274
let format = get_strftime_format(format, s.dtype())?;
275
s.datetime()
276
.map(|ca| Ok(ca.to_string(format.as_str())?.into_series()))?
277
},
278
#[cfg(feature = "dtype-date")]
279
DataType::Date => {
280
let format = get_strftime_format(format, s.dtype())?;
281
s.date()
282
.map(|ca| Ok(ca.to_string(format.as_str())?.into_series()))?
283
},
284
#[cfg(feature = "dtype-time")]
285
DataType::Time => {
286
let format = get_strftime_format(format, s.dtype())?;
287
s.time()
288
.map(|ca| ca.to_string(format.as_str()).into_series())
289
},
290
#[cfg(feature = "dtype-duration")]
291
DataType::Duration(_) => s
292
.duration()
293
.map(|ca| Ok(ca.to_string(format)?.into_series()))?,
294
dt => polars_bail!(opq = to_string, dt),
295
}
296
}
297
298
/// Convert from Time into String with the given format.
299
/// See [chrono strftime/strptime](https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html).
300
///
301
/// Alias for `to_string`.
302
fn strftime(&self, format: &str) -> PolarsResult<Series> {
303
self.to_string(format)
304
}
305
306
#[cfg(feature = "temporal")]
307
/// Convert date(time) object to timestamp in [`TimeUnit`].
308
fn timestamp(&self, tu: TimeUnit) -> PolarsResult<Int64Chunked> {
309
let s = self.as_series();
310
if matches!(s.dtype(), DataType::Time | DataType::Duration(_)) {
311
polars_bail!(opq = timestamp, s.dtype());
312
} else {
313
s.cast(&DataType::Datetime(tu, None))
314
.map(|s| s.datetime().unwrap().physical().clone())
315
}
316
}
317
}
318
319
impl<T: ?Sized + AsSeries> TemporalMethods for T {}
320
321