Skip to main content

polars_time/series/
mod.rs

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