polars_plan/dsl/function_expr/
datetime.rs

1#[cfg(feature = "timezones")]
2use chrono_tz::Tz;
3#[cfg(feature = "timezones")]
4use polars_time::base_utc_offset as base_utc_offset_fn;
5#[cfg(feature = "timezones")]
6use polars_time::dst_offset as dst_offset_fn;
7#[cfg(feature = "offset_by")]
8use polars_time::impl_offset_by;
9#[cfg(any(feature = "dtype-date", feature = "dtype-datetime"))]
10use polars_time::replace::{replace_date, replace_datetime};
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Serialize};
13
14use super::*;
15
16#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
17#[derive(Clone, PartialEq, Debug, Eq, Hash)]
18pub enum TemporalFunction {
19    Millennium,
20    Century,
21    Year,
22    IsLeapYear,
23    IsoYear,
24    Quarter,
25    Month,
26    Week,
27    WeekDay,
28    Day,
29    OrdinalDay,
30    Time,
31    Date,
32    Datetime,
33    Duration(TimeUnit),
34    Hour,
35    Minute,
36    Second,
37    Millisecond,
38    Microsecond,
39    Nanosecond,
40    TotalDays,
41    TotalHours,
42    TotalMinutes,
43    TotalSeconds,
44    TotalMilliseconds,
45    TotalMicroseconds,
46    TotalNanoseconds,
47    ToString(String),
48    CastTimeUnit(TimeUnit),
49    WithTimeUnit(TimeUnit),
50    #[cfg(feature = "timezones")]
51    ConvertTimeZone(TimeZone),
52    TimeStamp(TimeUnit),
53    Truncate,
54    #[cfg(feature = "offset_by")]
55    OffsetBy,
56    #[cfg(feature = "month_start")]
57    MonthStart,
58    #[cfg(feature = "month_end")]
59    MonthEnd,
60    #[cfg(feature = "timezones")]
61    BaseUtcOffset,
62    #[cfg(feature = "timezones")]
63    DSTOffset,
64    Round,
65    Replace,
66    #[cfg(feature = "timezones")]
67    ReplaceTimeZone(Option<TimeZone>, NonExistent),
68    Combine(TimeUnit),
69    DatetimeFunction {
70        time_unit: TimeUnit,
71        time_zone: Option<TimeZone>,
72    },
73}
74
75impl TemporalFunction {
76    pub(super) fn get_field(&self, mapper: FieldsMapper) -> PolarsResult<Field> {
77        use TemporalFunction::*;
78        match self {
79            Millennium | Century => mapper.with_dtype(DataType::Int8),
80            Year | IsoYear => mapper.with_dtype(DataType::Int32),
81            OrdinalDay => mapper.with_dtype(DataType::Int16),
82            Month | Quarter | Week | WeekDay | Day | Hour | Minute | Second => {
83                mapper.with_dtype(DataType::Int8)
84            },
85            Millisecond | Microsecond | Nanosecond => mapper.with_dtype(DataType::Int32),
86            TotalDays | TotalHours | TotalMinutes | TotalSeconds | TotalMilliseconds
87            | TotalMicroseconds | TotalNanoseconds => mapper.with_dtype(DataType::Int64),
88            ToString(_) => mapper.with_dtype(DataType::String),
89            WithTimeUnit(_) => mapper.with_same_dtype(),
90            CastTimeUnit(tu) => mapper.try_map_dtype(|dt| match dt {
91                DataType::Duration(_) => Ok(DataType::Duration(*tu)),
92                DataType::Datetime(_, tz) => Ok(DataType::Datetime(*tu, tz.clone())),
93                dtype => polars_bail!(ComputeError: "expected duration or datetime, got {}", dtype),
94            }),
95            #[cfg(feature = "timezones")]
96            ConvertTimeZone(tz) => mapper.try_map_dtype(|dt| match dt {
97                DataType::Datetime(tu, _) => Ok(DataType::Datetime(*tu, Some(tz.clone()))),
98                dtype => polars_bail!(ComputeError: "expected Datetime, got {}", dtype),
99            }),
100            TimeStamp(_) => mapper.with_dtype(DataType::Int64),
101            IsLeapYear => mapper.with_dtype(DataType::Boolean),
102            Time => mapper.with_dtype(DataType::Time),
103            Duration(tu) => mapper.with_dtype(DataType::Duration(*tu)),
104            Date => mapper.with_dtype(DataType::Date),
105            Datetime => mapper.try_map_dtype(|dt| match dt {
106                DataType::Datetime(tu, _) => Ok(DataType::Datetime(*tu, None)),
107                dtype => polars_bail!(ComputeError: "expected Datetime, got {}", dtype),
108            }),
109            Truncate => mapper.with_same_dtype(),
110            #[cfg(feature = "offset_by")]
111            OffsetBy => mapper.with_same_dtype(),
112            #[cfg(feature = "month_start")]
113            MonthStart => mapper.with_same_dtype(),
114            #[cfg(feature = "month_end")]
115            MonthEnd => mapper.with_same_dtype(),
116            #[cfg(feature = "timezones")]
117            BaseUtcOffset => mapper.with_dtype(DataType::Duration(TimeUnit::Milliseconds)),
118            #[cfg(feature = "timezones")]
119            DSTOffset => mapper.with_dtype(DataType::Duration(TimeUnit::Milliseconds)),
120            Round => mapper.with_same_dtype(),
121            Replace => mapper.with_same_dtype(),
122            #[cfg(feature = "timezones")]
123            ReplaceTimeZone(tz, _non_existent) => mapper.map_datetime_dtype_timezone(tz.as_ref()),
124            DatetimeFunction {
125                time_unit,
126                time_zone,
127            } => Ok(Field::new(
128                PlSmallStr::from_static("datetime"),
129                DataType::Datetime(*time_unit, time_zone.clone()),
130            )),
131            Combine(tu) => mapper.try_map_dtype(|dt| match dt {
132                DataType::Datetime(_, tz) => Ok(DataType::Datetime(*tu, tz.clone())),
133                DataType::Date => Ok(DataType::Datetime(*tu, None)),
134                dtype => {
135                    polars_bail!(ComputeError: "expected Date or Datetime, got {}", dtype)
136                },
137            }),
138        }
139    }
140
141    pub fn function_options(&self) -> FunctionOptions {
142        use TemporalFunction as T;
143        match self {
144            T::Millennium
145            | T::Century
146            | T::Year
147            | T::IsLeapYear
148            | T::IsoYear
149            | T::Quarter
150            | T::Month
151            | T::Week
152            | T::WeekDay
153            | T::Day
154            | T::OrdinalDay
155            | T::Time
156            | T::Date
157            | T::Datetime
158            | T::Hour
159            | T::Minute
160            | T::Second
161            | T::Millisecond
162            | T::Microsecond
163            | T::Nanosecond
164            | T::TotalDays
165            | T::TotalHours
166            | T::TotalMinutes
167            | T::TotalSeconds
168            | T::TotalMilliseconds
169            | T::TotalMicroseconds
170            | T::TotalNanoseconds
171            | T::ToString(_)
172            | T::TimeStamp(_)
173            | T::CastTimeUnit(_)
174            | T::WithTimeUnit(_) => FunctionOptions::elementwise(),
175            #[cfg(feature = "timezones")]
176            T::ConvertTimeZone(_) => FunctionOptions::elementwise(),
177            #[cfg(feature = "month_start")]
178            T::MonthStart => FunctionOptions::elementwise(),
179            #[cfg(feature = "month_end")]
180            T::MonthEnd => FunctionOptions::elementwise(),
181            #[cfg(feature = "timezones")]
182            T::BaseUtcOffset | T::DSTOffset => FunctionOptions::elementwise(),
183            T::Truncate => FunctionOptions::elementwise(),
184            #[cfg(feature = "offset_by")]
185            T::OffsetBy => FunctionOptions::elementwise(),
186            T::Round => FunctionOptions::elementwise(),
187            T::Replace => FunctionOptions::elementwise(),
188            T::Duration(_) => FunctionOptions::elementwise(),
189            #[cfg(feature = "timezones")]
190            T::ReplaceTimeZone(_, _) => FunctionOptions::elementwise(),
191            T::Combine(_) => FunctionOptions::elementwise(),
192            T::DatetimeFunction { .. } => {
193                FunctionOptions::elementwise().with_flags(|f| f | FunctionFlags::ALLOW_RENAME)
194            },
195        }
196    }
197}
198
199impl Display for TemporalFunction {
200    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
201        use TemporalFunction::*;
202        let s = match self {
203            Millennium => "millennium",
204            Century => "century",
205            Year => "year",
206            IsLeapYear => "is_leap_year",
207            IsoYear => "iso_year",
208            Quarter => "quarter",
209            Month => "month",
210            Week => "week",
211            WeekDay => "weekday",
212            Day => "day",
213            OrdinalDay => "ordinal_day",
214            Time => "time",
215            Date => "date",
216            Datetime => "datetime",
217            Duration(_) => "duration",
218            Hour => "hour",
219            Minute => "minute",
220            Second => "second",
221            Millisecond => "millisecond",
222            Microsecond => "microsecond",
223            Nanosecond => "nanosecond",
224            TotalDays => "total_days",
225            TotalHours => "total_hours",
226            TotalMinutes => "total_minutes",
227            TotalSeconds => "total_seconds",
228            TotalMilliseconds => "total_milliseconds",
229            TotalMicroseconds => "total_microseconds",
230            TotalNanoseconds => "total_nanoseconds",
231            ToString(_) => "to_string",
232            #[cfg(feature = "timezones")]
233            ConvertTimeZone(_) => "convert_time_zone",
234            CastTimeUnit(_) => "cast_time_unit",
235            WithTimeUnit(_) => "with_time_unit",
236            TimeStamp(tu) => return write!(f, "dt.timestamp({tu})"),
237            Truncate => "truncate",
238            #[cfg(feature = "offset_by")]
239            OffsetBy => "offset_by",
240            #[cfg(feature = "month_start")]
241            MonthStart => "month_start",
242            #[cfg(feature = "month_end")]
243            MonthEnd => "month_end",
244            #[cfg(feature = "timezones")]
245            BaseUtcOffset => "base_utc_offset",
246            #[cfg(feature = "timezones")]
247            DSTOffset => "dst_offset",
248            Round => "round",
249            Replace => "replace",
250            #[cfg(feature = "timezones")]
251            ReplaceTimeZone(_, _) => "replace_time_zone",
252            DatetimeFunction { .. } => return write!(f, "dt.datetime"),
253            Combine(_) => "combine",
254        };
255        write!(f, "dt.{s}")
256    }
257}
258
259pub(super) fn millennium(s: &Column) -> PolarsResult<Column> {
260    s.as_materialized_series()
261        .millennium()
262        .map(|ca| ca.into_column())
263}
264pub(super) fn century(s: &Column) -> PolarsResult<Column> {
265    s.as_materialized_series()
266        .century()
267        .map(|ca| ca.into_column())
268}
269pub(super) fn year(s: &Column) -> PolarsResult<Column> {
270    s.as_materialized_series().year().map(|ca| ca.into_column())
271}
272pub(super) fn is_leap_year(s: &Column) -> PolarsResult<Column> {
273    s.as_materialized_series()
274        .is_leap_year()
275        .map(|ca| ca.into_column())
276}
277pub(super) fn iso_year(s: &Column) -> PolarsResult<Column> {
278    s.as_materialized_series()
279        .iso_year()
280        .map(|ca| ca.into_column())
281}
282pub(super) fn month(s: &Column) -> PolarsResult<Column> {
283    s.as_materialized_series()
284        .month()
285        .map(|ca| ca.into_column())
286}
287pub(super) fn quarter(s: &Column) -> PolarsResult<Column> {
288    s.as_materialized_series()
289        .quarter()
290        .map(|ca| ca.into_column())
291}
292pub(super) fn week(s: &Column) -> PolarsResult<Column> {
293    s.as_materialized_series().week().map(|ca| ca.into_column())
294}
295pub(super) fn weekday(s: &Column) -> PolarsResult<Column> {
296    s.as_materialized_series()
297        .weekday()
298        .map(|ca| ca.into_column())
299}
300pub(super) fn day(s: &Column) -> PolarsResult<Column> {
301    s.as_materialized_series().day().map(|ca| ca.into_column())
302}
303pub(super) fn ordinal_day(s: &Column) -> PolarsResult<Column> {
304    s.as_materialized_series()
305        .ordinal_day()
306        .map(|ca| ca.into_column())
307}
308pub(super) fn time(s: &Column) -> PolarsResult<Column> {
309    match s.dtype() {
310        #[cfg(feature = "timezones")]
311        DataType::Datetime(_, Some(_)) => polars_ops::prelude::replace_time_zone(
312            s.datetime().unwrap(),
313            None,
314            &StringChunked::from_iter(std::iter::once("raise")),
315            NonExistent::Raise,
316        )?
317        .cast(&DataType::Time)
318        .map(Column::from),
319        DataType::Datetime(_, _) => s
320            .datetime()
321            .unwrap()
322            .cast(&DataType::Time)
323            .map(Column::from),
324        DataType::Time => Ok(s.clone()),
325        dtype => polars_bail!(ComputeError: "expected Datetime or Time, got {}", dtype),
326    }
327}
328pub(super) fn date(s: &Column) -> PolarsResult<Column> {
329    match s.dtype() {
330        #[cfg(feature = "timezones")]
331        DataType::Datetime(_, Some(_)) => {
332            let mut out = {
333                polars_ops::chunked_array::replace_time_zone(
334                    s.datetime().unwrap(),
335                    None,
336                    &StringChunked::from_iter(std::iter::once("raise")),
337                    NonExistent::Raise,
338                )?
339                .cast(&DataType::Date)?
340            };
341            // `replace_time_zone` may unset sorted flag. But, we're only taking the date
342            // part of the result, so we can safely preserve the sorted flag here. We may
343            // need to make an exception if a time zone introduces a change which involves
344            // "going back in time" and repeating a day, but we're not aware of that ever
345            // having happened.
346            out.set_sorted_flag(s.is_sorted_flag());
347            Ok(out.into())
348        },
349        DataType::Datetime(_, _) => s
350            .datetime()
351            .unwrap()
352            .cast(&DataType::Date)
353            .map(Column::from),
354        DataType::Date => Ok(s.clone()),
355        dtype => polars_bail!(ComputeError: "expected Datetime or Date, got {}", dtype),
356    }
357}
358pub(super) fn datetime(s: &Column) -> PolarsResult<Column> {
359    match s.dtype() {
360        #[cfg(feature = "timezones")]
361        DataType::Datetime(tu, Some(_)) => polars_ops::chunked_array::replace_time_zone(
362            s.datetime().unwrap(),
363            None,
364            &StringChunked::from_iter(std::iter::once("raise")),
365            NonExistent::Raise,
366        )?
367        .cast(&DataType::Datetime(*tu, None))
368        .map(|x| x.into()),
369        DataType::Datetime(tu, _) => s
370            .datetime()
371            .unwrap()
372            .cast(&DataType::Datetime(*tu, None))
373            .map(Column::from),
374        dtype => polars_bail!(ComputeError: "expected Datetime, got {}", dtype),
375    }
376}
377pub(super) fn hour(s: &Column) -> PolarsResult<Column> {
378    s.as_materialized_series().hour().map(|ca| ca.into_column())
379}
380pub(super) fn minute(s: &Column) -> PolarsResult<Column> {
381    s.as_materialized_series()
382        .minute()
383        .map(|ca| ca.into_column())
384}
385pub(super) fn second(s: &Column) -> PolarsResult<Column> {
386    s.as_materialized_series()
387        .second()
388        .map(|ca| ca.into_column())
389}
390pub(super) fn millisecond(s: &Column) -> PolarsResult<Column> {
391    s.as_materialized_series()
392        .nanosecond()
393        .map(|ca| (ca.wrapping_trunc_div_scalar(1_000_000)).into_column())
394}
395pub(super) fn microsecond(s: &Column) -> PolarsResult<Column> {
396    s.as_materialized_series()
397        .nanosecond()
398        .map(|ca| (ca.wrapping_trunc_div_scalar(1_000)).into_column())
399}
400pub(super) fn nanosecond(s: &Column) -> PolarsResult<Column> {
401    s.as_materialized_series()
402        .nanosecond()
403        .map(|ca| ca.into_column())
404}
405#[cfg(feature = "dtype-duration")]
406pub(super) fn total_days(s: &Column) -> PolarsResult<Column> {
407    s.as_materialized_series()
408        .duration()
409        .map(|ca| ca.days().into_column())
410}
411#[cfg(feature = "dtype-duration")]
412pub(super) fn total_hours(s: &Column) -> PolarsResult<Column> {
413    s.as_materialized_series()
414        .duration()
415        .map(|ca| ca.hours().into_column())
416}
417#[cfg(feature = "dtype-duration")]
418pub(super) fn total_minutes(s: &Column) -> PolarsResult<Column> {
419    s.as_materialized_series()
420        .duration()
421        .map(|ca| ca.minutes().into_column())
422}
423#[cfg(feature = "dtype-duration")]
424pub(super) fn total_seconds(s: &Column) -> PolarsResult<Column> {
425    s.as_materialized_series()
426        .duration()
427        .map(|ca| ca.seconds().into_column())
428}
429#[cfg(feature = "dtype-duration")]
430pub(super) fn total_milliseconds(s: &Column) -> PolarsResult<Column> {
431    s.as_materialized_series()
432        .duration()
433        .map(|ca| ca.milliseconds().into_column())
434}
435#[cfg(feature = "dtype-duration")]
436pub(super) fn total_microseconds(s: &Column) -> PolarsResult<Column> {
437    s.as_materialized_series()
438        .duration()
439        .map(|ca| ca.microseconds().into_column())
440}
441#[cfg(feature = "dtype-duration")]
442pub(super) fn total_nanoseconds(s: &Column) -> PolarsResult<Column> {
443    s.as_materialized_series()
444        .duration()
445        .map(|ca| ca.nanoseconds().into_column())
446}
447pub(super) fn timestamp(s: &Column, tu: TimeUnit) -> PolarsResult<Column> {
448    s.as_materialized_series()
449        .timestamp(tu)
450        .map(|ca| ca.into_column())
451}
452pub(super) fn to_string(s: &Column, format: &str) -> PolarsResult<Column> {
453    TemporalMethods::to_string(s.as_materialized_series(), format).map(Column::from)
454}
455
456#[cfg(feature = "timezones")]
457pub(super) fn convert_time_zone(s: &Column, time_zone: &TimeZone) -> PolarsResult<Column> {
458    match s.dtype() {
459        DataType::Datetime(_, _) => {
460            let mut ca = s.datetime()?.clone();
461            ca.set_time_zone(time_zone.clone())?;
462            Ok(ca.into_column())
463        },
464        dtype => polars_bail!(ComputeError: "expected Datetime, got {}", dtype),
465    }
466}
467pub(super) fn with_time_unit(s: &Column, tu: TimeUnit) -> PolarsResult<Column> {
468    match s.dtype() {
469        DataType::Datetime(_, _) => {
470            let mut ca = s.datetime()?.clone();
471            ca.set_time_unit(tu);
472            Ok(ca.into_column())
473        },
474        #[cfg(feature = "dtype-duration")]
475        DataType::Duration(_) => {
476            let mut ca = s.as_materialized_series().duration()?.clone();
477            ca.set_time_unit(tu);
478            Ok(ca.into_column())
479        },
480        dt => polars_bail!(ComputeError: "dtype `{}` has no time unit", dt),
481    }
482}
483pub(super) fn cast_time_unit(s: &Column, tu: TimeUnit) -> PolarsResult<Column> {
484    match s.dtype() {
485        DataType::Datetime(_, _) => {
486            let ca = s.datetime()?;
487            Ok(ca.cast_time_unit(tu).into_column())
488        },
489        #[cfg(feature = "dtype-duration")]
490        DataType::Duration(_) => {
491            let ca = s.as_materialized_series().duration()?;
492            Ok(ca.cast_time_unit(tu).into_column())
493        },
494        dt => polars_bail!(ComputeError: "dtype `{}` has no time unit", dt),
495    }
496}
497
498pub(super) fn truncate(s: &[Column]) -> PolarsResult<Column> {
499    let time_series = &s[0];
500    let every = s[1].str()?;
501
502    let mut out = match time_series.dtype() {
503        DataType::Datetime(_, tz) => match tz {
504            #[cfg(feature = "timezones")]
505            Some(tz) => time_series
506                .datetime()?
507                .truncate(tz.parse::<Tz>().ok().as_ref(), every)?
508                .into_column(),
509            _ => time_series.datetime()?.truncate(None, every)?.into_column(),
510        },
511        DataType::Date => time_series.date()?.truncate(None, every)?.into_column(),
512        dt => polars_bail!(opq = round, got = dt, expected = "date/datetime"),
513    };
514    out.set_sorted_flag(time_series.is_sorted_flag());
515    Ok(out)
516}
517
518#[cfg(feature = "offset_by")]
519pub(super) fn offset_by(s: &[Column]) -> PolarsResult<Column> {
520    impl_offset_by(s[0].as_materialized_series(), s[1].as_materialized_series()).map(Column::from)
521}
522
523#[cfg(feature = "month_start")]
524pub(super) fn month_start(s: &Column) -> PolarsResult<Column> {
525    Ok(match s.dtype() {
526        DataType::Datetime(_, tz) => match tz {
527            #[cfg(feature = "timezones")]
528            Some(tz) => s
529                .datetime()
530                .unwrap()
531                .month_start(tz.parse::<Tz>().ok().as_ref())?
532                .into_column(),
533            _ => s.datetime().unwrap().month_start(None)?.into_column(),
534        },
535        DataType::Date => s.date().unwrap().month_start(None)?.into_column(),
536        dt => polars_bail!(opq = month_start, got = dt, expected = "date/datetime"),
537    })
538}
539
540#[cfg(feature = "month_end")]
541pub(super) fn month_end(s: &Column) -> PolarsResult<Column> {
542    Ok(match s.dtype() {
543        DataType::Datetime(_, tz) => match tz {
544            #[cfg(feature = "timezones")]
545            Some(tz) => s
546                .datetime()
547                .unwrap()
548                .month_end(tz.parse::<Tz>().ok().as_ref())?
549                .into_column(),
550            _ => s.datetime().unwrap().month_end(None)?.into_column(),
551        },
552        DataType::Date => s.date().unwrap().month_end(None)?.into_column(),
553        dt => polars_bail!(opq = month_end, got = dt, expected = "date/datetime"),
554    })
555}
556
557#[cfg(feature = "timezones")]
558pub(super) fn base_utc_offset(s: &Column) -> PolarsResult<Column> {
559    match s.dtype() {
560        DataType::Datetime(time_unit, Some(tz)) => {
561            let tz = tz
562                .parse::<Tz>()
563                .expect("Time zone has already been validated");
564            Ok(base_utc_offset_fn(s.datetime().unwrap(), time_unit, &tz).into_column())
565        },
566        dt => polars_bail!(
567            opq = base_utc_offset,
568            got = dt,
569            expected = "time-zone-aware datetime"
570        ),
571    }
572}
573#[cfg(feature = "timezones")]
574pub(super) fn dst_offset(s: &Column) -> PolarsResult<Column> {
575    match s.dtype() {
576        DataType::Datetime(time_unit, Some(tz)) => {
577            let tz = tz
578                .parse::<Tz>()
579                .expect("Time zone has already been validated");
580            Ok(dst_offset_fn(s.datetime().unwrap(), time_unit, &tz).into_column())
581        },
582        dt => polars_bail!(
583            opq = dst_offset,
584            got = dt,
585            expected = "time-zone-aware datetime"
586        ),
587    }
588}
589
590pub(super) fn round(s: &[Column]) -> PolarsResult<Column> {
591    let time_series = &s[0];
592    let every = s[1].str()?;
593
594    Ok(match time_series.dtype() {
595        DataType::Datetime(_, tz) => match tz {
596            #[cfg(feature = "timezones")]
597            Some(tz) => time_series
598                .datetime()
599                .unwrap()
600                .round(every, tz.parse::<Tz>().ok().as_ref())?
601                .into_column(),
602            _ => time_series
603                .datetime()
604                .unwrap()
605                .round(every, None)?
606                .into_column(),
607        },
608        DataType::Date => time_series
609            .date()
610            .unwrap()
611            .round(every, None)?
612            .into_column(),
613        dt => polars_bail!(opq = round, got = dt, expected = "date/datetime"),
614    })
615}
616
617pub(super) fn replace(s: &[Column]) -> PolarsResult<Column> {
618    let time_series = &s[0];
619    let s_year = &s[1].strict_cast(&DataType::Int32)?;
620    let s_month = &s[2].strict_cast(&DataType::Int8)?;
621    let s_day = &s[3].strict_cast(&DataType::Int8)?;
622    let year = s_year.i32()?;
623    let month = s_month.i8()?;
624    let day = s_day.i8()?;
625
626    match time_series.dtype() {
627        DataType::Datetime(_, _) => {
628            let s_hour = &s[4].strict_cast(&DataType::Int8)?;
629            let s_minute = &s[5].strict_cast(&DataType::Int8)?;
630            let s_second = &s[6].strict_cast(&DataType::Int8)?;
631            let s_microsecond = &s[7].strict_cast(&DataType::Int32)?;
632            let hour = s_hour.i8()?;
633            let minute = s_minute.i8()?;
634            let second = s_second.i8()?;
635            let nanosecond = &(s_microsecond.i32()? * 1_000);
636            let s_ambiguous = &s[8].strict_cast(&DataType::String)?;
637            let ambiguous = s_ambiguous.str()?;
638
639            let out = replace_datetime(
640                time_series.datetime().unwrap(),
641                year,
642                month,
643                day,
644                hour,
645                minute,
646                second,
647                nanosecond,
648                ambiguous,
649            );
650            out.map(|s| s.into_column())
651        },
652        DataType::Date => {
653            let out = replace_date(time_series.date().unwrap(), year, month, day);
654            out.map(|s| s.into_column())
655        },
656        dt => polars_bail!(opq = round, got = dt, expected = "date/datetime"),
657    }
658}