Skip to main content

arrow_array/
temporal_conversions.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: See the NOTICE file distributed with this work for additional information regarding copyright ownership
3// SPDX-FileContributor: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements
4
5//! Conversion methods for dates and times.
6
7use crate::ArrowPrimitiveType;
8use crate::timezone::Tz;
9use arrow_schema::{DataType, TimeUnit};
10use chrono::{DateTime, Duration, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Timelike, Utc};
11use std::mem::ManuallyDrop;
12
13/// Number of seconds in a day
14pub const SECONDS_IN_DAY: i64 = 86_400;
15/// Number of milliseconds in a second
16pub const MILLISECONDS: i64 = 1_000;
17/// Number of microseconds in a second
18pub const MICROSECONDS: i64 = 1_000_000;
19/// Number of nanoseconds in a second
20pub const NANOSECONDS: i64 = 1_000_000_000;
21
22/// Number of milliseconds in a day
23pub const MILLISECONDS_IN_DAY: i64 = SECONDS_IN_DAY * MILLISECONDS;
24/// Number of microseconds in a day
25pub const MICROSECONDS_IN_DAY: i64 = SECONDS_IN_DAY * MICROSECONDS;
26/// Number of nanoseconds in a day
27pub const NANOSECONDS_IN_DAY: i64 = SECONDS_IN_DAY * NANOSECONDS;
28
29/// Constant from chrono crate
30///
31/// Number of days between Januari 1, 1970 and December 31, 1 BCE which we define to be day 0.
32/// 4 full leap year cycles until December 31, 1600     4 * 146097 = 584388
33/// 1 day until January 1, 1601                                           1
34/// 369 years until Januari 1, 1970                      369 * 365 = 134685
35/// of which floor(369 / 4) are leap years          floor(369 / 4) =     92
36/// except for 1700, 1800 and 1900                                       -3 +
37///                                                                  --------
38///                                                                  719163
39pub const UNIX_EPOCH_DAY: i64 = 719_163;
40
41/// converts a `i32` representing a `date32` to [`NaiveDateTime`]
42#[inline]
43pub fn date32_to_datetime(v: i32) -> Option<NaiveDateTime> {
44    Some(DateTime::from_timestamp(v as i64 * SECONDS_IN_DAY, 0)?.naive_utc())
45}
46
47/// converts a `i64` representing a `date64` to [`NaiveDateTime`]
48#[inline]
49pub fn date64_to_datetime(v: i64) -> Option<NaiveDateTime> {
50    let (sec, milli_sec) = split_second(v, MILLISECONDS);
51
52    let datetime = DateTime::from_timestamp(
53        // extract seconds from milliseconds
54        sec,
55        // discard extracted seconds and convert milliseconds to nanoseconds
56        milli_sec * MICROSECONDS as u32,
57    )?;
58    Some(datetime.naive_utc())
59}
60
61/// converts a `i32` representing a `time32(s)` to [`NaiveDateTime`]
62#[inline]
63pub fn time32s_to_time(v: i32) -> Option<NaiveTime> {
64    NaiveTime::from_num_seconds_from_midnight_opt(u32::try_from(v).ok()?, 0)
65}
66
67/// converts a `i32` representing a `time32(ms)` to [`NaiveDateTime`]
68#[inline]
69pub fn time32ms_to_time(v: i32) -> Option<NaiveTime> {
70    let v = v as i64;
71    NaiveTime::from_num_seconds_from_midnight_opt(
72        // extract seconds from milliseconds
73        u32::try_from(v / MILLISECONDS).ok()?,
74        // discard extracted seconds and convert milliseconds to
75        // nanoseconds
76        u32::try_from(v % MILLISECONDS * MICROSECONDS).ok()?,
77    )
78}
79
80/// converts a `i64` representing a `time64(us)` to [`NaiveDateTime`]
81#[inline]
82pub fn time64us_to_time(v: i64) -> Option<NaiveTime> {
83    NaiveTime::from_num_seconds_from_midnight_opt(
84        // extract seconds from microseconds
85        u32::try_from(v / MICROSECONDS).ok()?,
86        // discard extracted seconds and convert microseconds to
87        // nanoseconds
88        u32::try_from(v % MICROSECONDS * MILLISECONDS).ok()?,
89    )
90}
91
92/// converts a `i64` representing a `time64(ns)` to [`NaiveDateTime`]
93#[inline]
94pub fn time64ns_to_time(v: i64) -> Option<NaiveTime> {
95    NaiveTime::from_num_seconds_from_midnight_opt(
96        // extract seconds from nanoseconds
97        u32::try_from(v / NANOSECONDS).ok()?,
98        // discard extracted seconds
99        u32::try_from(v % NANOSECONDS).ok()?,
100    )
101}
102
103/// converts [`NaiveTime`] to a `i32` representing a `time32(s)`
104#[inline]
105pub fn time_to_time32s(v: NaiveTime) -> i32 {
106    v.num_seconds_from_midnight() as i32
107}
108
109/// converts [`NaiveTime`] to a `i32` representing a `time32(ms)`
110#[inline]
111pub fn time_to_time32ms(v: NaiveTime) -> i32 {
112    (v.num_seconds_from_midnight() as i64 * MILLISECONDS
113        + v.nanosecond() as i64 * MILLISECONDS / NANOSECONDS) as i32
114}
115
116/// converts [`NaiveTime`] to a `i64` representing a `time64(us)`
117#[inline]
118pub fn time_to_time64us(v: NaiveTime) -> i64 {
119    v.num_seconds_from_midnight() as i64 * MICROSECONDS
120        + v.nanosecond() as i64 * MICROSECONDS / NANOSECONDS
121}
122
123/// converts [`NaiveTime`] to a `i64` representing a `time64(ns)`
124#[inline]
125pub fn time_to_time64ns(v: NaiveTime) -> i64 {
126    v.num_seconds_from_midnight() as i64 * NANOSECONDS + v.nanosecond() as i64
127}
128
129/// converts a `i64` representing a `timestamp(s)` to [`NaiveDateTime`]
130#[inline]
131pub fn timestamp_s_to_datetime(v: i64) -> Option<NaiveDateTime> {
132    Some(DateTime::from_timestamp(v, 0)?.naive_utc())
133}
134
135/// Similar to timestamp_s_to_datetime but only compute `date`
136#[inline]
137pub fn timestamp_s_to_date(secs: i64) -> Option<NaiveDateTime> {
138    let days = secs.div_euclid(86_400) + UNIX_EPOCH_DAY;
139    if days < i32::MIN as i64 || days > i32::MAX as i64 {
140        return None;
141    }
142    let date = NaiveDate::from_num_days_from_ce_opt(days as i32)?;
143    Some(date.and_time(NaiveTime::default()).and_utc().naive_utc())
144}
145
146/// Similar to timestamp_s_to_datetime but only compute `time`
147#[inline]
148pub fn timestamp_s_to_time(secs: i64) -> Option<NaiveDateTime> {
149    let secs = secs.rem_euclid(86_400);
150    let time = NaiveTime::from_num_seconds_from_midnight_opt(secs as u32, 0)?;
151    Some(
152        DateTime::<Utc>::from_naive_utc_and_offset(
153            NaiveDateTime::new(NaiveDate::default(), time),
154            Utc,
155        )
156        .naive_utc(),
157    )
158}
159
160/// converts a `i64` representing a `timestamp(ms)` to [`NaiveDateTime`]
161#[inline]
162pub fn timestamp_ms_to_datetime(v: i64) -> Option<NaiveDateTime> {
163    let (sec, milli_sec) = split_second(v, MILLISECONDS);
164
165    let datetime = DateTime::from_timestamp(
166        // extract seconds from milliseconds
167        sec,
168        // discard extracted seconds and convert milliseconds to nanoseconds
169        milli_sec * MICROSECONDS as u32,
170    )?;
171    Some(datetime.naive_utc())
172}
173
174/// converts a `i64` representing a `timestamp(us)` to [`NaiveDateTime`]
175#[inline]
176pub fn timestamp_us_to_datetime(v: i64) -> Option<NaiveDateTime> {
177    let (sec, micro_sec) = split_second(v, MICROSECONDS);
178
179    let datetime = DateTime::from_timestamp(
180        // extract seconds from microseconds
181        sec,
182        // discard extracted seconds and convert microseconds to nanoseconds
183        micro_sec * MILLISECONDS as u32,
184    )?;
185    Some(datetime.naive_utc())
186}
187
188/// converts a `i64` representing a `timestamp(ns)` to [`NaiveDateTime`]
189#[inline]
190pub fn timestamp_ns_to_datetime(v: i64) -> Option<NaiveDateTime> {
191    let (sec, nano_sec) = split_second(v, NANOSECONDS);
192
193    let datetime = DateTime::from_timestamp(
194        // extract seconds from nanoseconds
195        sec, // discard extracted seconds
196        nano_sec,
197    )?;
198    Some(datetime.naive_utc())
199}
200
201#[inline]
202pub(crate) fn split_second(v: i64, base: i64) -> (i64, u32) {
203    (v.div_euclid(base), v.rem_euclid(base) as u32)
204}
205
206/// converts a `i64` representing a `duration(s)` to [`Option<Duration>`]
207#[inline]
208pub fn try_duration_s_to_duration(v: i64) -> Option<Duration> {
209    Duration::try_seconds(v)
210}
211
212/// converts a `i64` representing a `duration(ms)` to [`Option<Duration>`]
213#[inline]
214pub fn try_duration_ms_to_duration(v: i64) -> Option<Duration> {
215    Duration::try_milliseconds(v)
216}
217
218/// converts a `i64` representing a `duration(us)` to [`Duration`]
219#[inline]
220pub fn duration_us_to_duration(v: i64) -> Duration {
221    Duration::microseconds(v)
222}
223
224/// converts a `i64` representing a `duration(ns)` to [`Duration`]
225#[inline]
226pub fn duration_ns_to_duration(v: i64) -> Duration {
227    Duration::nanoseconds(v)
228}
229
230/// Converts an [`ArrowPrimitiveType`] to [`NaiveDateTime`]
231pub fn as_datetime<T: ArrowPrimitiveType>(v: i64) -> Option<NaiveDateTime> {
232    // Dropping the `DATA_TYPE` temporary calls `DataType`'s drop glue, which
233    // LLVM doesn't inline, on every value. A const owns no heap memory, so
234    // skipping the drop leaks nothing.
235    as_datetime_with_data_type(&ManuallyDrop::new(T::DATA_TYPE), v)
236}
237
238/// Converts a value of the given [`DataType`] to [`NaiveDateTime`]
239///
240/// Non-generic counterpart of [`as_datetime`], driven by the runtime
241/// [`DataType`] so callers that already dispatch on the data type (e.g. `Debug`
242/// impls) do not monomorphize this logic for every primitive type
243#[inline]
244pub(crate) fn as_datetime_with_data_type(data_type: &DataType, v: i64) -> Option<NaiveDateTime> {
245    match data_type {
246        DataType::Date32 => date32_to_datetime(v as i32),
247        DataType::Date64 => date64_to_datetime(v),
248        DataType::Time32(_) | DataType::Time64(_) => None,
249        DataType::Timestamp(unit, _) => match unit {
250            TimeUnit::Second => timestamp_s_to_datetime(v),
251            TimeUnit::Millisecond => timestamp_ms_to_datetime(v),
252            TimeUnit::Microsecond => timestamp_us_to_datetime(v),
253            TimeUnit::Nanosecond => timestamp_ns_to_datetime(v),
254        },
255        // interval is not yet fully documented [ARROW-3097]
256        DataType::Interval(_) => None,
257        _ => None,
258    }
259}
260
261/// Converts an [`ArrowPrimitiveType`] to [`DateTime<Tz>`]
262pub fn as_datetime_with_timezone<T: ArrowPrimitiveType>(v: i64, tz: Tz) -> Option<DateTime<Tz>> {
263    // See `as_datetime` for why the temporary is not dropped
264    as_datetime_with_timezone_and_data_type(&ManuallyDrop::new(T::DATA_TYPE), v, tz)
265}
266
267/// Converts a value of the given [`DataType`] to [`DateTime<Tz>`]
268///
269/// Non-generic counterpart of [`as_datetime_with_timezone`], see
270/// [`as_datetime_with_data_type`]
271#[inline]
272pub(crate) fn as_datetime_with_timezone_and_data_type(
273    data_type: &DataType,
274    v: i64,
275    tz: Tz,
276) -> Option<DateTime<Tz>> {
277    let naive = as_datetime_with_data_type(data_type, v)?;
278    Some(Utc.from_utc_datetime(&naive).with_timezone(&tz))
279}
280
281/// Converts an [`ArrowPrimitiveType`] to [`NaiveDate`]
282pub fn as_date<T: ArrowPrimitiveType>(v: i64) -> Option<NaiveDate> {
283    as_datetime::<T>(v).map(|datetime| datetime.date())
284}
285
286/// Converts an [`ArrowPrimitiveType`] to [`NaiveTime`]
287pub fn as_time<T: ArrowPrimitiveType>(v: i64) -> Option<NaiveTime> {
288    // See `as_datetime` for why the temporary is not dropped
289    as_time_with_data_type(&ManuallyDrop::new(T::DATA_TYPE), v)
290}
291
292/// Converts a value of the given [`DataType`] to [`NaiveTime`]
293///
294/// Non-generic counterpart of [`as_time`], see [`as_datetime_with_data_type`]
295#[inline]
296pub(crate) fn as_time_with_data_type(data_type: &DataType, v: i64) -> Option<NaiveTime> {
297    match data_type {
298        DataType::Time32(unit) => {
299            // safe to immediately cast to u32 as `self.value(i)` is positive i32
300            let v = v as u32;
301            match unit {
302                TimeUnit::Second => time32s_to_time(v as i32),
303                TimeUnit::Millisecond => time32ms_to_time(v as i32),
304                _ => None,
305            }
306        }
307        DataType::Time64(unit) => match unit {
308            TimeUnit::Microsecond => time64us_to_time(v),
309            TimeUnit::Nanosecond => time64ns_to_time(v),
310            _ => None,
311        },
312        DataType::Timestamp(_, _) => {
313            as_datetime_with_data_type(data_type, v).map(|datetime| datetime.time())
314        }
315        DataType::Date32 | DataType::Date64 => NaiveTime::from_hms_opt(0, 0, 0),
316        DataType::Interval(_) => None,
317        _ => None,
318    }
319}
320
321/// Converts an [`ArrowPrimitiveType`] to [`Duration`]
322pub fn as_duration<T: ArrowPrimitiveType>(v: i64) -> Option<Duration> {
323    // See `as_datetime` for why the temporary is not dropped
324    match &*ManuallyDrop::new(T::DATA_TYPE) {
325        DataType::Duration(unit) => match unit {
326            TimeUnit::Second => try_duration_s_to_duration(v),
327            TimeUnit::Millisecond => try_duration_ms_to_duration(v),
328            TimeUnit::Microsecond => Some(duration_us_to_duration(v)),
329            TimeUnit::Nanosecond => Some(duration_ns_to_duration(v)),
330        },
331        _ => None,
332    }
333}
334
335#[cfg(test)]
336mod tests {
337    use crate::temporal_conversions::{
338        NANOSECONDS, date64_to_datetime, split_second, time32ms_to_time, time32s_to_time,
339        time64ns_to_time, time64us_to_time, timestamp_ms_to_datetime, timestamp_ns_to_datetime,
340        timestamp_s_to_date, timestamp_s_to_datetime, timestamp_s_to_time,
341        timestamp_us_to_datetime,
342    };
343    use chrono::DateTime;
344
345    #[test]
346    fn test_timestamp_func() {
347        let timestamp = 1234;
348        let datetime = timestamp_s_to_datetime(timestamp).unwrap();
349        let expected_date = datetime.date();
350        let expected_time = datetime.time();
351
352        assert_eq!(
353            timestamp_s_to_date(timestamp).unwrap().date(),
354            expected_date
355        );
356        assert_eq!(
357            timestamp_s_to_time(timestamp).unwrap().time(),
358            expected_time
359        );
360    }
361
362    #[test]
363    fn negative_input_timestamp_ns_to_datetime() {
364        assert_eq!(
365            timestamp_ns_to_datetime(-1),
366            DateTime::from_timestamp(-1, 999_999_999).map(|x| x.naive_utc())
367        );
368
369        assert_eq!(
370            timestamp_ns_to_datetime(-1_000_000_001),
371            DateTime::from_timestamp(-2, 999_999_999).map(|x| x.naive_utc())
372        );
373    }
374
375    #[test]
376    fn negative_input_timestamp_us_to_datetime() {
377        assert_eq!(
378            timestamp_us_to_datetime(-1),
379            DateTime::from_timestamp(-1, 999_999_000).map(|x| x.naive_utc())
380        );
381
382        assert_eq!(
383            timestamp_us_to_datetime(-1_000_001),
384            DateTime::from_timestamp(-2, 999_999_000).map(|x| x.naive_utc())
385        );
386    }
387
388    #[test]
389    fn negative_input_timestamp_ms_to_datetime() {
390        assert_eq!(
391            timestamp_ms_to_datetime(-1),
392            DateTime::from_timestamp(-1, 999_000_000).map(|x| x.naive_utc())
393        );
394
395        assert_eq!(
396            timestamp_ms_to_datetime(-1_001),
397            DateTime::from_timestamp(-2, 999_000_000).map(|x| x.naive_utc())
398        );
399    }
400
401    #[test]
402    fn negative_input_date64_to_datetime() {
403        assert_eq!(
404            date64_to_datetime(-1),
405            DateTime::from_timestamp(-1, 999_000_000).map(|x| x.naive_utc())
406        );
407
408        assert_eq!(
409            date64_to_datetime(-1_001),
410            DateTime::from_timestamp(-2, 999_000_000).map(|x| x.naive_utc())
411        );
412    }
413
414    #[test]
415    fn test_split_seconds() {
416        let (sec, nano_sec) = split_second(100, NANOSECONDS);
417        assert_eq!(sec, 0);
418        assert_eq!(nano_sec, 100);
419
420        let (sec, nano_sec) = split_second(123_000_000_456, NANOSECONDS);
421        assert_eq!(sec, 123);
422        assert_eq!(nano_sec, 456);
423
424        let (sec, nano_sec) = split_second(-1, NANOSECONDS);
425        assert_eq!(sec, -1);
426        assert_eq!(nano_sec, 999_999_999);
427
428        let (sec, nano_sec) = split_second(-123_000_000_001, NANOSECONDS);
429        assert_eq!(sec, -124);
430        assert_eq!(nano_sec, 999_999_999);
431    }
432
433    #[test]
434    fn test_time_conversions_out_of_bounds_no_wrapping() {
435        let wrapped = (1_i64 << 32) + 5;
436        assert_eq!(time64us_to_time(wrapped * 1_000_000), None);
437        assert_eq!(time64ns_to_time(wrapped * 1_000_000_000), None);
438
439        assert_eq!(time32s_to_time(86_400), None);
440        assert_eq!(time32s_to_time(-1), None);
441        assert_eq!(time32ms_to_time(86_400_000), None);
442        assert_eq!(time32ms_to_time(-1), None);
443        assert_eq!(time64us_to_time(86_400_000_000), None);
444        assert_eq!(time64us_to_time(-1), None);
445        assert_eq!(time64ns_to_time(86_400_000_000_000), None);
446        assert_eq!(time64ns_to_time(-1), None);
447    }
448}