polars_plan/dsl/function_expr/
datetime.rs

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