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