polars_time/series/
mod.rs

1use std::ops::{Deref, Div};
2
3use arrow::temporal_conversions::{MICROSECONDS_IN_DAY, MILLISECONDS_IN_DAY, NANOSECONDS_IN_DAY};
4use polars_core::prelude::arity::unary_elementwise_values;
5use polars_core::prelude::*;
6
7use crate::chunkedarray::*;
8
9pub trait AsSeries {
10    fn as_series(&self) -> &Series;
11}
12
13impl AsSeries for Series {
14    fn as_series(&self) -> &Series {
15        self
16    }
17}
18
19pub 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, |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() {
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, |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    /// Convert Time into String with the given format.
255    /// See [chrono strftime/strptime](https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html).
256    fn to_string(&self, format: &str) -> PolarsResult<Series> {
257        let s = self.as_series();
258        match s.dtype() {
259            #[cfg(feature = "dtype-datetime")]
260            DataType::Datetime(_, _) => {
261                let format = get_strftime_format(format, s.dtype())?;
262                s.datetime()
263                    .map(|ca| Ok(ca.to_string(format.as_str())?.into_series()))?
264            },
265            #[cfg(feature = "dtype-date")]
266            DataType::Date => {
267                let format = get_strftime_format(format, s.dtype())?;
268                s.date()
269                    .map(|ca| Ok(ca.to_string(format.as_str())?.into_series()))?
270            },
271            #[cfg(feature = "dtype-time")]
272            DataType::Time => {
273                let format = get_strftime_format(format, s.dtype())?;
274                s.time()
275                    .map(|ca| ca.to_string(format.as_str()).into_series())
276            },
277            #[cfg(feature = "dtype-duration")]
278            DataType::Duration(_) => s
279                .duration()
280                .map(|ca| Ok(ca.to_string(format)?.into_series()))?,
281            dt => polars_bail!(opq = to_string, dt),
282        }
283    }
284
285    /// Convert from Time into String with the given format.
286    /// See [chrono strftime/strptime](https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html).
287    ///
288    /// Alias for `to_string`.
289    fn strftime(&self, format: &str) -> PolarsResult<Series> {
290        self.to_string(format)
291    }
292
293    #[cfg(feature = "temporal")]
294    /// Convert date(time) object to timestamp in [`TimeUnit`].
295    fn timestamp(&self, tu: TimeUnit) -> PolarsResult<Int64Chunked> {
296        let s = self.as_series();
297        if matches!(s.dtype(), DataType::Time | DataType::Duration(_)) {
298            polars_bail!(opq = timestamp, s.dtype());
299        } else {
300            s.cast(&DataType::Datetime(tu, None))
301                .map(|s| s.datetime().unwrap().deref().clone())
302        }
303    }
304}
305
306impl<T: ?Sized + AsSeries> TemporalMethods for T {}