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
24pub 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 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 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 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 const STR_LEN: usize = 15;
164}
165
166impl TimePrimitive for Time64NanosecondType {
167 type Integer = i64;
168 const SCALE: usize = 9;
170 const PRECISION_FACTOR: i64 = 1_000_000_000;
171 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}