Skip to main content

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