Skip to content

Commit

Permalink
msql fixed a bug in tz-aware datetime decoding
Browse files Browse the repository at this point in the history
went undetected because of chronotope/chrono#1211
  • Loading branch information
lovasoa committed Aug 1, 2023
1 parent 47238cb commit 50571c6
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 9 deletions.
27 changes: 20 additions & 7 deletions sqlx-core/src/mssql/types/chrono.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,14 @@ impl Type<Mssql> for NaiveDateTime {
}

fn compatible(ty: &MssqlTypeInfo) -> bool {
matches!(ty.0.ty, DataType::DateTime2N)
matches!(
ty.0.ty,
DataType::DateTime2N
| DataType::DateTimeOffsetN
| DataType::DateTime
| DataType::DateTimeN
| DataType::SmallDateTime
)
}
}

Expand Down Expand Up @@ -151,9 +158,9 @@ where
{
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
buf.extend_from_slice(&encode_date_time2(&self.naive_utc()));
let from_utc = self.offset().fix().local_minus_utc();
let seconds_from_utc = self.offset().fix().local_minus_utc();
let mut encoded_offset: [u8; 2] = [0, 0];
LittleEndian::write_i16(&mut encoded_offset, (from_utc / 60) as i16);
LittleEndian::write_i16(&mut encoded_offset, (seconds_from_utc / 60) as i16);
buf.extend_from_slice(&encoded_offset);
IsNull::No
}
Expand Down Expand Up @@ -221,7 +228,9 @@ impl Decode<'_, Mssql> for NaiveDateTime {
let bytes = value.as_bytes()?;
match value.type_info.0.ty {
DataType::DateTime2N => decode_datetime2(value.type_info.0.scale, bytes),
_ => Err("unsupported datetime type".into()),
_ => {
<DateTime<FixedOffset> as Decode<'_, Mssql>>::decode(value).map(|d| d.naive_local())
}
}
}
}
Expand Down Expand Up @@ -263,9 +272,13 @@ impl Decode<'_, Mssql> for DateTime<FixedOffset> {

fn decode_datetimeoffset(scale: u8, bytes: &[u8]) -> Result<DateTime<FixedOffset>, BoxDynError> {
let naive = decode_datetime2(scale, &bytes[..bytes.len() - 2])?;
let offset = LittleEndian::read_i16(&bytes[bytes.len() - 2..]);
let offset_parsed = FixedOffset::east_opt(i32::from(offset)).ok_or_else(|| {
Box::new(err_protocol!("invalid offset {} in DateTime2N", offset)) as BoxDynError
let offset_mins = LittleEndian::read_i16(&bytes[bytes.len() - 2..]);
let offset_secs = i32::from(offset_mins) * 60;
let offset_parsed = FixedOffset::east_opt(offset_secs).ok_or_else(|| {
Box::new(err_protocol!(
"invalid offset {} in DateTime2N",
offset_secs
)) as BoxDynError
})?;
Ok(DateTime::from_utc(naive, offset_parsed))
}
11 changes: 9 additions & 2 deletions tests/mssql/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ test_type!(null_varbinary<Option<Vec<u8>>>(Mssql,
#[cfg(feature = "chrono")]
mod chrono {
use super::*;
use sqlx_core::types::chrono::NaiveTime;
use sqlx_core::types::chrono::{FixedOffset, NaiveTime};
use sqlx_oldapi::types::chrono::{DateTime, NaiveDate, NaiveDateTime};

test_type!(smalldatetime_type<DateTime<_>>(
Expand All @@ -114,14 +114,21 @@ mod chrono {
.fixed_offset()
));

test_type!(old_datetime_type_as_naive<NaiveDateTime>(
Mssql,
"CAST('1901-05-08 23:58:59' as DateTime)"
== NaiveDateTime::parse_from_str("1901-05-08 23:58:59", "%Y-%m-%d %H:%M:%S")
.unwrap()
));

test_type!(datetime2<NaiveDateTime>(
Mssql,
"CAST('2016-10-23 12:45:37.1234567' as DateTime2)"
== NaiveDateTime::parse_from_str("2016-10-23 12:45:37.1234567", "%Y-%m-%d %H:%M:%S%.f")
.unwrap()
));

test_type!(DateTime<_>(
test_type!(datetimeoffset<DateTime<FixedOffset>>(
Mssql,
"CAST('2016-10-23 12:45:37.1234567 +02:00' as datetimeoffset(7))" == DateTime::parse_from_rfc3339("2016-10-23T12:45:37.1234567+02:00").unwrap()
));
Expand Down

0 comments on commit 50571c6

Please sign in to comment.