Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pola-rs
GitHub Repository: pola-rs/polars
Path: blob/main/crates/polars-time/src/month_end.rs
8430 views
1
use arrow::legacy::time_zone::Tz;
2
use chrono::NaiveDateTime;
3
use polars_core::prelude::*;
4
use polars_core::utils::arrow::temporal_conversions::{
5
MILLISECONDS, SECONDS_IN_DAY, timestamp_ms_to_datetime, timestamp_ns_to_datetime,
6
timestamp_us_to_datetime,
7
};
8
9
use crate::month_start::roll_backward;
10
#[cfg(feature = "timezones")]
11
use crate::utils::{try_localize_datetime, unlocalize_datetime};
12
use crate::windows::duration::Duration;
13
14
// roll forward to the last day of the month
15
fn roll_forward(
16
t: i64,
17
time_zone: Option<&Tz>,
18
timestamp_to_datetime: fn(i64) -> NaiveDateTime,
19
datetime_to_timestamp: fn(NaiveDateTime) -> i64,
20
offset_fn: fn(&Duration, i64, Option<&Tz>) -> PolarsResult<i64>,
21
) -> PolarsResult<i64> {
22
// Use Ambiguous::Latest to roll back to the start of the month. It doesn't matter
23
// if that timestamp lands on an ambiguous time as we then add 1 month anyway, we
24
// could just as well use Ambiguous::Earliest.
25
let naive_t = match time_zone {
26
#[cfg(feature = "timezones")]
27
Some(tz) => datetime_to_timestamp(unlocalize_datetime(timestamp_to_datetime(t), tz)),
28
_ => t,
29
};
30
let naive_month_start_t =
31
roll_backward(naive_t, None, timestamp_to_datetime, datetime_to_timestamp)?;
32
let naive_result = offset_fn(
33
&Duration::parse("-1d"),
34
offset_fn(&Duration::parse("1mo"), naive_month_start_t, None)?,
35
None,
36
)?;
37
let result = match time_zone {
38
#[cfg(feature = "timezones")]
39
Some(tz) => datetime_to_timestamp(
40
try_localize_datetime(
41
timestamp_to_datetime(naive_result),
42
tz,
43
Ambiguous::Raise,
44
NonExistent::Raise,
45
)?
46
.expect("we didn't use Ambiguous::Null or NonExistent::Null"),
47
),
48
_ => naive_result,
49
};
50
Ok(result)
51
}
52
53
pub trait PolarsMonthEnd {
54
fn month_end(&self, time_zone: Option<&Tz>) -> PolarsResult<Self>
55
where
56
Self: Sized;
57
}
58
59
impl PolarsMonthEnd for DatetimeChunked {
60
fn month_end(&self, time_zone: Option<&Tz>) -> PolarsResult<Self> {
61
let timestamp_to_datetime: fn(i64) -> NaiveDateTime;
62
let datetime_to_timestamp: fn(NaiveDateTime) -> i64;
63
let offset_fn: fn(&Duration, i64, Option<&Tz>) -> PolarsResult<i64>;
64
match self.time_unit() {
65
TimeUnit::Nanoseconds => {
66
timestamp_to_datetime = timestamp_ns_to_datetime;
67
datetime_to_timestamp = datetime_to_timestamp_ns;
68
offset_fn = Duration::add_ns;
69
},
70
TimeUnit::Microseconds => {
71
timestamp_to_datetime = timestamp_us_to_datetime;
72
datetime_to_timestamp = datetime_to_timestamp_us;
73
offset_fn = Duration::add_us;
74
},
75
TimeUnit::Milliseconds => {
76
timestamp_to_datetime = timestamp_ms_to_datetime;
77
datetime_to_timestamp = datetime_to_timestamp_ms;
78
offset_fn = Duration::add_ms;
79
},
80
};
81
Ok(self
82
.phys
83
.try_apply_nonnull_values_generic(|t| {
84
roll_forward(
85
t,
86
time_zone,
87
timestamp_to_datetime,
88
datetime_to_timestamp,
89
offset_fn,
90
)
91
})?
92
.into_datetime(self.time_unit(), self.time_zone().clone()))
93
}
94
}
95
96
impl PolarsMonthEnd for DateChunked {
97
fn month_end(&self, _time_zone: Option<&Tz>) -> PolarsResult<Self> {
98
const MSECS_IN_DAY: i64 = MILLISECONDS * SECONDS_IN_DAY;
99
let ret = self.phys.try_apply_nonnull_values_generic(|t| {
100
let fwd = roll_forward(
101
MSECS_IN_DAY * t as i64,
102
None,
103
timestamp_ms_to_datetime,
104
datetime_to_timestamp_ms,
105
Duration::add_ms,
106
)?;
107
PolarsResult::Ok((fwd / MSECS_IN_DAY) as i32)
108
})?;
109
Ok(ret.into_date())
110
}
111
}
112
113