From dbc9f68758b45aa91854e17240e2b2128dc886a0 Mon Sep 17 00:00:00 2001 From: Tobias Rapp Date: Fri, 3 Nov 2023 14:02:31 +0100 Subject: [PATCH] Apply timezone when mapping from Timestamp to chrono Date and DateTime Update FromSql implementation of Date, Date, DateTime, and DateTime to map the incoming timezone information, when available. To explicitly ignore the timezone information use of the NaiveDate / NaiveDateTime types would be preferred. --- src/sql_type/chrono.rs | 28 ++++++++++++++++++++++++---- tests/from_to_sql.rs | 26 ++++++++++++++++++-------- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/src/sql_type/chrono.rs b/src/sql_type/chrono.rs index e1fbc0ed..3006afc6 100644 --- a/src/sql_type/chrono.rs +++ b/src/sql_type/chrono.rs @@ -64,14 +64,24 @@ where impl FromSql for DateTime { fn from_sql(val: &SqlValue) -> Result> { let ts = val.to_timestamp()?; - datetime_from_sql(&Utc, &ts) + if ts.with_tz() { + datetime_from_sql(&fixed_offset_from_sql(&ts)?, &ts) + .map(|val| val.with_timezone(&Utc)) + } else { + datetime_from_sql(&Utc, &ts) + } } } impl FromSql for DateTime { fn from_sql(val: &SqlValue) -> Result> { let ts = val.to_timestamp()?; - datetime_from_sql(&Local, &ts) + if ts.with_tz() { + datetime_from_sql(&fixed_offset_from_sql(&ts)?, &ts) + .map(|val| val.with_timezone(&Local)) + } else { + datetime_from_sql(&Local, &ts) + } } } @@ -144,7 +154,12 @@ where impl FromSql for Date { fn from_sql(val: &SqlValue) -> Result> { let ts = val.to_timestamp()?; - date_from_sql(&Utc, &ts) + if ts.with_tz() { + date_from_sql(&fixed_offset_from_sql(&ts)?, &ts) + .map(|val| val.with_timezone(&Utc)) + } else { + date_from_sql(&Utc, &ts) + } } } @@ -152,7 +167,12 @@ impl FromSql for Date { impl FromSql for Date { fn from_sql(val: &SqlValue) -> Result> { let ts = val.to_timestamp()?; - date_from_sql(&Local, &ts) + if ts.with_tz() { + date_from_sql(&fixed_offset_from_sql(&ts)?, &ts) + .map(|val| val.with_timezone(&Local)) + } else { + date_from_sql(&Local, &ts) + } } } diff --git a/tests/from_to_sql.rs b/tests/from_to_sql.rs index 98dafec7..880a393d 100644 --- a/tests/from_to_sql.rs +++ b/tests/from_to_sql.rs @@ -887,17 +887,27 @@ mod chrono { &dttm ); - // TIMESTAMP WITH TIME ZONE -> DateTime TZ is ignored. - let dttm = Utc.ymd(2012, 3, 4).and_hms_nano(5, 6, 7, 123456789); + // TIMESTAMP WITH TIME ZONE -> DateTime TZ is mapped. + let dttm = Utc.ymd(2012, 3, 4).and_hms_nano(4, 6, 7, 123456789); test_from_sql!(&conn, "TO_TIMESTAMP_TZ('2012-03-04 05:06:07.123456789 +01:00', 'YYYY-MM-DD HH24:MI:SS.FF9 TZH:TZM')", &OracleType::TimestampTZ(9), &dttm); - // TIMESTAMP WITH TIME ZONE -> DateTime TZ is ignored. + // TIMESTAMP WITH TIME ZONE -> DateTime TZ is mapped. let dttm = Local.ymd(2012, 3, 4).and_hms_nano(5, 6, 7, 123456789); - test_from_sql!(&conn, - "TO_TIMESTAMP_TZ('2012-03-04 05:06:07.123456789 +01:00', 'YYYY-MM-DD HH24:MI:SS.FF9 TZH:TZM')", - &OracleType::TimestampTZ(9), &dttm); + let tz_offset = dttm.offset().fix().local_minus_utc(); + let tz_sign = if tz_offset >= 0 { '+' } else { '-' }; + let tz_hour = tz_offset.abs() / 3600; + let tz_min = tz_offset.abs() % 3600 / 60; + test_from_sql!( + &conn, + &format!( + "TO_TIMESTAMP_TZ('2012-03-04 05:06:07.123456789 {}{:02}:{:02}', 'YYYY-MM-DD HH24:MI:SS.FF9 TZH:TZM')", + tz_sign, tz_hour, tz_min + ), + &OracleType::TimestampTZ(9), + &dttm + ); // TIMESTAMP WITH TIME ZONE -> DateTime TZ is set. let dttm = fixed_cet.ymd(2012, 3, 4).and_hms_nano(5, 6, 7, 123456789); @@ -1015,13 +1025,13 @@ mod chrono { &dttm ); - // TIMESTAMP WITH TIME ZONE -> Date TZ is ignored. + // TIMESTAMP WITH TIME ZONE -> Date TZ is mapped. let dttm = Utc.ymd(2012, 3, 4); test_from_sql!(&conn, "TO_TIMESTAMP_TZ('2012-03-04 05:06:07.123456789 +01:00', 'YYYY-MM-DD HH24:MI:SS.FF9 TZH:TZM')", &OracleType::TimestampTZ(9), &dttm); - // TIMESTAMP WITH TIME ZONE -> Date TZ is ignored. + // TIMESTAMP WITH TIME ZONE -> Date TZ is mapped. let dttm = Local.ymd(2012, 3, 4); test_from_sql!(&conn, "TO_TIMESTAMP_TZ('2012-03-04 05:06:07.123456789 +01:00', 'YYYY-MM-DD HH24:MI:SS.FF9 TZH:TZM')",