1use chrono::{Datelike, Timelike};
21use polars_error::PolarsResult;
22
23use super::arity::unary;
24use crate::array::*;
25use crate::datatypes::*;
26use crate::temporal_conversions::*;
27use crate::types::NativeType;
28
29trait Int8Weekday: Datelike {
32 fn i8_weekday(&self) -> i8 {
33 self.weekday().number_from_monday().try_into().unwrap()
34 }
35}
36
37impl Int8Weekday for chrono::NaiveDateTime {}
38impl<T: chrono::TimeZone> Int8Weekday for chrono::DateTime<T> {}
39
40trait Int8IsoWeek: Datelike {
43 fn i8_iso_week(&self) -> i8 {
44 self.iso_week().week().try_into().unwrap()
45 }
46}
47
48impl Int8IsoWeek for chrono::NaiveDateTime {}
49impl<T: chrono::TimeZone> Int8IsoWeek for chrono::DateTime<T> {}
50
51macro_rules! date_like {
54 ($extract:ident, $array:ident, $dtype:path) => {
55 match $array.dtype().to_logical_type() {
56 ArrowDataType::Date32 | ArrowDataType::Date64 | ArrowDataType::Timestamp(_, None) => {
57 date_variants($array, $dtype, |x| x.$extract().try_into().unwrap())
58 },
59 ArrowDataType::Timestamp(time_unit, Some(timezone_str)) => {
60 let array = $array.as_any().downcast_ref().unwrap();
61
62 if let Ok(timezone) = parse_offset(timezone_str.as_str()) {
63 Ok(extract_impl(array, *time_unit, timezone, |x| {
64 x.$extract().try_into().unwrap()
65 }))
66 } else {
67 chrono_tz(array, *time_unit, timezone_str.as_str(), |x| {
68 x.$extract().try_into().unwrap()
69 })
70 }
71 },
72 _ => unimplemented!(),
73 }
74 };
75}
76
77pub fn year(array: &dyn Array) -> PolarsResult<PrimitiveArray<i32>> {
79 date_like!(year, array, ArrowDataType::Int32)
80}
81
82pub fn month(array: &dyn Array) -> PolarsResult<PrimitiveArray<i8>> {
86 date_like!(month, array, ArrowDataType::Int8)
87}
88
89pub fn day(array: &dyn Array) -> PolarsResult<PrimitiveArray<i8>> {
93 date_like!(day, array, ArrowDataType::Int8)
94}
95
96pub fn weekday(array: &dyn Array) -> PolarsResult<PrimitiveArray<i8>> {
100 date_like!(i8_weekday, array, ArrowDataType::Int8)
101}
102
103pub fn iso_week(array: &dyn Array) -> PolarsResult<PrimitiveArray<i8>> {
107 date_like!(i8_iso_week, array, ArrowDataType::Int8)
108}
109
110macro_rules! time_like {
113 ($extract:ident, $array:ident, $dtype:path) => {
114 match $array.dtype().to_logical_type() {
115 ArrowDataType::Date32 | ArrowDataType::Date64 | ArrowDataType::Timestamp(_, None) => {
116 date_variants($array, $dtype, |x| x.$extract().try_into().unwrap())
117 },
118 ArrowDataType::Time32(_) | ArrowDataType::Time64(_) => {
119 time_variants($array, ArrowDataType::UInt32, |x| {
120 x.$extract().try_into().unwrap()
121 })
122 },
123 ArrowDataType::Timestamp(time_unit, Some(timezone_str)) => {
124 let array = $array.as_any().downcast_ref().unwrap();
125
126 if let Ok(timezone) = parse_offset(timezone_str.as_str()) {
127 Ok(extract_impl(array, *time_unit, timezone, |x| {
128 x.$extract().try_into().unwrap()
129 }))
130 } else {
131 chrono_tz(array, *time_unit, timezone_str.as_str(), |x| {
132 x.$extract().try_into().unwrap()
133 })
134 }
135 },
136 _ => unimplemented!(),
137 }
138 };
139}
140
141pub fn hour(array: &dyn Array) -> PolarsResult<PrimitiveArray<i8>> {
145 time_like!(hour, array, ArrowDataType::Int8)
146}
147
148pub fn minute(array: &dyn Array) -> PolarsResult<PrimitiveArray<i8>> {
152 time_like!(minute, array, ArrowDataType::Int8)
153}
154
155pub fn second(array: &dyn Array) -> PolarsResult<PrimitiveArray<i8>> {
159 time_like!(second, array, ArrowDataType::Int8)
160}
161
162pub fn nanosecond(array: &dyn Array) -> PolarsResult<PrimitiveArray<i32>> {
168 time_like!(nanosecond, array, ArrowDataType::Int32)
169}
170
171fn date_variants<F, O>(
172 array: &dyn Array,
173 dtype: ArrowDataType,
174 op: F,
175) -> PolarsResult<PrimitiveArray<O>>
176where
177 O: NativeType,
178 F: Fn(chrono::NaiveDateTime) -> O,
179{
180 match array.dtype().to_logical_type() {
181 ArrowDataType::Date32 => {
182 let array = array
183 .as_any()
184 .downcast_ref::<PrimitiveArray<i32>>()
185 .unwrap();
186 Ok(unary(array, |x| op(date32_to_datetime(x)), dtype))
187 },
188 ArrowDataType::Date64 => {
189 let array = array
190 .as_any()
191 .downcast_ref::<PrimitiveArray<i64>>()
192 .unwrap();
193 Ok(unary(array, |x| op(date64_to_datetime(x)), dtype))
194 },
195 ArrowDataType::Timestamp(time_unit, None) => {
196 let array = array
197 .as_any()
198 .downcast_ref::<PrimitiveArray<i64>>()
199 .unwrap();
200 let func = match time_unit {
201 TimeUnit::Second => timestamp_s_to_datetime,
202 TimeUnit::Millisecond => timestamp_ms_to_datetime,
203 TimeUnit::Microsecond => timestamp_us_to_datetime,
204 TimeUnit::Nanosecond => timestamp_ns_to_datetime,
205 };
206 Ok(PrimitiveArray::<O>::from_trusted_len_iter(
207 array.iter().map(|v| v.map(|x| op(func(*x)))),
208 ))
209 },
210 _ => unreachable!(),
211 }
212}
213
214fn time_variants<F, O>(
215 array: &dyn Array,
216 dtype: ArrowDataType,
217 op: F,
218) -> PolarsResult<PrimitiveArray<O>>
219where
220 O: NativeType,
221 F: Fn(chrono::NaiveTime) -> O,
222{
223 match array.dtype().to_logical_type() {
224 ArrowDataType::Time32(TimeUnit::Second) => {
225 let array = array
226 .as_any()
227 .downcast_ref::<PrimitiveArray<i32>>()
228 .unwrap();
229 Ok(unary(array, |x| op(time32s_to_time(x)), dtype))
230 },
231 ArrowDataType::Time32(TimeUnit::Millisecond) => {
232 let array = array
233 .as_any()
234 .downcast_ref::<PrimitiveArray<i32>>()
235 .unwrap();
236 Ok(unary(array, |x| op(time32ms_to_time(x)), dtype))
237 },
238 ArrowDataType::Time64(TimeUnit::Microsecond) => {
239 let array = array
240 .as_any()
241 .downcast_ref::<PrimitiveArray<i64>>()
242 .unwrap();
243 Ok(unary(array, |x| op(time64us_to_time(x)), dtype))
244 },
245 ArrowDataType::Time64(TimeUnit::Nanosecond) => {
246 let array = array
247 .as_any()
248 .downcast_ref::<PrimitiveArray<i64>>()
249 .unwrap();
250 Ok(unary(array, |x| op(time64ns_to_time(x)), dtype))
251 },
252 _ => unreachable!(),
253 }
254}
255
256#[cfg(feature = "chrono-tz")]
257fn chrono_tz<F, O>(
258 array: &PrimitiveArray<i64>,
259 time_unit: TimeUnit,
260 timezone_str: &str,
261 op: F,
262) -> PolarsResult<PrimitiveArray<O>>
263where
264 O: NativeType,
265 F: Fn(chrono::DateTime<chrono_tz::Tz>) -> O,
266{
267 let timezone = parse_offset_tz(timezone_str)?;
268 Ok(extract_impl(array, time_unit, timezone, op))
269}
270
271#[cfg(not(feature = "chrono-tz"))]
272fn chrono_tz<F, O>(
273 _: &PrimitiveArray<i64>,
274 _: TimeUnit,
275 timezone_str: &str,
276 _: F,
277) -> PolarsResult<PrimitiveArray<O>>
278where
279 O: NativeType,
280 F: Fn(chrono::DateTime<chrono::FixedOffset>) -> O,
281{
282 panic!(
283 "timezone \"{}\" cannot be parsed (feature chrono-tz is not active)",
284 timezone_str
285 )
286}
287
288fn extract_impl<T, A, F>(
289 array: &PrimitiveArray<i64>,
290 time_unit: TimeUnit,
291 timezone: T,
292 extract: F,
293) -> PrimitiveArray<A>
294where
295 T: chrono::TimeZone,
296 A: NativeType,
297 F: Fn(chrono::DateTime<T>) -> A,
298{
299 match time_unit {
300 TimeUnit::Second => {
301 let op = |x| {
302 let datetime = timestamp_s_to_datetime(x);
303 let offset = timezone.offset_from_utc_datetime(&datetime);
304 extract(chrono::DateTime::<T>::from_naive_utc_and_offset(
305 datetime, offset,
306 ))
307 };
308 unary(array, op, A::PRIMITIVE.into())
309 },
310 TimeUnit::Millisecond => {
311 let op = |x| {
312 let datetime = timestamp_ms_to_datetime(x);
313 let offset = timezone.offset_from_utc_datetime(&datetime);
314 extract(chrono::DateTime::<T>::from_naive_utc_and_offset(
315 datetime, offset,
316 ))
317 };
318 unary(array, op, A::PRIMITIVE.into())
319 },
320 TimeUnit::Microsecond => {
321 let op = |x| {
322 let datetime = timestamp_us_to_datetime(x);
323 let offset = timezone.offset_from_utc_datetime(&datetime);
324 extract(chrono::DateTime::<T>::from_naive_utc_and_offset(
325 datetime, offset,
326 ))
327 };
328 unary(array, op, A::PRIMITIVE.into())
329 },
330 TimeUnit::Nanosecond => {
331 let op = |x| {
332 let datetime = timestamp_ns_to_datetime(x);
333 let offset = timezone.offset_from_utc_datetime(&datetime);
334 extract(chrono::DateTime::<T>::from_naive_utc_and_offset(
335 datetime, offset,
336 ))
337 };
338 unary(array, op, A::PRIMITIVE.into())
339 },
340 }
341}