use std::time::Duration;
pub use time::{Date, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset};
use crate::tds::codec::ColumnData;
#[inline]
fn from_days(days: i64, start_year: i32) -> Date {
let base = Date::from_calendar_date(start_year, Month::January, 1).unwrap();
base.checked_add(time::Duration::days(days))
.unwrap_or(if days < 0 { Date::MIN } else { Date::MAX })
}
#[inline]
#[cfg(feature = "tds73")]
fn nanos_from_increments(increments: u64, scale: u8) -> u64 {
let pow = 9u32.saturating_sub(scale as u32);
increments.saturating_mul(10u64.saturating_pow(pow))
}
#[inline]
#[cfg(feature = "tds73")]
fn from_secs(secs: u64) -> Time {
Time::from_hms(0, 0, 0).unwrap() + Duration::from_secs(secs)
}
#[inline]
fn from_sec_fragments(sec_fragments: u64) -> Time {
Time::from_hms(0, 0, 0).unwrap() + Duration::from_nanos(sec_fragments * (1e9 as u64) / 300)
}
#[inline]
fn to_days(date: Date, start_year: i32) -> i64 {
(date - Date::from_calendar_date(start_year, Month::January, 1).unwrap()).whole_days()
}
#[inline]
#[cfg(not(feature = "tds73"))]
fn to_sec_fragments(from: Time) -> i64 {
let nanos: i64 = (from - Time::from_hms(0, 0, 0).unwrap())
.whole_nanoseconds()
.try_into()
.unwrap();
nanos * 300 / (1e9 as i64)
}
#[cfg(feature = "tds73")]
from_sql!(
PrimitiveDateTime:
ColumnData::SmallDateTime(ref dt) => dt.map(|dt| PrimitiveDateTime::new(
from_days(dt.days as i64, 1900),
from_secs(dt.seconds_fragments as u64 * 60),
)),
ColumnData::DateTime2(ref dt) => dt.map(|dt| PrimitiveDateTime::new(
from_days(dt.date.days() as i64, 1),
Time::from_hms(0,0,0).unwrap() + Duration::from_nanos(nanos_from_increments(dt.time.increments, dt.time.scale))
)),
ColumnData::DateTime(ref dt) => dt.map(|dt| PrimitiveDateTime::new(
from_days(dt.days as i64, 1900),
from_sec_fragments(dt.seconds_fragments as u64)
));
Time:
ColumnData::Time(ref time) => time.map(|time| {
let ns = nanos_from_increments(time.increments, time.scale);
Time::from_hms(0,0,0).unwrap() + Duration::from_nanos(ns)
});
Date:
ColumnData::Date(ref date) => date.map(|date| from_days(date.days() as i64, 1));
OffsetDateTime:
ColumnData::DateTimeOffset(ref dto) => dto.map(|dto| {
let date = from_days(dto.datetime2.date.days() as i64, 1);
let dt = dto.datetime2;
let time = Time::from_hms(0,0,0).unwrap()
+ Duration::from_nanos(nanos_from_increments(dt.time.increments, dt.time.scale));
let offset = UtcOffset::from_whole_seconds(dto.offset as i32 * 60)
.unwrap_or(UtcOffset::UTC);
date.with_time(time).assume_utc().to_offset(offset)
})
);
#[cfg(feature = "tds73")]
to_sql!(self_,
Date: (ColumnData::Date, super::Date::new(to_days(*self_, 1) as u32));
Time: (ColumnData::Time, {
let nanos: u64 = (*self_ - Time::from_hms(0, 0, 0).unwrap()).whole_nanoseconds().try_into().unwrap();
let increments = nanos / 100;
super::Time {increments, scale: 7}
});
PrimitiveDateTime: (ColumnData::DateTime2, {
let time = self_.time();
let nanos: u64 = (time - Time::from_hms(0, 0, 0).unwrap()).whole_nanoseconds().try_into().unwrap();
let increments = nanos / 100;
let date = super::Date::new(to_days(self_.date(), 1) as u32);
let time = super::Time {increments, scale: 7};
super::DateTime2::new(date, time)
});
OffsetDateTime: (ColumnData::DateTimeOffset, {
let tz = self_.offset();
let offset = (tz.whole_seconds() / 60) as i16;
let utc_date = self_.to_offset(UtcOffset::UTC);
let nanos: u64 = (utc_date.time() - Time::from_hms(0, 0, 0).unwrap()).whole_nanoseconds().try_into().unwrap();
let date = super::Date::new(to_days(utc_date.date(), 1) as u32);
let time = super::Time { increments: nanos / 100, scale: 7 };
super::DateTimeOffset::new(super::DateTime2::new(date, time), offset)
});
);
#[cfg(not(feature = "tds73"))]
to_sql!(self_,
PrimitiveDateTime: (ColumnData::DateTime, {
let date = self_.date();
let time = self_.time();
let days = to_days(date, 1900) as i32;
let seconds_fragments = to_sec_fragments(time);
super::DateTime::new(days, seconds_fragments as u32)
});
);
#[cfg(not(feature = "tds73"))]
from_sql!(
PrimitiveDateTime:
ColumnData::DateTime(ref dt) => dt.map(|dt| {
from_days(dt.days as i64, 1900).with_time(from_sec_fragments(dt.seconds_fragments as u64))
})
);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_days_handles_negative_offsets() {
assert_eq!(
from_days(-1, 1900),
Date::from_calendar_date(1899, Month::December, 31).unwrap()
);
let expected = Date::from_calendar_date(1850, Month::January, 1).unwrap();
let days = to_days(expected, 1900);
assert!(
days < 0,
"expected a negative day offset for pre-1900 dates"
);
assert_eq!(from_days(days, 1900), expected);
}
#[test]
fn datetime_before_1900_decodes() {
let expected_date = Date::from_calendar_date(1850, Month::January, 1).unwrap();
let days = to_days(expected_date, 1900) as i32;
let dt = crate::tds::time::DateTime::new(days, 0);
let decoded = from_days(dt.days() as i64, 1900)
.with_time(from_sec_fragments(dt.seconds_fragments() as u64));
assert_eq!(decoded.date(), expected_date);
assert_eq!(decoded.time(), Time::from_hms(0, 0, 0).unwrap());
}
#[cfg(feature = "tds73")]
#[test]
fn time_from_sql_and_back() {
use crate::FromSql;
let expected = Time::from_hms(12, 34, 56).unwrap();
let nanos: u64 = (expected - Time::from_hms(0, 0, 0).unwrap())
.whole_nanoseconds()
.try_into()
.unwrap();
let increments: u64 = nanos / 100;
let tds_time = super::super::Time::new(increments, 7);
let data = ColumnData::Time(Some(tds_time));
let decoded = Time::from_sql(&data).unwrap().unwrap();
assert_eq!(decoded, expected);
use crate::ToSql;
let round_tripped = decoded.to_sql();
match round_tripped {
ColumnData::Time(Some(t)) => assert_eq!(t.increments, increments),
other => panic!("unexpected: {:?}", other),
}
}
#[cfg(feature = "tds73")]
#[test]
fn date_from_sql_and_back() {
use crate::{FromSql, ToSql};
let expected = Date::from_calendar_date(2020, Month::June, 15).unwrap();
let days = to_days(expected, 1) as u32;
let data = ColumnData::Date(Some(super::super::Date::new(days)));
let decoded = Date::from_sql(&data).unwrap().unwrap();
assert_eq!(decoded, expected);
match decoded.to_sql() {
ColumnData::Date(Some(d)) => assert_eq!(d.days(), days),
other => panic!("unexpected: {:?}", other),
}
}
#[cfg(feature = "tds73")]
#[test]
fn primitive_datetime_from_datetime2_and_back() {
use crate::{FromSql, ToSql};
let date = Date::from_calendar_date(2020, Month::June, 15).unwrap();
let time = Time::from_hms(1, 2, 3).unwrap();
let expected = PrimitiveDateTime::new(date, time);
let days = to_days(date, 1) as u32;
let nanos: u64 = (time - Time::from_hms(0, 0, 0).unwrap())
.whole_nanoseconds()
.try_into()
.unwrap();
let increments = nanos / 100;
let dt2 = super::super::DateTime2::new(
super::super::Date::new(days),
super::super::Time::new(increments, 7),
);
let data = ColumnData::DateTime2(Some(dt2));
let decoded = PrimitiveDateTime::from_sql(&data).unwrap().unwrap();
assert_eq!(decoded, expected);
match decoded.to_sql() {
ColumnData::DateTime2(Some(dt)) => {
assert_eq!(dt.date.days(), days);
}
other => panic!("unexpected: {:?}", other),
}
}
#[cfg(feature = "tds73")]
#[test]
fn primitive_datetime_from_smalldatetime_and_datetime() {
use crate::FromSql;
let sdt = crate::tds::time::SmallDateTime::new(1, 30); let data = ColumnData::SmallDateTime(Some(sdt));
let decoded = PrimitiveDateTime::from_sql(&data).unwrap().unwrap();
assert_eq!(decoded.date(), from_days(1, 1900));
let dt = crate::tds::time::DateTime::new(1, 0);
let data = ColumnData::DateTime(Some(dt));
let decoded = PrimitiveDateTime::from_sql(&data).unwrap().unwrap();
assert_eq!(decoded.date(), from_days(1, 1900));
}
#[cfg(feature = "tds73")]
#[test]
fn offset_date_time_from_sql_and_back() {
use crate::{FromSql, ToSql};
let date = Date::from_calendar_date(2020, Month::June, 15).unwrap();
let time = Time::from_hms(1, 2, 3).unwrap();
let days = to_days(date, 1) as u32;
let nanos: u64 = (time - Time::from_hms(0, 0, 0).unwrap())
.whole_nanoseconds()
.try_into()
.unwrap();
let increments = nanos / 100;
let dt2 = super::super::DateTime2::new(
super::super::Date::new(days),
super::super::Time::new(increments, 7),
);
let dto = super::super::DateTimeOffset::new(dt2, 60);
let data = ColumnData::DateTimeOffset(Some(dto));
let decoded = OffsetDateTime::from_sql(&data).unwrap().unwrap();
assert_eq!(
decoded.offset(),
UtcOffset::from_whole_seconds(3600).unwrap()
);
match decoded.to_sql() {
ColumnData::DateTimeOffset(Some(round_tripped)) => {
assert_eq!(round_tripped.offset, 60);
}
other => panic!("unexpected: {:?}", other),
}
}
#[cfg(feature = "tds73")]
#[test]
fn from_sql_null_variants_return_none_tds73() {
use crate::FromSql;
assert_eq!(Time::from_sql(&ColumnData::Time(None)).unwrap(), None);
assert_eq!(Date::from_sql(&ColumnData::Date(None)).unwrap(), None);
assert_eq!(
OffsetDateTime::from_sql(&ColumnData::DateTimeOffset(None)).unwrap(),
None
);
}
#[test]
fn primitive_datetime_from_sql_null_returns_none() {
use crate::FromSql;
assert_eq!(
PrimitiveDateTime::from_sql(&ColumnData::DateTime(None)).unwrap(),
None
);
}
}