arrow_odbc/
date_time.rs

1use std::{
2    convert::TryInto,
3    fmt::Display,
4    io::Write,
5    marker::PhantomData,
6    ops::{Div, Mul, Rem},
7};
8
9use arrow::{
10    array::{Array, PrimitiveArray},
11    datatypes::{
12        ArrowPrimitiveType, Time32MillisecondType, Time64MicrosecondType, Time64NanosecondType,
13    },
14};
15use chrono::{Datelike, NaiveDate};
16use odbc_api::{
17    buffers::{AnySliceMut, BufferDesc, TextColumnSliceMut},
18    sys::{Date, Time, Timestamp},
19};
20
21use crate::{WriterError, odbc_writer::WriteStrategy, reader::MappingError};
22
23/// Transform date to days since unix epoch as i32
24pub fn days_since_epoch(date: &Date) -> i32 {
25    let unix_epoch = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap();
26    let date =
27        NaiveDate::from_ymd_opt(date.year as i32, date.month as u32, date.day as u32).unwrap();
28    let duration = date.signed_duration_since(unix_epoch);
29    duration.num_days().try_into().unwrap()
30}
31
32pub fn seconds_since_epoch(from: &Timestamp) -> i64 {
33    let ndt = NaiveDate::from_ymd_opt(from.year as i32, from.month as u32, from.day as u32)
34        .unwrap()
35        .and_hms_opt(from.hour as u32, from.minute as u32, from.second as u32)
36        .unwrap();
37    ndt.and_utc().timestamp()
38}
39
40pub fn ms_since_epoch(from: &Timestamp) -> i64 {
41    let ndt = NaiveDate::from_ymd_opt(from.year as i32, from.month as u32, from.day as u32)
42        .unwrap()
43        .and_hms_nano_opt(
44            from.hour as u32,
45            from.minute as u32,
46            from.second as u32,
47            from.fraction,
48        )
49        .unwrap();
50    ndt.and_utc().timestamp_millis()
51}
52
53pub fn us_since_epoch(from: &Timestamp) -> i64 {
54    let ndt = NaiveDate::from_ymd_opt(from.year as i32, from.month as u32, from.day as u32)
55        .unwrap()
56        .and_hms_nano_opt(
57            from.hour as u32,
58            from.minute as u32,
59            from.second as u32,
60            from.fraction,
61        )
62        .unwrap();
63    ndt.and_utc().timestamp_micros()
64}
65
66pub fn ns_since_epoch(from: &Timestamp) -> Result<i64, MappingError> {
67    let ndt = NaiveDate::from_ymd_opt(from.year as i32, from.month as u32, from.day as u32)
68        .unwrap()
69        .and_hms_nano_opt(
70            from.hour as u32,
71            from.minute as u32,
72            from.second as u32,
73            from.fraction,
74        )
75        .unwrap();
76
77    // The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and
78    // 2262-04-11T23:47:16.854775804
79    ndt.and_utc()
80        .timestamp_nanos_opt()
81        .ok_or(MappingError::OutOfRangeTimestampNs { value: ndt })
82}
83
84pub fn epoch_to_date(from: i32) -> Date {
85    // Offset between between ce and unix epoch
86    const OFFSET: i32 = 719_163;
87    let nd = NaiveDate::from_num_days_from_ce_opt(from + OFFSET).unwrap();
88    Date {
89        year: nd.year().try_into().unwrap(),
90        month: nd.month().try_into().unwrap(),
91        day: nd.day().try_into().unwrap(),
92    }
93}
94
95pub fn sec_since_midnight_to_time(from: i32) -> Time {
96    let unit_min = 60;
97    let unit_hour = unit_min * 60;
98    let hour = from / unit_hour;
99    let minute = (from % unit_hour) / unit_min;
100    let second = from % unit_min;
101    Time {
102        hour: hour.try_into().unwrap(),
103        minute: minute.try_into().unwrap(),
104        second: second.try_into().unwrap(),
105    }
106}
107
108pub struct NullableTimeAsText<P> {
109    _phantom: PhantomData<P>,
110}
111
112impl<P> NullableTimeAsText<P> {
113    pub fn new() -> Self {
114        Self {
115            _phantom: PhantomData,
116        }
117    }
118}
119
120pub trait TimePrimitive {
121    type Integer: From<i32>
122        + Copy
123        + Mul<Output = Self::Integer>
124        + Div<Output = Self::Integer>
125        + Rem<Output = Self::Integer>
126        + Display;
127    const SCALE: usize;
128    const PRECISION_FACTOR: Self::Integer;
129    const STR_LEN: usize;
130
131    fn insert_at(index: usize, from: Self::Integer, to: &mut TextColumnSliceMut<u8>) {
132        let sixty: Self::Integer = 60.into();
133        let unit_min = sixty * Self::PRECISION_FACTOR;
134        let unit_hour = unit_min * sixty;
135        let hour = from / unit_hour;
136        let minute = (from % unit_hour) / unit_min;
137        let second = (from % unit_min) / Self::PRECISION_FACTOR;
138        let fraction = from % Self::PRECISION_FACTOR;
139        write!(
140            to.set_mut(index, Self::STR_LEN),
141            "{hour:02}:{minute:02}:{second:02}.{fraction:0s$}",
142            s = Self::SCALE
143        )
144        .unwrap();
145    }
146}
147
148impl TimePrimitive for Time32MillisecondType {
149    type Integer = i32;
150    const SCALE: usize = 3;
151    const PRECISION_FACTOR: i32 = 1_000;
152    // Length of text representation of time. HH:MM:SS.fff
153    const STR_LEN: usize = 12;
154}
155
156impl TimePrimitive for Time64MicrosecondType {
157    type Integer = i64;
158
159    const SCALE: usize = 6;
160    const PRECISION_FACTOR: i64 = 1_000_000;
161    // Length of text representation of time. HH:MM:SS.ffffff
162    const STR_LEN: usize = 15;
163}
164
165impl TimePrimitive for Time64NanosecondType {
166    type Integer = i64;
167    // For now we insert nanoseconds with a precision of 7 digits rather than 9
168    const SCALE: usize = 9;
169    const PRECISION_FACTOR: i64 = 1_000_000_000;
170    // Length of text representation of time. HH:MM:SS.fffffffff
171    const STR_LEN: usize = 18;
172}
173
174impl<P> WriteStrategy for NullableTimeAsText<P>
175where
176    P: ArrowPrimitiveType + TimePrimitive<Integer = <P as ArrowPrimitiveType>::Native>,
177{
178    fn buffer_desc(&self) -> BufferDesc {
179        BufferDesc::Text {
180            max_str_len: P::STR_LEN,
181        }
182    }
183
184    fn write_rows(
185        &self,
186        param_offset: usize,
187        column_buf: AnySliceMut<'_>,
188        array: &dyn Array,
189    ) -> Result<(), WriterError> {
190        let from = array.as_any().downcast_ref::<PrimitiveArray<P>>().unwrap();
191        let mut to = column_buf.as_text_view().unwrap();
192        for (index, elapsed_since_midnight) in from.iter().enumerate() {
193            if let Some(from) = elapsed_since_midnight {
194                P::insert_at(index + param_offset, from, &mut to)
195            } else {
196                to.set_cell(index + param_offset, None)
197            }
198        }
199        Ok(())
200    }
201}