Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pola-rs
GitHub Repository: pola-rs/polars
Path: blob/main/crates/polars-time/src/truncate.rs
6939 views
1
use arrow::legacy::time_zone::Tz;
2
use arrow::temporal_conversions::MILLISECONDS_IN_DAY;
3
use polars_core::prelude::arity::broadcast_try_binary_elementwise;
4
use polars_core::prelude::*;
5
use polars_utils::cache::LruCache;
6
7
use crate::prelude::*;
8
9
pub trait PolarsTruncate {
10
fn truncate(&self, tz: Option<&Tz>, every: &StringChunked) -> PolarsResult<Self>
11
where
12
Self: Sized;
13
}
14
15
#[inline(always)]
16
pub(crate) fn fast_truncate(t: i64, every: i64) -> i64 {
17
let remainder = t % every;
18
t - (remainder + every * (remainder < 0) as i64)
19
}
20
21
impl PolarsTruncate for DatetimeChunked {
22
fn truncate(&self, tz: Option<&Tz>, every: &StringChunked) -> PolarsResult<Self> {
23
polars_ensure!(
24
self.len() == every.len() || self.len() == 1 || every.len() == 1,
25
length_mismatch = "dt.truncate",
26
self.len(),
27
every.len()
28
);
29
30
let time_zone = self.time_zone();
31
let offset = Duration::new(0);
32
33
// Let's check if we can use a fastpath...
34
if every.len() == 1 {
35
if let Some(every) = every.get(0) {
36
let every_parsed = Duration::parse(every);
37
if every_parsed.negative {
38
polars_bail!(ComputeError: "cannot truncate a Datetime to a negative duration")
39
}
40
if (time_zone.is_none() || time_zone.as_ref() == Some(&TimeZone::UTC))
41
&& (every_parsed.months() == 0 && every_parsed.weeks() == 0)
42
{
43
// ... yes we can! Weeks, months, and time zones require extra logic.
44
// But in this simple case, it's just simple integer arithmetic.
45
let every = match self.time_unit() {
46
TimeUnit::Milliseconds => every_parsed.duration_ms(),
47
TimeUnit::Microseconds => every_parsed.duration_us(),
48
TimeUnit::Nanoseconds => every_parsed.duration_ns(),
49
};
50
return Ok(self
51
.physical()
52
.apply_values(|t| fast_truncate(t, every))
53
.into_datetime(self.time_unit(), time_zone.clone()));
54
} else {
55
let w = Window::new(every_parsed, every_parsed, offset);
56
let out = match self.time_unit() {
57
TimeUnit::Milliseconds => self
58
.physical()
59
.try_apply_nonnull_values_generic(|t| w.truncate_ms(t, tz)),
60
TimeUnit::Microseconds => self
61
.physical()
62
.try_apply_nonnull_values_generic(|t| w.truncate_us(t, tz)),
63
TimeUnit::Nanoseconds => self
64
.physical()
65
.try_apply_nonnull_values_generic(|t| w.truncate_ns(t, tz)),
66
};
67
return Ok(out?.into_datetime(self.time_unit(), self.time_zone().clone()));
68
}
69
} else {
70
return Ok(Int64Chunked::full_null(self.name().clone(), self.len())
71
.into_datetime(self.time_unit(), self.time_zone().clone()));
72
}
73
}
74
75
// A sqrt(n) cache is not too small, not too large.
76
let mut duration_cache = LruCache::with_capacity((every.len() as f64).sqrt() as usize);
77
78
let func = match self.time_unit() {
79
TimeUnit::Nanoseconds => Window::truncate_ns,
80
TimeUnit::Microseconds => Window::truncate_us,
81
TimeUnit::Milliseconds => Window::truncate_ms,
82
};
83
84
let out = broadcast_try_binary_elementwise(
85
self.physical(),
86
every,
87
|opt_timestamp, opt_every| match (opt_timestamp, opt_every) {
88
(Some(timestamp), Some(every)) => {
89
let every = *duration_cache.get_or_insert_with(every, Duration::parse);
90
91
if every.negative {
92
polars_bail!(ComputeError: "cannot truncate a Datetime to a negative duration")
93
}
94
95
let w = Window::new(every, every, offset);
96
func(&w, timestamp, tz).map(Some)
97
},
98
_ => Ok(None),
99
},
100
);
101
Ok(out?.into_datetime(self.time_unit(), self.time_zone().clone()))
102
}
103
}
104
105
impl PolarsTruncate for DateChunked {
106
fn truncate(&self, _tz: Option<&Tz>, every: &StringChunked) -> PolarsResult<Self> {
107
polars_ensure!(
108
self.len() == every.len() || self.len() == 1 || every.len() == 1,
109
length_mismatch = "dt.truncate",
110
self.len(),
111
every.len()
112
);
113
114
let offset = Duration::new(0);
115
let out = match every.len() {
116
1 => {
117
if let Some(every) = every.get(0) {
118
let every = Duration::parse(every);
119
if every.negative {
120
polars_bail!(ComputeError: "cannot truncate a Date to a negative duration")
121
}
122
let w = Window::new(every, every, offset);
123
self.physical().try_apply_nonnull_values_generic(|t| {
124
Ok((w.truncate_ms(MILLISECONDS_IN_DAY * t as i64, None)?
125
/ MILLISECONDS_IN_DAY) as i32)
126
})
127
} else {
128
Ok(Int32Chunked::full_null(self.name().clone(), self.len()))
129
}
130
},
131
_ => broadcast_try_binary_elementwise(self.physical(), every, |opt_t, opt_every| {
132
// A sqrt(n) cache is not too small, not too large.
133
let mut duration_cache =
134
LruCache::with_capacity((every.len() as f64).sqrt() as usize);
135
match (opt_t, opt_every) {
136
(Some(t), Some(every)) => {
137
let every = *duration_cache.get_or_insert_with(every, Duration::parse);
138
139
if every.negative {
140
polars_bail!(ComputeError: "cannot truncate a Date to a negative duration")
141
}
142
143
let w = Window::new(every, every, offset);
144
Ok(Some(
145
(w.truncate_ms(MILLISECONDS_IN_DAY * t as i64, None)?
146
/ MILLISECONDS_IN_DAY) as i32,
147
))
148
},
149
_ => Ok(None),
150
}
151
}),
152
};
153
Ok(out?.into_date())
154
}
155
}
156
157