1use chrono::NaiveDateTime;
2#[cfg(feature = "timezones")]
3use chrono::TimeZone;
4use now::DateTimeNow;
5use polars_arrow::legacy::time_zone::Tz;
6use polars_arrow::temporal_conversions::*;
7use polars_core::prelude::*;
8use polars_defs::time::duration::Duration;
9use polars_defs::time::group_by::{ClosedWindow, StartBy};
10
11use crate::prelude::*;
12
13#[allow(clippy::too_many_arguments)]
27pub(crate) fn ensure_t_in_or_in_front_of_window(
28 mut every: Duration,
29 t: i64,
30 offset_fn: fn(&Duration, i64, Option<&Tz>) -> PolarsResult<i64>,
31 nte_duration_fn: fn(&Duration) -> i64,
32 period: Duration,
33 mut start: i64,
34 closed_window: ClosedWindow,
35 tz: Option<&Tz>,
36) -> PolarsResult<Bounds> {
37 every.negative = !every.negative;
38 let mut stop = offset_fn(&period, start, tz)?;
39
40 while Bounds::new(start, stop).is_past(t, closed_window) {
41 let mut gap = start - t;
42 if matches!(closed_window, ClosedWindow::Right | ClosedWindow::None) {
43 gap += 1;
44 }
45 debug_assert!(gap >= 1);
46
47 let stride = (gap + nte_duration_fn(&every) - 1) / nte_duration_fn(&every);
49 debug_assert!(stride >= 1);
50 let stride = std::cmp::max(stride, 1);
51
52 start = offset_fn(&(every * stride), start, tz)?;
53 stop = offset_fn(&period, start, tz)?;
54 }
55 Ok(Bounds::new_checked(start, stop))
56}
57
58#[derive(Copy, Clone)]
60pub struct Window {
61 pub(crate) every: Duration,
65 pub(crate) period: Duration,
66 pub offset: Duration,
67}
68
69impl Window {
70 pub fn new(every: Duration, period: Duration, offset: Duration) -> Self {
71 debug_assert!(!every.negative);
72 Self {
73 every,
74 period,
75 offset,
76 }
77 }
78
79 pub fn truncate_ns(&self, t: i64, tz: Option<&Tz>) -> PolarsResult<i64> {
81 self.every.truncate_ns(t, tz)
82 }
83
84 pub fn truncate_us(&self, t: i64, tz: Option<&Tz>) -> PolarsResult<i64> {
86 self.every.truncate_us(t, tz)
87 }
88
89 pub fn truncate_ms(&self, t: i64, tz: Option<&Tz>) -> PolarsResult<i64> {
91 self.every.truncate_ms(t, tz)
92 }
93
94 pub fn round_ns(&self, t: i64, tz: Option<&Tz>) -> PolarsResult<i64> {
96 let t = t + self.every.duration_ns() / 2_i64;
97 self.truncate_ns(t, tz)
98 }
99
100 pub fn round_us(&self, t: i64, tz: Option<&Tz>) -> PolarsResult<i64> {
102 let t = t + self.every.duration_ns()
103 / (2 * timeunit_scale(ArrowTimeUnit::Nanosecond, ArrowTimeUnit::Microsecond) as i64);
104 self.truncate_us(t, tz)
105 }
106
107 pub fn round_ms(&self, t: i64, tz: Option<&Tz>) -> PolarsResult<i64> {
109 let t = t + self.every.duration_ns()
110 / (2 * timeunit_scale(ArrowTimeUnit::Nanosecond, ArrowTimeUnit::Millisecond) as i64);
111 self.truncate_ms(t, tz)
112 }
113
114 pub fn get_earliest_bounds_ns(
118 &self,
119 t: i64,
120 closed_window: ClosedWindow,
121 tz: Option<&Tz>,
122 ) -> PolarsResult<Bounds> {
123 let start = self.truncate_ns(t, tz)?;
124 let start = self.offset.add_ns(start, tz)?;
125 ensure_t_in_or_in_front_of_window(
126 self.every,
127 t,
128 Duration::add_ns,
129 Duration::nte_duration_ns,
130 self.period,
131 start,
132 closed_window,
133 tz,
134 )
135 }
136
137 pub fn get_earliest_bounds_us(
138 &self,
139 t: i64,
140 closed_window: ClosedWindow,
141 tz: Option<&Tz>,
142 ) -> PolarsResult<Bounds> {
143 let start = self.truncate_us(t, tz)?;
144 let start = self.offset.add_us(start, tz)?;
145 ensure_t_in_or_in_front_of_window(
146 self.every,
147 t,
148 Duration::add_us,
149 Duration::nte_duration_us,
150 self.period,
151 start,
152 closed_window,
153 tz,
154 )
155 }
156
157 pub fn get_earliest_bounds_ms(
158 &self,
159 t: i64,
160 closed_window: ClosedWindow,
161 tz: Option<&Tz>,
162 ) -> PolarsResult<Bounds> {
163 let start = self.truncate_ms(t, tz)?;
164 let start = self.offset.add_ms(start, tz)?;
165 ensure_t_in_or_in_front_of_window(
166 self.every,
167 t,
168 Duration::add_ms,
169 Duration::nte_duration_ms,
170 self.period,
171 start,
172 closed_window,
173 tz,
174 )
175 }
176
177 pub(crate) fn estimate_overlapping_bounds_ns(&self, boundary: Bounds) -> usize {
178 (boundary.duration() / self.every.duration_ns()
179 + self.period.duration_ns() / self.every.duration_ns()) as usize
180 }
181
182 pub(crate) fn estimate_overlapping_bounds_us(&self, boundary: Bounds) -> usize {
183 (boundary.duration() / self.every.duration_us()
184 + self.period.duration_us() / self.every.duration_us()) as usize
185 }
186
187 pub(crate) fn estimate_overlapping_bounds_ms(&self, boundary: Bounds) -> usize {
188 (boundary.duration() / self.every.duration_ms()
189 + self.period.duration_ms() / self.every.duration_ms()) as usize
190 }
191
192 pub fn get_overlapping_bounds_iter<'a>(
193 &'a self,
194 boundary: Bounds,
195 closed_window: ClosedWindow,
196 tu: TimeUnit,
197 tz: Option<&'a Tz>,
198 start_by: StartBy,
199 ) -> PolarsResult<BoundsIter<'a>> {
200 BoundsIter::new(*self, closed_window, boundary, tu, tz, start_by)
201 }
202}
203
204pub struct BoundsIter<'a> {
205 window: Window,
206 boundary: Bounds,
208 bi: Bounds,
210 tu: TimeUnit,
211 tz: Option<&'a Tz>,
212}
213impl<'a> BoundsIter<'a> {
214 fn new(
215 window: Window,
216 closed_window: ClosedWindow,
217 boundary: Bounds,
218 tu: TimeUnit,
219 tz: Option<&'a Tz>,
220 start_by: StartBy,
221 ) -> PolarsResult<Self> {
222 let bi = match start_by {
223 StartBy::DataPoint => {
224 let mut boundary = boundary;
225 let offset_fn = match tu {
226 TimeUnit::Nanoseconds => Duration::add_ns,
227 TimeUnit::Microseconds => Duration::add_us,
228 TimeUnit::Milliseconds => Duration::add_ms,
229 };
230 boundary.stop = offset_fn(&window.period, boundary.start, tz)?;
231 boundary
232 },
233 StartBy::WindowBound => match tu {
234 TimeUnit::Nanoseconds => {
235 window.get_earliest_bounds_ns(boundary.start, closed_window, tz)?
236 },
237 TimeUnit::Microseconds => {
238 window.get_earliest_bounds_us(boundary.start, closed_window, tz)?
239 },
240 TimeUnit::Milliseconds => {
241 window.get_earliest_bounds_ms(boundary.start, closed_window, tz)?
242 },
243 },
244 _ => {
245 {
246 #[allow(clippy::type_complexity)]
247 let (from, to, offset_fn, nte_duration_fn): (
248 fn(i64) -> NaiveDateTime,
249 fn(NaiveDateTime) -> i64,
250 fn(&Duration, i64, Option<&Tz>) -> PolarsResult<i64>,
251 fn(&Duration) -> i64,
252 ) = match tu {
253 TimeUnit::Nanoseconds => (
254 timestamp_ns_to_datetime,
255 datetime_to_timestamp_ns,
256 Duration::add_ns,
257 Duration::nte_duration_ns,
258 ),
259 TimeUnit::Microseconds => (
260 timestamp_us_to_datetime,
261 datetime_to_timestamp_us,
262 Duration::add_us,
263 Duration::nte_duration_us,
264 ),
265 TimeUnit::Milliseconds => (
266 timestamp_ms_to_datetime,
267 datetime_to_timestamp_ms,
268 Duration::add_ms,
269 Duration::nte_duration_ms,
270 ),
271 };
272 let dt = from(boundary.start);
274 match tz {
275 #[cfg(feature = "timezones")]
276 Some(tz) => {
277 let dt = tz.from_utc_datetime(&dt);
278 let dt = dt.beginning_of_week();
279 let dt = dt.naive_utc();
280 let start = to(dt);
281 let start = offset_fn(
283 &Duration::parse(&format!("{}d", start_by.weekday().unwrap())),
284 start,
285 Some(tz),
286 )?;
287 let start = offset_fn(&window.offset, start, Some(tz))?;
289 ensure_t_in_or_in_front_of_window(
292 window.every,
293 boundary.start,
294 offset_fn,
295 nte_duration_fn,
296 window.period,
297 start,
298 closed_window,
299 Some(tz),
300 )?
301 },
302 _ => {
303 let tz = chrono::Utc;
304 let dt = dt.and_local_timezone(tz).unwrap();
305 let dt = dt.beginning_of_week();
306 let dt = dt.naive_utc();
307 let start = to(dt);
308 let start = offset_fn(
310 &Duration::parse(&format!("{}d", start_by.weekday().unwrap())),
311 start,
312 None,
313 )
314 .unwrap();
315 let start = offset_fn(&window.offset, start, None).unwrap();
317 ensure_t_in_or_in_front_of_window(
320 window.every,
321 boundary.start,
322 offset_fn,
323 nte_duration_fn,
324 window.period,
325 start,
326 closed_window,
327 None,
328 )?
329 },
330 }
331 }
332 },
333 };
334 Ok(Self {
335 window,
336 boundary,
337 bi,
338 tu,
339 tz,
340 })
341 }
342}
343
344impl Iterator for BoundsIter<'_> {
345 type Item = Bounds;
346
347 fn next(&mut self) -> Option<Self::Item> {
348 if self.bi.start < self.boundary.stop {
349 let out = self.bi;
350 match self.tu {
351 TimeUnit::Nanoseconds => {
354 self.bi.start = self.window.every.add_ns(self.bi.start, self.tz).unwrap();
355 self.bi.stop = self.window.period.add_ns(self.bi.start, self.tz).unwrap();
356 },
357 TimeUnit::Microseconds => {
358 self.bi.start = self.window.every.add_us(self.bi.start, self.tz).unwrap();
359 self.bi.stop = self.window.period.add_us(self.bi.start, self.tz).unwrap();
360 },
361 TimeUnit::Milliseconds => {
362 self.bi.start = self.window.every.add_ms(self.bi.start, self.tz).unwrap();
363 self.bi.stop = self.window.period.add_ms(self.bi.start, self.tz).unwrap();
364 },
365 }
366 Some(out)
367 } else {
368 None
369 }
370 }
371
372 fn nth(&mut self, n: usize) -> Option<Self::Item> {
373 let n: i64 = n.try_into().unwrap();
374 if self.bi.start < self.boundary.stop {
375 match self.tu {
376 TimeUnit::Nanoseconds => {
377 self.bi.start = (self.window.every * n)
378 .add_ns(self.bi.start, self.tz)
379 .unwrap();
380 self.bi.stop = (self.window.period).add_ns(self.bi.start, self.tz).unwrap();
381 },
382 TimeUnit::Microseconds => {
383 self.bi.start = (self.window.every * n)
384 .add_us(self.bi.start, self.tz)
385 .unwrap();
386 self.bi.stop = (self.window.period).add_us(self.bi.start, self.tz).unwrap();
387 },
388 TimeUnit::Milliseconds => {
389 self.bi.start = (self.window.every * n)
390 .add_ms(self.bi.start, self.tz)
391 .unwrap();
392 self.bi.stop = (self.window.period).add_ms(self.bi.start, self.tz).unwrap();
393 },
394 }
395 self.next()
396 } else {
397 None
398 }
399 }
400}
401
402impl<'a> BoundsIter<'a> {
403 pub fn get_stride(&self, target: i64) -> usize {
408 let mut stride = 0;
409 if self.bi.start < self.boundary.stop && target > self.bi.start {
410 let gap = target - self.bi.start;
411 match self.tu {
412 TimeUnit::Nanoseconds => {
413 if gap
414 > self.window.every.nte_duration_ns() + self.window.period.nte_duration_ns()
415 {
416 stride = ((gap - self.window.period.nte_duration_ns()) as usize)
417 / (self.window.every.nte_duration_ns() as usize);
418 }
419 },
420 TimeUnit::Microseconds => {
421 if gap
422 > self.window.every.nte_duration_us() + self.window.period.nte_duration_us()
423 {
424 stride = ((gap - self.window.period.nte_duration_us()) as usize)
425 / (self.window.every.nte_duration_us() as usize);
426 }
427 },
428 TimeUnit::Milliseconds => {
429 if gap
430 > self.window.every.nte_duration_ms() + self.window.period.nte_duration_ms()
431 {
432 stride = ((gap - self.window.period.nte_duration_ms()) as usize)
433 / (self.window.every.nte_duration_ms() as usize);
434 }
435 },
436 }
437 }
438 stride
439 }
440}