Path: blob/main/crates/polars-ops/src/chunked_array/datetime/replace_time_zone.rs
6939 views
use std::str::FromStr;12use arrow::legacy::kernels::convert_to_naive_local;3use arrow::temporal_conversions::{4timestamp_ms_to_datetime, timestamp_ns_to_datetime, timestamp_us_to_datetime,5};6use chrono::NaiveDateTime;7use chrono_tz::UTC;8use polars_core::chunked_array::ops::arity::try_binary_elementwise;9use polars_core::prelude::*;1011pub fn replace_time_zone(12datetime: &Logical<DatetimeType, Int64Type>,13time_zone: Option<&TimeZone>,14ambiguous: &StringChunked,15non_existent: NonExistent,16) -> PolarsResult<DatetimeChunked> {17let from_time_zone = datetime.time_zone().clone().unwrap_or(TimeZone::UTC);1819let from_tz = from_time_zone.to_chrono()?;2021let to_tz = if let Some(tz) = time_zone {22tz.to_chrono()?23} else {24chrono_tz::UTC25};2627if (from_tz == to_tz)28& ((from_tz == UTC) | ((ambiguous.len() == 1) & (ambiguous.get(0) == Some("raise"))))29{30let mut out = datetime31.phys32.clone()33.into_datetime(datetime.time_unit(), time_zone.cloned());34out.physical_mut()35.set_sorted_flag(datetime.physical().is_sorted_flag());36return Ok(out);37}38let timestamp_to_datetime: fn(i64) -> NaiveDateTime = match datetime.time_unit() {39TimeUnit::Milliseconds => timestamp_ms_to_datetime,40TimeUnit::Microseconds => timestamp_us_to_datetime,41TimeUnit::Nanoseconds => timestamp_ns_to_datetime,42};43let datetime_to_timestamp: fn(NaiveDateTime) -> i64 = match datetime.time_unit() {44TimeUnit::Milliseconds => datetime_to_timestamp_ms,45TimeUnit::Microseconds => datetime_to_timestamp_us,46TimeUnit::Nanoseconds => datetime_to_timestamp_ns,47};4849let out = if ambiguous.len() == 150&& ambiguous.get(0) != Some("null")51&& non_existent == NonExistent::Raise52{53impl_replace_time_zone_fast(54datetime,55ambiguous.get(0),56timestamp_to_datetime,57datetime_to_timestamp,58&from_tz,59&to_tz,60)61} else {62impl_replace_time_zone(63datetime,64ambiguous,65non_existent,66timestamp_to_datetime,67datetime_to_timestamp,68&from_tz,69&to_tz,70)71};7273let mut out = out?.into_datetime(datetime.time_unit(), time_zone.cloned());74if from_time_zone == TimeZone::UTC && ambiguous.len() == 1 && ambiguous.get(0) == Some("raise")75{76// In general, the sortedness flag can't be preserved.77// To be safe, we only do so in the simplest case when we know for sure that there is no "daylight savings weirdness" going on, i.e.:78// - `from_tz` is guaranteed to not observe daylight savings time;79// - user is just passing 'raise' to 'ambiguous'.80// Both conditions above need to be satisfied.81out.physical_mut()82.set_sorted_flag(datetime.physical().is_sorted_flag());83}84Ok(out)85}8687/// If `ambiguous` is length-1 and not equal to "null", we can take a slightly faster path.88pub fn impl_replace_time_zone_fast(89datetime: &Logical<DatetimeType, Int64Type>,90ambiguous: Option<&str>,91timestamp_to_datetime: fn(i64) -> NaiveDateTime,92datetime_to_timestamp: fn(NaiveDateTime) -> i64,93from_tz: &chrono_tz::Tz,94to_tz: &chrono_tz::Tz,95) -> PolarsResult<Int64Chunked> {96match ambiguous {97Some(ambiguous) => datetime.phys.try_apply_nonnull_values_generic(|timestamp| {98let ndt = timestamp_to_datetime(timestamp);99Ok(datetime_to_timestamp(100convert_to_naive_local(101from_tz,102to_tz,103ndt,104Ambiguous::from_str(ambiguous)?,105NonExistent::Raise,106)?107.expect("we didn't use Ambiguous::Null or NonExistent::Null"),108))109}),110_ => Ok(datetime.phys.apply(|_| None)),111}112}113114pub fn impl_replace_time_zone(115datetime: &Logical<DatetimeType, Int64Type>,116ambiguous: &StringChunked,117non_existent: NonExistent,118timestamp_to_datetime: fn(i64) -> NaiveDateTime,119datetime_to_timestamp: fn(NaiveDateTime) -> i64,120from_tz: &chrono_tz::Tz,121to_tz: &chrono_tz::Tz,122) -> PolarsResult<Int64Chunked> {123match ambiguous.len() {1241 => {125let iter = datetime.phys.downcast_iter().map(|arr| {126let element_iter = arr.iter().map(|timestamp_opt| match timestamp_opt {127Some(timestamp) => {128let ndt = timestamp_to_datetime(*timestamp);129let res = convert_to_naive_local(130from_tz,131to_tz,132ndt,133Ambiguous::from_str(ambiguous.get(0).unwrap())?,134non_existent,135)?;136Ok::<_, PolarsError>(res.map(datetime_to_timestamp))137},138None => Ok(None),139});140element_iter.try_collect_arr()141});142ChunkedArray::try_from_chunk_iter(datetime.phys.name().clone(), iter)143},144_ => try_binary_elementwise(145datetime.physical(),146ambiguous,147|timestamp_opt, ambiguous_opt| match (timestamp_opt, ambiguous_opt) {148(Some(timestamp), Some(ambiguous)) => {149let ndt = timestamp_to_datetime(timestamp);150Ok(convert_to_naive_local(151from_tz,152to_tz,153ndt,154Ambiguous::from_str(ambiguous)?,155non_existent,156)?157.map(datetime_to_timestamp))158},159_ => Ok(None),160},161),162}163}164165166