1use arrow::legacy::time_zone::Tz;
2use chrono::{Datelike, NaiveDateTime, NaiveTime};
3use polars_core::chunked_array::temporal::time_to_time64ns;
4use polars_core::prelude::*;
5use polars_core::series::IsSorted;
6
7use crate::prelude::*;
8
9pub fn in_nanoseconds_window(ndt: &NaiveDateTime) -> bool {
10 !(ndt.year() > 2554 || ndt.year() < 1386)
12}
13
14pub fn date_range(
16 name: PlSmallStr,
17 start: NaiveDateTime,
18 end: NaiveDateTime,
19 interval: Duration,
20 closed: ClosedWindow,
21 tu: TimeUnit,
22 tz: Option<&Tz>,
23) -> PolarsResult<DatetimeChunked> {
24 let (start, end) = match tu {
25 TimeUnit::Nanoseconds => (
26 start.and_utc().timestamp_nanos_opt().unwrap(),
27 end.and_utc().timestamp_nanos_opt().unwrap(),
28 ),
29 TimeUnit::Microseconds => (
30 start.and_utc().timestamp_micros(),
31 end.and_utc().timestamp_micros(),
32 ),
33 TimeUnit::Milliseconds => (
34 start.and_utc().timestamp_millis(),
35 end.and_utc().timestamp_millis(),
36 ),
37 };
38 datetime_range_impl(name, start, end, interval, closed, tu, tz)
39}
40
41#[doc(hidden)]
42pub fn datetime_range_impl(
43 name: PlSmallStr,
44 start: i64,
45 end: i64,
46 interval: Duration,
47 closed: ClosedWindow,
48 tu: TimeUnit,
49 tz: Option<&Tz>,
50) -> PolarsResult<DatetimeChunked> {
51 let out = Int64Chunked::new_vec(
52 name,
53 datetime_range_i64(start, end, interval, closed, tu, tz)?,
54 );
55 let mut out = match tz {
56 #[cfg(feature = "timezones")]
57 Some(tz) => out.into_datetime(tu, Some(TimeZone::from_chrono(tz))),
58 _ => out.into_datetime(tu, None),
59 };
60
61 out.set_sorted_flag(IsSorted::Ascending);
62 Ok(out)
63}
64
65pub fn time_range(
67 name: PlSmallStr,
68 start: NaiveTime,
69 end: NaiveTime,
70 interval: Duration,
71 closed: ClosedWindow,
72) -> PolarsResult<TimeChunked> {
73 let start = time_to_time64ns(&start);
74 let end = time_to_time64ns(&end);
75 time_range_impl(name, start, end, interval, closed)
76}
77
78#[doc(hidden)]
79pub fn time_range_impl(
80 name: PlSmallStr,
81 start: i64,
82 end: i64,
83 interval: Duration,
84 closed: ClosedWindow,
85) -> PolarsResult<TimeChunked> {
86 let mut out = Int64Chunked::new_vec(
87 name,
88 datetime_range_i64(start, end, interval, closed, TimeUnit::Nanoseconds, None)?,
89 )
90 .into_time();
91
92 out.set_sorted_flag(IsSorted::Ascending);
93 Ok(out)
94}
95
96pub(crate) fn datetime_range_i64(
98 start: i64,
99 end: i64,
100 interval: Duration,
101 closed: ClosedWindow,
102 time_unit: TimeUnit,
103 time_zone: Option<&Tz>,
104) -> PolarsResult<Vec<i64>> {
105 if start > end {
106 return Ok(Vec::new());
107 }
108 polars_ensure!(
109 !interval.negative && !interval.is_zero(),
110 ComputeError: "`interval` must be positive"
111 );
112
113 let duration = match time_unit {
114 TimeUnit::Nanoseconds => interval.duration_ns(),
115 TimeUnit::Microseconds => interval.duration_us(),
116 TimeUnit::Milliseconds => interval.duration_ms(),
117 };
118 let time_zone_opt: Option<TimeZone> = match time_zone {
119 #[cfg(feature = "timezones")]
120 Some(tz) => Some(TimeZone::from_chrono(tz)),
121 _ => None,
122 };
123
124 if interval.is_constant_duration(time_zone_opt.as_ref()) {
125 let step: usize = duration.try_into().map_err(
127 |_err| polars_err!(ComputeError: "Could not convert {:?} to usize", duration),
128 )?;
129 polars_ensure!(
130 step != 0,
131 InvalidOperation: "interval {} is too small for time unit {} and got rounded down to zero",
132 interval,
133 time_unit,
134 );
135 return match closed {
136 ClosedWindow::Both => Ok((start..=end).step_by(step).collect::<Vec<i64>>()),
137 ClosedWindow::None => Ok((start + duration..end).step_by(step).collect::<Vec<i64>>()),
138 ClosedWindow::Left => Ok((start..end).step_by(step).collect::<Vec<i64>>()),
139 ClosedWindow::Right => Ok((start + duration..=end).step_by(step).collect::<Vec<i64>>()),
140 };
141 }
142
143 let size = ((end - start) / duration + 1) as usize;
144 let offset_fn = match time_unit {
145 TimeUnit::Nanoseconds => Duration::add_ns,
146 TimeUnit::Microseconds => Duration::add_us,
147 TimeUnit::Milliseconds => Duration::add_ms,
148 };
149 let mut ts = Vec::with_capacity(size);
150 let mut i = match closed {
151 ClosedWindow::Both | ClosedWindow::Left => 0,
152 ClosedWindow::Right | ClosedWindow::None => 1,
153 };
154 let mut t = offset_fn(&(interval * i), start, time_zone)?;
155 i += 1;
156 match closed {
157 ClosedWindow::Both | ClosedWindow::Right => {
158 while t <= end {
159 ts.push(t);
160 t = offset_fn(&(interval * i), start, time_zone)?;
161 i += 1;
162 }
163 },
164 ClosedWindow::Left | ClosedWindow::None => {
165 while t < end {
166 ts.push(t);
167 t = offset_fn(&(interval * i), start, time_zone)?;
168 i += 1;
169 }
170 },
171 }
172 debug_assert!(size >= ts.len());
173 Ok(ts)
174}