use std::{
convert::TryInto,
fmt::Display,
io::Write,
marker::PhantomData,
ops::{Div, Mul, Rem},
};
use arrow::{
array::{Array, PrimitiveArray},
datatypes::{
ArrowPrimitiveType, Time32MillisecondType, Time64MicrosecondType, Time64NanosecondType,
},
};
use chrono::{Datelike, NaiveDate};
use odbc_api::{
BindParamDesc,
buffers::{AnySliceMut, TextColumnSliceMut},
sys::{Date, Time, Timestamp},
};
use crate::{WriterError, odbc_writer::WriteStrategy, reader::MappingError};
pub fn days_since_epoch(date: &Date) -> i32 {
let unix_epoch = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap();
let date =
NaiveDate::from_ymd_opt(date.year as i32, date.month as u32, date.day as u32).unwrap();
let duration = date.signed_duration_since(unix_epoch);
duration.num_days().try_into().unwrap()
}
pub fn seconds_since_epoch(from: &Timestamp) -> i64 {
let ndt = NaiveDate::from_ymd_opt(from.year as i32, from.month as u32, from.day as u32)
.unwrap()
.and_hms_opt(from.hour as u32, from.minute as u32, from.second as u32)
.unwrap();
ndt.and_utc().timestamp()
}
pub fn ms_since_epoch(from: &Timestamp) -> i64 {
let ndt = NaiveDate::from_ymd_opt(from.year as i32, from.month as u32, from.day as u32)
.unwrap()
.and_hms_nano_opt(
from.hour as u32,
from.minute as u32,
from.second as u32,
from.fraction,
)
.unwrap();
ndt.and_utc().timestamp_millis()
}
pub fn us_since_epoch(from: &Timestamp) -> i64 {
let ndt = NaiveDate::from_ymd_opt(from.year as i32, from.month as u32, from.day as u32)
.unwrap()
.and_hms_nano_opt(
from.hour as u32,
from.minute as u32,
from.second as u32,
from.fraction,
)
.unwrap();
ndt.and_utc().timestamp_micros()
}
pub fn ns_since_epoch(from: &Timestamp) -> Result<i64, MappingError> {
let ndt = NaiveDate::from_ymd_opt(from.year as i32, from.month as u32, from.day as u32)
.unwrap()
.and_hms_nano_opt(
from.hour as u32,
from.minute as u32,
from.second as u32,
from.fraction,
)
.unwrap();
ndt.and_utc()
.timestamp_nanos_opt()
.ok_or(MappingError::OutOfRangeTimestampNs { value: ndt })
}
pub fn epoch_to_date(from: i32) -> Date {
const OFFSET: i32 = 719_163;
let nd = NaiveDate::from_num_days_from_ce_opt(from + OFFSET).unwrap();
Date {
year: nd.year().try_into().unwrap(),
month: nd.month().try_into().unwrap(),
day: nd.day().try_into().unwrap(),
}
}
pub fn sec_since_midnight_to_time(from: i32) -> Time {
let unit_min = 60;
let unit_hour = unit_min * 60;
let hour = from / unit_hour;
let minute = (from % unit_hour) / unit_min;
let second = from % unit_min;
Time {
hour: hour.try_into().unwrap(),
minute: minute.try_into().unwrap(),
second: second.try_into().unwrap(),
}
}
pub struct NullableTimeAsText<P> {
_phantom: PhantomData<P>,
}
impl<P> NullableTimeAsText<P> {
pub fn new() -> Self {
Self {
_phantom: PhantomData,
}
}
}
pub trait TimePrimitive {
type Integer: From<i32>
+ Copy
+ Mul<Output = Self::Integer>
+ Div<Output = Self::Integer>
+ Rem<Output = Self::Integer>
+ Display;
const SCALE: usize;
const PRECISION_FACTOR: Self::Integer;
const STR_LEN: usize;
fn insert_at(index: usize, from: Self::Integer, to: &mut TextColumnSliceMut<u8>) {
let sixty: Self::Integer = 60.into();
let unit_min = sixty * Self::PRECISION_FACTOR;
let unit_hour = unit_min * sixty;
let hour = from / unit_hour;
let minute = (from % unit_hour) / unit_min;
let second = (from % unit_min) / Self::PRECISION_FACTOR;
let fraction = from % Self::PRECISION_FACTOR;
write!(
to.set_mut(index, Self::STR_LEN),
"{hour:02}:{minute:02}:{second:02}.{fraction:0s$}",
s = Self::SCALE
)
.unwrap();
}
}
impl TimePrimitive for Time32MillisecondType {
type Integer = i32;
const SCALE: usize = 3;
const PRECISION_FACTOR: i32 = 1_000;
const STR_LEN: usize = 12;
}
impl TimePrimitive for Time64MicrosecondType {
type Integer = i64;
const SCALE: usize = 6;
const PRECISION_FACTOR: i64 = 1_000_000;
const STR_LEN: usize = 15;
}
impl TimePrimitive for Time64NanosecondType {
type Integer = i64;
const SCALE: usize = 9;
const PRECISION_FACTOR: i64 = 1_000_000_000;
const STR_LEN: usize = 18;
}
impl<P> WriteStrategy for NullableTimeAsText<P>
where
P: ArrowPrimitiveType + TimePrimitive<Integer = <P as ArrowPrimitiveType>::Native>,
{
fn buffer_desc(&self) -> BindParamDesc {
BindParamDesc::text(P::STR_LEN)
}
fn write_rows(
&self,
param_offset: usize,
column_buf: AnySliceMut<'_>,
array: &dyn Array,
) -> Result<(), WriterError> {
let from = array.as_any().downcast_ref::<PrimitiveArray<P>>().unwrap();
let mut to = column_buf.as_text_view().unwrap();
for (index, elapsed_since_midnight) in from.iter().enumerate() {
if let Some(from) = elapsed_since_midnight {
P::insert_at(index + param_offset, from, &mut to)
} else {
to.set_cell(index + param_offset, None)
}
}
Ok(())
}
}