2020-11-22 16:34:03 +08:00
//! [`ToSql`] and [`FromSql`] implementation for [`time::OffsetDateTime`].
2018-10-31 03:11:35 +08:00
use crate ::types ::{ FromSql , FromSqlError , FromSqlResult , ToSql , ToSqlOutput , ValueRef } ;
2021-06-13 00:29:01 +08:00
use crate ::{ Error , Result } ;
use time ::format_description ::well_known ::Rfc3339 ;
use time ::format_description ::{ modifier , Component , FormatItem } ;
use time ::{ OffsetDateTime , PrimitiveDateTime , UtcOffset } ;
const DATE_FORMAT : & [ FormatItem < '_ > ] = & [
FormatItem ::Component ( Component ::Year ( modifier ::Year {
repr : modifier ::YearRepr ::Full ,
iso_week_based : false ,
sign_is_mandatory : false ,
padding : modifier ::Padding ::Zero ,
} ) ) ,
FormatItem ::Literal ( b " - " ) ,
FormatItem ::Component ( Component ::Month ( modifier ::Month {
repr : modifier ::MonthRepr ::Numerical ,
padding : modifier ::Padding ::Zero ,
} ) ) ,
FormatItem ::Literal ( b " - " ) ,
FormatItem ::Component ( Component ::Day ( modifier ::Day {
padding : modifier ::Padding ::Zero ,
} ) ) ,
] ;
const SHORT_TIME_FORMAT : & [ FormatItem < '_ > ] = & [
FormatItem ::Component ( Component ::Hour ( modifier ::Hour {
padding : modifier ::Padding ::Zero ,
is_12_hour_clock : false ,
} ) ) ,
FormatItem ::Literal ( b " : " ) ,
FormatItem ::Component ( Component ::Minute ( modifier ::Minute {
padding : modifier ::Padding ::Zero ,
} ) ) ,
FormatItem ::Literal ( b " : " ) ,
FormatItem ::Component ( Component ::Second ( modifier ::Second {
padding : modifier ::Padding ::Zero ,
} ) ) ,
] ;
const TIME_FORMAT : & [ FormatItem < '_ > ] = & [
FormatItem ::Compound ( SHORT_TIME_FORMAT ) ,
FormatItem ::Literal ( b " . " ) ,
FormatItem ::Component ( Component ::Subsecond ( modifier ::Subsecond {
digits : modifier ::SubsecondDigits ::OneOrMore , // TODO SQLite supports ZeroOrMore
} ) ) ,
] ;
const LEGACY_TIME_FORMAT : & [ FormatItem < '_ > ] = & [
FormatItem ::Compound ( SHORT_TIME_FORMAT ) ,
FormatItem ::Literal ( b " : " ) , // legacy
FormatItem ::Component ( Component ::Subsecond ( modifier ::Subsecond {
digits : modifier ::SubsecondDigits ::OneOrMore ,
} ) ) ,
] ;
const OFFSET_FORMAT : & [ FormatItem < '_ > ] = & [
FormatItem ::Component ( Component ::OffsetHour ( modifier ::OffsetHour {
sign_is_mandatory : true ,
padding : modifier ::Padding ::Zero ,
} ) ) ,
FormatItem ::Literal ( b " : " ) ,
FormatItem ::Component ( Component ::OffsetMinute ( modifier ::OffsetMinute {
padding : modifier ::Padding ::Zero ,
} ) ) ,
] ;
const PRIMITIVE_SHORT_DATE_TIME_FORMAT : & [ FormatItem < '_ > ] = & [
FormatItem ::Compound ( DATE_FORMAT ) ,
FormatItem ::Literal ( b " " ) , // TODO "T"
FormatItem ::Compound ( SHORT_TIME_FORMAT ) ,
] ;
const PRIMITIVE_DATE_TIME_FORMAT : & [ FormatItem < '_ > ] = & [
FormatItem ::Compound ( DATE_FORMAT ) ,
FormatItem ::Literal ( b " " ) , // TODO "T"
FormatItem ::Compound ( TIME_FORMAT ) ,
] ;
const PRIMITIVE_DATE_TIME_Z_FORMAT : & [ FormatItem < '_ > ] = & [
FormatItem ::Compound ( DATE_FORMAT ) ,
FormatItem ::Literal ( b " " ) , // TODO "T"
FormatItem ::Compound ( TIME_FORMAT ) ,
FormatItem ::Literal ( b " Z " ) , // TODO "T"
] ;
const OFFSET_SHORT_DATE_TIME_FORMAT : & [ FormatItem < '_ > ] = & [
FormatItem ::Compound ( DATE_FORMAT ) ,
FormatItem ::Literal ( b " " ) , // TODO "T"
FormatItem ::Compound ( SHORT_TIME_FORMAT ) ,
//FormatItem::Literal(b" "), optional
FormatItem ::Compound ( OFFSET_FORMAT ) ,
] ;
const OFFSET_DATE_TIME_FORMAT : & [ FormatItem < '_ > ] = & [
FormatItem ::Compound ( DATE_FORMAT ) ,
FormatItem ::Literal ( b " " ) , // TODO "T"
FormatItem ::Compound ( TIME_FORMAT ) ,
// FormatItem::Literal(b" "), optional
FormatItem ::Compound ( OFFSET_FORMAT ) ,
] ;
const LEGACY_DATE_TIME_FORMAT : & [ FormatItem < '_ > ] = & [
FormatItem ::Compound ( DATE_FORMAT ) ,
FormatItem ::Literal ( b " " ) , // TODO "T"
FormatItem ::Compound ( LEGACY_TIME_FORMAT ) ,
FormatItem ::Literal ( b " " ) ,
FormatItem ::Compound ( OFFSET_FORMAT ) ,
] ;
2016-02-26 01:44:53 +08:00
2020-07-11 23:59:29 +08:00
impl ToSql for OffsetDateTime {
2020-11-04 11:10:23 +08:00
#[ inline ]
2018-12-08 04:57:04 +08:00
fn to_sql ( & self ) -> Result < ToSqlOutput < '_ > > {
2021-06-12 03:42:02 +08:00
// FIXME keep original offset
2021-06-12 18:04:10 +08:00
let time_string = self
. to_offset ( UtcOffset ::UTC )
2021-06-13 00:29:01 +08:00
. format ( & PRIMITIVE_DATE_TIME_Z_FORMAT )
. map_err ( | err | Error ::ToSqlConversionFailure ( err . into ( ) ) ) ? ;
2016-05-26 10:57:43 +08:00
Ok ( ToSqlOutput ::from ( time_string ) )
2016-02-26 01:44:53 +08:00
}
}
2020-07-11 23:59:29 +08:00
impl FromSql for OffsetDateTime {
2018-12-08 04:57:04 +08:00
fn column_result ( value : ValueRef < '_ > ) -> FromSqlResult < Self > {
2020-07-11 23:59:29 +08:00
value . as_str ( ) . and_then ( | s | {
2021-06-13 00:29:01 +08:00
let s = s . strip_suffix ( 'Z' ) . unwrap_or ( s ) ;
2020-07-11 23:59:29 +08:00
match s . len ( ) {
2021-06-12 18:04:10 +08:00
len if len < = 19 = > {
// TODO YYYY-MM-DDTHH:MM:SS
2021-06-13 00:29:01 +08:00
PrimitiveDateTime ::parse ( s , & PRIMITIVE_SHORT_DATE_TIME_FORMAT )
. map ( | d | d . assume_utc ( ) )
2021-06-12 18:04:10 +08:00
}
_ if s . as_bytes ( ) [ 10 ] = = b 'T' = > {
// YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM
2021-06-13 00:29:01 +08:00
OffsetDateTime ::parse ( s , & Rfc3339 )
2021-06-12 18:04:10 +08:00
}
_ if s . as_bytes ( ) [ 19 ] = = b ':' = > {
// legacy
2021-06-13 00:29:01 +08:00
OffsetDateTime ::parse ( s , & LEGACY_DATE_TIME_FORMAT )
2021-06-12 18:04:10 +08:00
}
2021-06-13 00:29:01 +08:00
_ if s . as_bytes ( ) [ 19 ] = = b '.' = > OffsetDateTime ::parse ( s , & OFFSET_DATE_TIME_FORMAT )
. or_else ( | err | {
PrimitiveDateTime ::parse ( s , & PRIMITIVE_DATE_TIME_FORMAT )
2021-06-12 18:04:10 +08:00
. map ( | d | d . assume_utc ( ) )
. map_err ( | _ | err )
2021-06-13 00:29:01 +08:00
} ) ,
_ = > OffsetDateTime ::parse ( s , & OFFSET_SHORT_DATE_TIME_FORMAT ) ,
2020-07-11 23:59:29 +08:00
}
. map_err ( | err | FromSqlError ::Other ( Box ::new ( err ) ) )
} )
2016-05-16 11:30:11 +08:00
}
2016-02-26 01:44:53 +08:00
}
#[ cfg(test) ]
mod test {
2020-11-03 17:32:46 +08:00
use crate ::{ Connection , Result } ;
2021-06-13 00:29:01 +08:00
use time ::{ Date , Month , OffsetDateTime , Time , UtcOffset } ;
2016-02-26 01:44:53 +08:00
2020-11-06 05:14:00 +08:00
fn checked_memory_handle ( ) -> Result < Connection > {
let db = Connection ::open_in_memory ( ) ? ;
db . execute_batch ( " CREATE TABLE foo (t TEXT, i INTEGER, f FLOAT) " ) ? ;
Ok ( db )
2016-02-26 01:44:53 +08:00
}
#[ test ]
2020-11-06 05:14:00 +08:00
fn test_offset_date_time ( ) -> Result < ( ) > {
let db = checked_memory_handle ( ) ? ;
2016-02-26 01:44:53 +08:00
2017-07-18 23:46:49 +08:00
let mut ts_vec = vec! [ ] ;
2021-06-13 00:29:01 +08:00
let make_datetime = | secs : i128 , nanos : i128 | {
OffsetDateTime ::from_unix_timestamp_nanos ( 1_000_000_000 * secs + nanos ) . unwrap ( )
} ;
2020-07-11 23:59:29 +08:00
ts_vec . push ( make_datetime ( 10_000 , 0 ) ) ; //January 1, 1970 2:46:40 AM
ts_vec . push ( make_datetime ( 10_000 , 1000 ) ) ; //January 1, 1970 2:46:40 AM (and one microsecond)
ts_vec . push ( make_datetime ( 1_500_391_124 , 1_000_000 ) ) ; //July 18, 2017
ts_vec . push ( make_datetime ( 2_000_000_000 , 2_000_000 ) ) ; //May 18, 2033
ts_vec . push ( make_datetime ( 3_000_000_000 , 999_999_999 ) ) ; //January 24, 2065
ts_vec . push ( make_datetime ( 10_000_000_000 , 0 ) ) ; //November 20, 2286
2017-07-18 23:46:49 +08:00
for ts in ts_vec {
2021-01-20 04:16:08 +08:00
db . execute ( " INSERT INTO foo(t) VALUES (?) " , [ ts ] ) ? ;
2017-07-18 23:46:49 +08:00
2020-11-06 05:14:00 +08:00
let from : OffsetDateTime = db . query_row ( " SELECT t FROM foo " , [ ] , | r | r . get ( 0 ) ) ? ;
2017-07-18 23:46:49 +08:00
2020-11-06 05:14:00 +08:00
db . execute ( " DELETE FROM foo " , [ ] ) ? ;
2017-07-18 23:46:49 +08:00
assert_eq! ( from , ts ) ;
}
2020-11-06 05:14:00 +08:00
Ok ( ( ) )
2016-02-26 01:44:53 +08:00
}
2018-11-22 23:50:10 +08:00
2021-06-12 03:42:02 +08:00
#[ test ]
fn test_string_values ( ) -> Result < ( ) > {
let db = checked_memory_handle ( ) ? ;
2021-06-12 18:04:10 +08:00
for ( s , t ) in vec! [
(
" 2013-10-07 08:23:19.120 " ,
2021-06-13 00:29:01 +08:00
Ok ( Date ::from_calendar_date ( 2013 , Month ::October , 7 )
. unwrap ( )
. with_time ( Time ::from_hms_milli ( 8 , 23 , 19 , 120 ) . unwrap ( ) )
2021-06-12 18:04:10 +08:00
. assume_utc ( ) ) ,
) ,
(
" 2013-10-07 08:23:19.120Z " ,
2021-06-13 00:29:01 +08:00
Ok ( Date ::from_calendar_date ( 2013 , Month ::October , 7 )
. unwrap ( )
. with_time ( Time ::from_hms_milli ( 8 , 23 , 19 , 120 ) . unwrap ( ) )
2021-06-12 18:04:10 +08:00
. assume_utc ( ) ) ,
) ,
2021-06-12 03:42:02 +08:00
//"2013-10-07T08:23:19.120Z", // TODO
2021-06-12 18:04:10 +08:00
(
" 2013-10-07 04:23:19.120-04:00 " ,
2021-06-13 00:29:01 +08:00
Ok ( Date ::from_calendar_date ( 2013 , Month ::October , 7 )
. unwrap ( )
. with_time ( Time ::from_hms_milli ( 4 , 23 , 19 , 120 ) . unwrap ( ) )
. assume_offset ( UtcOffset ::from_hms ( - 4 , 0 , 0 ) . unwrap ( ) ) ) ,
2021-06-12 18:04:10 +08:00
) ,
2021-06-12 03:42:02 +08:00
] {
let result : Result < OffsetDateTime > = db . query_row ( " SELECT ? " , [ s ] , | r | r . get ( 0 ) ) ;
2021-06-12 18:04:10 +08:00
assert_eq! ( result , t ) ;
2021-06-12 03:42:02 +08:00
}
Ok ( ( ) )
}
2018-11-22 23:50:10 +08:00
#[ test ]
2020-11-06 05:14:00 +08:00
fn test_sqlite_functions ( ) -> Result < ( ) > {
let db = checked_memory_handle ( ) ? ;
2020-07-11 23:59:29 +08:00
let result : Result < OffsetDateTime > =
2020-11-03 17:32:46 +08:00
db . query_row ( " SELECT CURRENT_TIMESTAMP " , [ ] , | r | r . get ( 0 ) ) ;
2018-11-22 23:50:10 +08:00
assert! ( result . is_ok ( ) ) ;
2020-11-06 05:14:00 +08:00
Ok ( ( ) )
2018-11-22 23:50:10 +08:00
}
2021-01-30 05:03:50 +08:00
#[ test ]
fn test_param ( ) -> Result < ( ) > {
let db = checked_memory_handle ( ) ? ;
2021-01-30 19:55:00 +08:00
let result : Result < bool > = db . query_row ( " SELECT 1 WHERE ? BETWEEN datetime('now', '-1 minute') AND datetime('now', '+1 minute') " , [ OffsetDateTime ::now_utc ( ) ] , | r | r . get ( 0 ) ) ;
2021-01-30 05:03:50 +08:00
assert! ( result . is_ok ( ) ) ;
Ok ( ( ) )
}
2016-02-26 01:44:53 +08:00
}