use arrow::legacy::time_zone::Tz;
use chrono::{Datelike, NaiveDateTime, NaiveTime};
use polars_core::chunked_array::temporal::time_to_time64ns;
use polars_core::prelude::*;
use polars_core::series::IsSorted;
use polars_utils::format_pl_smallstr;
use crate::prelude::*;
pub fn in_nanoseconds_window(ndt: &NaiveDateTime) -> bool {
!(ndt.year() > 2554 || ndt.year() < 1386)
}
pub fn date_range(
name: PlSmallStr,
start: NaiveDateTime,
end: NaiveDateTime,
interval: Duration,
closed: ClosedWindow,
tu: TimeUnit,
tz: Option<&Tz>,
) -> PolarsResult<DatetimeChunked> {
let (start, end) = match tu {
TimeUnit::Nanoseconds => (
start.and_utc().timestamp_nanos_opt().unwrap(),
end.and_utc().timestamp_nanos_opt().unwrap(),
),
TimeUnit::Microseconds => (
start.and_utc().timestamp_micros(),
end.and_utc().timestamp_micros(),
),
TimeUnit::Milliseconds => (
start.and_utc().timestamp_millis(),
end.and_utc().timestamp_millis(),
),
};
datetime_range_impl(name, start, end, interval, closed, tu, tz)
}
#[doc(hidden)]
pub fn datetime_range_impl(
name: PlSmallStr,
start: i64,
end: i64,
interval: Duration,
closed: ClosedWindow,
tu: TimeUnit,
tz: Option<&Tz>,
) -> PolarsResult<DatetimeChunked> {
let out = Int64Chunked::new_vec(
name,
datetime_range_i64(start, end, interval, closed, tu, tz)?,
);
let mut out = match tz {
#[cfg(feature = "timezones")]
Some(tz) => out.into_datetime(tu, Some(format_pl_smallstr!("{}", tz))),
_ => out.into_datetime(tu, None),
};
out.set_sorted_flag(IsSorted::Ascending);
Ok(out)
}
pub fn time_range(
name: PlSmallStr,
start: NaiveTime,
end: NaiveTime,
interval: Duration,
closed: ClosedWindow,
) -> PolarsResult<TimeChunked> {
let start = time_to_time64ns(&start);
let end = time_to_time64ns(&end);
time_range_impl(name, start, end, interval, closed)
}
#[doc(hidden)]
pub fn time_range_impl(
name: PlSmallStr,
start: i64,
end: i64,
interval: Duration,
closed: ClosedWindow,
) -> PolarsResult<TimeChunked> {
let mut out = Int64Chunked::new_vec(
name,
datetime_range_i64(start, end, interval, closed, TimeUnit::Nanoseconds, None)?,
)
.into_time();
out.set_sorted_flag(IsSorted::Ascending);
Ok(out)
}
pub(crate) fn datetime_range_i64(
start: i64,
end: i64,
interval: Duration,
closed: ClosedWindow,
tu: TimeUnit,
tz: Option<&Tz>,
) -> PolarsResult<Vec<i64>> {
if start > end {
return Ok(Vec::new());
}
polars_ensure!(
!interval.negative && !interval.is_zero(),
ComputeError: "`interval` must be positive"
);
let duration = match tu {
TimeUnit::Nanoseconds => interval.duration_ns(),
TimeUnit::Microseconds => interval.duration_us(),
TimeUnit::Milliseconds => interval.duration_ms(),
};
let time_zone_opt_string: Option<String> = match tz {
#[cfg(feature = "timezones")]
Some(tz) => Some(tz.to_string()),
_ => None,
};
if interval.is_constant_duration(time_zone_opt_string.as_deref()) {
let step: usize = duration.try_into().map_err(
|_err| polars_err!(ComputeError: "Could not convert {:?} to usize", duration),
)?;
return match closed {
ClosedWindow::Both => Ok((start..=end).step_by(step).collect::<Vec<i64>>()),
ClosedWindow::None => Ok((start + duration..end).step_by(step).collect::<Vec<i64>>()),
ClosedWindow::Left => Ok((start..end).step_by(step).collect::<Vec<i64>>()),
ClosedWindow::Right => Ok((start + duration..=end).step_by(step).collect::<Vec<i64>>()),
};
}
let size = ((end - start) / duration + 1) as usize;
let offset_fn = match tu {
TimeUnit::Nanoseconds => Duration::add_ns,
TimeUnit::Microseconds => Duration::add_us,
TimeUnit::Milliseconds => Duration::add_ms,
};
let mut ts = Vec::with_capacity(size);
let mut i = match closed {
ClosedWindow::Both | ClosedWindow::Left => 0,
ClosedWindow::Right | ClosedWindow::None => 1,
};
let mut t = offset_fn(&(interval * i), start, tz)?;
i += 1;
match closed {
ClosedWindow::Both | ClosedWindow::Right => {
while t <= end {
ts.push(t);
t = offset_fn(&(interval * i), start, tz)?;
i += 1;
}
},
ClosedWindow::Left | ClosedWindow::None => {
while t < end {
ts.push(t);
t = offset_fn(&(interval * i), start, tz)?;
i += 1;
}
},
}
debug_assert!(size >= ts.len());
Ok(ts)
}