polars_arrow/
temporal_conversions.rs1use chrono::format::{parse, Parsed, StrftimeItems};
4use chrono::{DateTime, Duration, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, TimeDelta};
5use polars_error::{polars_err, PolarsResult};
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]
28pub fn date32_to_datetime(v: i32) -> NaiveDateTime {
29 date32_to_datetime_opt(v).expect("invalid or out-of-range datetime")
30}
31
32#[inline]
34pub fn date32_to_datetime_opt(v: i32) -> Option<NaiveDateTime> {
35 let delta = TimeDelta::try_days(v.into())?;
36 NaiveDateTime::UNIX_EPOCH.checked_add_signed(delta)
37}
38
39#[inline]
41pub fn date32_to_date(days: i32) -> NaiveDate {
42 date32_to_date_opt(days).expect("out-of-range date")
43}
44
45#[inline]
47pub fn date32_to_date_opt(days: i32) -> Option<NaiveDate> {
48 NaiveDate::from_num_days_from_ce_opt(EPOCH_DAYS_FROM_CE + days)
49}
50
51#[inline]
53pub fn date64_to_datetime(v: i64) -> NaiveDateTime {
54 TimeDelta::try_milliseconds(v)
55 .and_then(|delta| NaiveDateTime::UNIX_EPOCH.checked_add_signed(delta))
56 .expect("invalid or out-of-range datetime")
57}
58
59#[inline]
61pub fn date64_to_date(milliseconds: i64) -> NaiveDate {
62 date64_to_datetime(milliseconds).date()
63}
64
65#[inline]
67pub fn time32s_to_time(v: i32) -> NaiveTime {
68 NaiveTime::from_num_seconds_from_midnight_opt(v as u32, 0).expect("invalid time")
69}
70
71#[inline]
73pub fn duration_s_to_duration(v: i64) -> Duration {
74 Duration::try_seconds(v).expect("out-of-range duration")
75}
76
77#[inline]
79pub fn duration_ms_to_duration(v: i64) -> Duration {
80 Duration::try_milliseconds(v).expect("out-of-range in duration conversion")
81}
82
83#[inline]
85pub fn duration_us_to_duration(v: i64) -> Duration {
86 Duration::microseconds(v)
87}
88
89#[inline]
91pub fn duration_ns_to_duration(v: i64) -> Duration {
92 Duration::nanoseconds(v)
93}
94
95#[inline]
97pub fn time32ms_to_time(v: i32) -> NaiveTime {
98 let v = v as i64;
99 let seconds = v / MILLISECONDS;
100
101 let milli_to_nano = 1_000_000;
102 let nano = (v - seconds * MILLISECONDS) * milli_to_nano;
103 NaiveTime::from_num_seconds_from_midnight_opt(seconds as u32, nano as u32)
104 .expect("invalid time")
105}
106
107#[inline]
109pub fn time64us_to_time(v: i64) -> NaiveTime {
110 time64us_to_time_opt(v).expect("invalid time")
111}
112
113#[inline]
115pub fn time64us_to_time_opt(v: i64) -> Option<NaiveTime> {
116 NaiveTime::from_num_seconds_from_midnight_opt(
117 (v / MICROSECONDS) as u32,
119 (v % MICROSECONDS * MILLISECONDS) as u32,
122 )
123}
124
125#[inline]
127pub fn time64ns_to_time(v: i64) -> NaiveTime {
128 time64ns_to_time_opt(v).expect("invalid time")
129}
130
131#[inline]
133pub fn time64ns_to_time_opt(v: i64) -> Option<NaiveTime> {
134 NaiveTime::from_num_seconds_from_midnight_opt(
135 (v / NANOSECONDS) as u32,
137 (v % NANOSECONDS) as u32,
139 )
140}
141
142#[inline]
144pub fn timestamp_s_to_datetime(seconds: i64) -> NaiveDateTime {
145 timestamp_s_to_datetime_opt(seconds).expect("invalid or out-of-range datetime")
146}
147
148#[inline]
150pub fn timestamp_s_to_datetime_opt(seconds: i64) -> Option<NaiveDateTime> {
151 Some(DateTime::from_timestamp(seconds, 0)?.naive_utc())
152}
153
154#[inline]
156pub fn timestamp_ms_to_datetime(v: i64) -> NaiveDateTime {
157 timestamp_ms_to_datetime_opt(v).expect("invalid or out-of-range datetime")
158}
159
160#[inline]
162pub fn timestamp_ms_to_datetime_opt(v: i64) -> Option<NaiveDateTime> {
163 let delta = TimeDelta::try_milliseconds(v)?;
164 NaiveDateTime::UNIX_EPOCH.checked_add_signed(delta)
165}
166
167#[inline]
169pub fn timestamp_us_to_datetime(v: i64) -> NaiveDateTime {
170 timestamp_us_to_datetime_opt(v).expect("invalid or out-of-range datetime")
171}
172
173#[inline]
175pub fn timestamp_us_to_datetime_opt(v: i64) -> Option<NaiveDateTime> {
176 let delta = TimeDelta::microseconds(v);
177 NaiveDateTime::UNIX_EPOCH.checked_add_signed(delta)
178}
179
180#[inline]
182pub fn timestamp_ns_to_datetime(v: i64) -> NaiveDateTime {
183 timestamp_ns_to_datetime_opt(v).expect("invalid or out-of-range datetime")
184}
185
186#[inline]
188pub fn timestamp_ns_to_datetime_opt(v: i64) -> Option<NaiveDateTime> {
189 let delta = TimeDelta::nanoseconds(v);
190 NaiveDateTime::UNIX_EPOCH.checked_add_signed(delta)
191}
192
193#[inline]
195pub(crate) fn timestamp_to_naive_datetime(
196 timestamp: i64,
197 time_unit: TimeUnit,
198) -> chrono::NaiveDateTime {
199 match time_unit {
200 TimeUnit::Second => timestamp_s_to_datetime(timestamp),
201 TimeUnit::Millisecond => timestamp_ms_to_datetime(timestamp),
202 TimeUnit::Microsecond => timestamp_us_to_datetime(timestamp),
203 TimeUnit::Nanosecond => timestamp_ns_to_datetime(timestamp),
204 }
205}
206
207#[inline]
209pub fn timestamp_to_datetime<T: chrono::TimeZone>(
210 timestamp: i64,
211 time_unit: TimeUnit,
212 timezone: &T,
213) -> chrono::DateTime<T> {
214 timezone.from_utc_datetime(×tamp_to_naive_datetime(timestamp, time_unit))
215}
216
217pub fn timeunit_scale(a: TimeUnit, b: TimeUnit) -> f64 {
221 match (a, b) {
222 (TimeUnit::Second, TimeUnit::Second) => 1.0,
223 (TimeUnit::Second, TimeUnit::Millisecond) => 0.001,
224 (TimeUnit::Second, TimeUnit::Microsecond) => 0.000_001,
225 (TimeUnit::Second, TimeUnit::Nanosecond) => 0.000_000_001,
226 (TimeUnit::Millisecond, TimeUnit::Second) => 1_000.0,
227 (TimeUnit::Millisecond, TimeUnit::Millisecond) => 1.0,
228 (TimeUnit::Millisecond, TimeUnit::Microsecond) => 0.001,
229 (TimeUnit::Millisecond, TimeUnit::Nanosecond) => 0.000_001,
230 (TimeUnit::Microsecond, TimeUnit::Second) => 1_000_000.0,
231 (TimeUnit::Microsecond, TimeUnit::Millisecond) => 1_000.0,
232 (TimeUnit::Microsecond, TimeUnit::Microsecond) => 1.0,
233 (TimeUnit::Microsecond, TimeUnit::Nanosecond) => 0.001,
234 (TimeUnit::Nanosecond, TimeUnit::Second) => 1_000_000_000.0,
235 (TimeUnit::Nanosecond, TimeUnit::Millisecond) => 1_000_000.0,
236 (TimeUnit::Nanosecond, TimeUnit::Microsecond) => 1_000.0,
237 (TimeUnit::Nanosecond, TimeUnit::Nanosecond) => 1.0,
238 }
239}
240
241#[inline]
246pub fn utf8_to_timestamp_scalar<T: chrono::TimeZone>(
247 value: &str,
248 fmt: &str,
249 tz: &T,
250 tu: &TimeUnit,
251) -> Option<i64> {
252 let mut parsed = Parsed::new();
253 let fmt = StrftimeItems::new(fmt);
254 let r = parse(&mut parsed, value, fmt).ok();
255 if r.is_some() {
256 parsed
257 .to_datetime()
258 .map(|x| x.naive_utc())
259 .map(|x| tz.from_utc_datetime(&x))
260 .map(|x| match tu {
261 TimeUnit::Second => x.timestamp(),
262 TimeUnit::Millisecond => x.timestamp_millis(),
263 TimeUnit::Microsecond => x.timestamp_micros(),
264 TimeUnit::Nanosecond => x.timestamp_nanos_opt().unwrap(),
265 })
266 .ok()
267 } else {
268 None
269 }
270}
271
272pub fn parse_offset(offset: &str) -> PolarsResult<FixedOffset> {
276 if offset == "UTC" {
277 return Ok(FixedOffset::east_opt(0).expect("FixedOffset::east out of bounds"));
278 }
279 let error = "timezone offset must be of the form [-]00:00";
280
281 let mut a = offset.split(':');
282 let first: &str = a
283 .next()
284 .ok_or_else(|| polars_err!(InvalidOperation: error))?;
285 let last = a
286 .next()
287 .ok_or_else(|| polars_err!(InvalidOperation: error))?;
288 let hours: i32 = first
289 .parse()
290 .map_err(|_| polars_err!(InvalidOperation: error))?;
291 let minutes: i32 = last
292 .parse()
293 .map_err(|_| polars_err!(InvalidOperation: error))?;
294
295 Ok(FixedOffset::east_opt(hours * 60 * 60 + minutes * 60)
296 .expect("FixedOffset::east out of bounds"))
297}
298
299#[cfg(feature = "chrono-tz")]
301#[cfg_attr(docsrs, doc(cfg(feature = "chrono-tz")))]
302pub fn parse_offset_tz(timezone: &str) -> PolarsResult<chrono_tz::Tz> {
303 timezone
304 .parse::<chrono_tz::Tz>()
305 .map_err(|_| polars_err!(InvalidOperation: "timezone \"{timezone}\" cannot be parsed"))
306}