polars_arrow/
temporal_conversions.rs1use chrono::format::{Parsed, StrftimeItems, parse};
4use chrono::{DateTime, Duration, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, TimeDelta};
5use polars_error::{PolarsResult, polars_err};
6
7use crate::datatypes::TimeUnit;
8
9pub const SECONDS_IN_DAY: i64 = 86_400;
11pub const MILLISECONDS: i64 = 1_000;
13pub const MICROSECONDS: i64 = 1_000_000;
15pub const NANOSECONDS: i64 = 1_000_000_000;
17pub const MILLISECONDS_IN_DAY: i64 = SECONDS_IN_DAY * MILLISECONDS;
19pub const MICROSECONDS_IN_DAY: i64 = SECONDS_IN_DAY * MICROSECONDS;
21pub const NANOSECONDS_IN_DAY: i64 = SECONDS_IN_DAY * NANOSECONDS;
23pub const EPOCH_DAYS_FROM_CE: i32 = 719_163;
25
26#[inline]
27fn unix_epoch() -> NaiveDateTime {
28 DateTime::UNIX_EPOCH.naive_utc()
29}
30
31#[inline]
33pub fn date32_to_datetime(v: i32) -> NaiveDateTime {
34 date32_to_datetime_opt(v).expect("invalid or out-of-range datetime")
35}
36
37#[inline]
39pub fn date32_to_datetime_opt(v: i32) -> Option<NaiveDateTime> {
40 let delta = TimeDelta::try_days(v.into())?;
41 unix_epoch().checked_add_signed(delta)
42}
43
44#[inline]
46pub fn date32_to_date(days: i32) -> NaiveDate {
47 date32_to_date_opt(days).expect("out-of-range date")
48}
49
50#[inline]
52pub fn date32_to_date_opt(days: i32) -> Option<NaiveDate> {
53 NaiveDate::from_num_days_from_ce_opt(EPOCH_DAYS_FROM_CE + days)
54}
55
56#[inline]
58pub fn date64_to_datetime(v: i64) -> NaiveDateTime {
59 TimeDelta::try_milliseconds(v)
60 .and_then(|delta| unix_epoch().checked_add_signed(delta))
61 .expect("invalid or out-of-range datetime")
62}
63
64#[inline]
66pub fn date64_to_date(milliseconds: i64) -> NaiveDate {
67 date64_to_datetime(milliseconds).date()
68}
69
70#[inline]
72pub fn time32s_to_time(v: i32) -> NaiveTime {
73 NaiveTime::from_num_seconds_from_midnight_opt(v as u32, 0).expect("invalid time")
74}
75
76#[inline]
78pub fn duration_s_to_duration(v: i64) -> Duration {
79 Duration::try_seconds(v).expect("out-of-range duration")
80}
81
82#[inline]
84pub fn duration_ms_to_duration(v: i64) -> Duration {
85 Duration::try_milliseconds(v).expect("out-of-range in duration conversion")
86}
87
88#[inline]
90pub fn duration_us_to_duration(v: i64) -> Duration {
91 Duration::microseconds(v)
92}
93
94#[inline]
96pub fn duration_ns_to_duration(v: i64) -> Duration {
97 Duration::nanoseconds(v)
98}
99
100#[inline]
102pub fn time32ms_to_time(v: i32) -> NaiveTime {
103 let v = v as i64;
104 let seconds = v / MILLISECONDS;
105
106 let milli_to_nano = 1_000_000;
107 let nano = (v - seconds * MILLISECONDS) * milli_to_nano;
108 NaiveTime::from_num_seconds_from_midnight_opt(seconds as u32, nano as u32)
109 .expect("invalid time")
110}
111
112#[inline]
114pub fn time64us_to_time(v: i64) -> NaiveTime {
115 time64us_to_time_opt(v).expect("invalid time")
116}
117
118#[inline]
120pub fn time64us_to_time_opt(v: i64) -> Option<NaiveTime> {
121 NaiveTime::from_num_seconds_from_midnight_opt(
122 (v / MICROSECONDS) as u32,
124 (v % MICROSECONDS * MILLISECONDS) as u32,
127 )
128}
129
130#[inline]
132pub fn time64ns_to_time(v: i64) -> NaiveTime {
133 time64ns_to_time_opt(v).expect("invalid time")
134}
135
136#[inline]
138pub fn time64ns_to_time_opt(v: i64) -> Option<NaiveTime> {
139 NaiveTime::from_num_seconds_from_midnight_opt(
140 (v / NANOSECONDS) as u32,
142 (v % NANOSECONDS) as u32,
144 )
145}
146
147#[inline]
149pub fn timestamp_s_to_datetime(seconds: i64) -> NaiveDateTime {
150 timestamp_s_to_datetime_opt(seconds).expect("invalid or out-of-range datetime")
151}
152
153#[inline]
155pub fn timestamp_s_to_datetime_opt(seconds: i64) -> Option<NaiveDateTime> {
156 Some(DateTime::from_timestamp(seconds, 0)?.naive_utc())
157}
158
159#[inline]
161pub fn timestamp_ms_to_datetime(v: i64) -> NaiveDateTime {
162 timestamp_ms_to_datetime_opt(v).expect("invalid or out-of-range datetime")
163}
164
165#[inline]
167pub fn timestamp_ms_to_datetime_opt(v: i64) -> Option<NaiveDateTime> {
168 let delta = TimeDelta::try_milliseconds(v)?;
169 unix_epoch().checked_add_signed(delta)
170}
171
172#[inline]
174pub fn timestamp_us_to_datetime(v: i64) -> NaiveDateTime {
175 timestamp_us_to_datetime_opt(v).expect("invalid or out-of-range datetime")
176}
177
178#[inline]
180pub fn timestamp_us_to_datetime_opt(v: i64) -> Option<NaiveDateTime> {
181 let delta = TimeDelta::microseconds(v);
182 unix_epoch().checked_add_signed(delta)
183}
184
185#[inline]
187pub fn timestamp_ns_to_datetime(v: i64) -> NaiveDateTime {
188 timestamp_ns_to_datetime_opt(v).expect("invalid or out-of-range datetime")
189}
190
191#[inline]
193pub fn timestamp_ns_to_datetime_opt(v: i64) -> Option<NaiveDateTime> {
194 let delta = TimeDelta::nanoseconds(v);
195 unix_epoch().checked_add_signed(delta)
196}
197
198#[inline]
200pub(crate) fn timestamp_to_naive_datetime(
201 timestamp: i64,
202 time_unit: TimeUnit,
203) -> chrono::NaiveDateTime {
204 match time_unit {
205 TimeUnit::Second => timestamp_s_to_datetime(timestamp),
206 TimeUnit::Millisecond => timestamp_ms_to_datetime(timestamp),
207 TimeUnit::Microsecond => timestamp_us_to_datetime(timestamp),
208 TimeUnit::Nanosecond => timestamp_ns_to_datetime(timestamp),
209 }
210}
211
212#[inline]
214pub fn timestamp_to_datetime<T: chrono::TimeZone>(
215 timestamp: i64,
216 time_unit: TimeUnit,
217 timezone: &T,
218) -> chrono::DateTime<T> {
219 timezone.from_utc_datetime(×tamp_to_naive_datetime(timestamp, time_unit))
220}
221
222pub fn timeunit_scale(a: TimeUnit, b: TimeUnit) -> f64 {
226 match (a, b) {
227 (TimeUnit::Second, TimeUnit::Second) => 1.0,
228 (TimeUnit::Second, TimeUnit::Millisecond) => 0.001,
229 (TimeUnit::Second, TimeUnit::Microsecond) => 0.000_001,
230 (TimeUnit::Second, TimeUnit::Nanosecond) => 0.000_000_001,
231 (TimeUnit::Millisecond, TimeUnit::Second) => 1_000.0,
232 (TimeUnit::Millisecond, TimeUnit::Millisecond) => 1.0,
233 (TimeUnit::Millisecond, TimeUnit::Microsecond) => 0.001,
234 (TimeUnit::Millisecond, TimeUnit::Nanosecond) => 0.000_001,
235 (TimeUnit::Microsecond, TimeUnit::Second) => 1_000_000.0,
236 (TimeUnit::Microsecond, TimeUnit::Millisecond) => 1_000.0,
237 (TimeUnit::Microsecond, TimeUnit::Microsecond) => 1.0,
238 (TimeUnit::Microsecond, TimeUnit::Nanosecond) => 0.001,
239 (TimeUnit::Nanosecond, TimeUnit::Second) => 1_000_000_000.0,
240 (TimeUnit::Nanosecond, TimeUnit::Millisecond) => 1_000_000.0,
241 (TimeUnit::Nanosecond, TimeUnit::Microsecond) => 1_000.0,
242 (TimeUnit::Nanosecond, TimeUnit::Nanosecond) => 1.0,
243 }
244}
245
246#[inline]
251pub fn utf8_to_timestamp_scalar<T: chrono::TimeZone>(
252 value: &str,
253 fmt: &str,
254 tz: &T,
255 tu: &TimeUnit,
256) -> Option<i64> {
257 let mut parsed = Parsed::new();
258 let fmt = StrftimeItems::new(fmt);
259 let r = parse(&mut parsed, value, fmt).ok();
260 if r.is_some() {
261 parsed
262 .to_datetime()
263 .map(|x| x.naive_utc())
264 .map(|x| tz.from_utc_datetime(&x))
265 .map(|x| match tu {
266 TimeUnit::Second => x.timestamp(),
267 TimeUnit::Millisecond => x.timestamp_millis(),
268 TimeUnit::Microsecond => x.timestamp_micros(),
269 TimeUnit::Nanosecond => x.timestamp_nanos_opt().unwrap(),
270 })
271 .ok()
272 } else {
273 None
274 }
275}
276
277pub fn parse_offset(offset: &str) -> PolarsResult<FixedOffset> {
281 if offset == "UTC" {
282 return Ok(FixedOffset::east_opt(0).expect("FixedOffset::east out of bounds"));
283 }
284 let error = "timezone offset must be of the form [-]00:00";
285
286 let mut a = offset.split(':');
287 let first: &str = a
288 .next()
289 .ok_or_else(|| polars_err!(InvalidOperation: error))?;
290 let last = a
291 .next()
292 .ok_or_else(|| polars_err!(InvalidOperation: error))?;
293 let hours: i32 = first
294 .parse()
295 .map_err(|_| polars_err!(InvalidOperation: error))?;
296 let minutes: i32 = last
297 .parse()
298 .map_err(|_| polars_err!(InvalidOperation: error))?;
299
300 Ok(FixedOffset::east_opt(hours * 60 * 60 + minutes * 60)
301 .expect("FixedOffset::east out of bounds"))
302}
303
304#[cfg(feature = "chrono-tz")]
306#[cfg_attr(docsrs, doc(cfg(feature = "chrono-tz")))]
307pub fn parse_offset_tz(timezone: &str) -> PolarsResult<chrono_tz::Tz> {
308 timezone
309 .parse::<chrono_tz::Tz>()
310 .map_err(|_| polars_err!(InvalidOperation: "timezone \"{timezone}\" cannot be parsed"))
311}