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 } ;
use crate ::Result ;
2021-06-12 18:04:10 +08:00
use time ::{ Format , OffsetDateTime , PrimitiveDateTime , UtcOffset } ;
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 )
. format ( " %Y-%m-%d %H:%M:%S.%NZ " ) ;
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 | {
match s . len ( ) {
2021-06-12 18:04:10 +08:00
len if len < = 10 = > PrimitiveDateTime ::parse ( s , " %Y-%m-%d " ) . map ( | d | d . assume_utc ( ) ) ,
len if len < = 19 = > {
// TODO YYYY-MM-DDTHH:MM:SS
PrimitiveDateTime ::parse ( s , " %Y-%m-%d %H:%M:%S " ) . map ( | d | d . assume_utc ( ) )
}
_ if s . ends_with ( 'Z' ) = > {
// TODO YYYY-MM-DDTHH:MM:SS.SSS
// FIXME time bug: %N specifier doesn't parse millis correctly (https://github.com/time-rs/time/issues/329)
PrimitiveDateTime ::parse ( s , " %Y-%m-%d %H:%M:%S.%NZ " ) . map ( | d | d . assume_utc ( ) )
}
_ if s . as_bytes ( ) [ 10 ] = = b 'T' = > {
// YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM
OffsetDateTime ::parse ( s , Format ::Rfc3339 )
}
_ if s . as_bytes ( ) [ 19 ] = = b ':' = > {
// legacy
// FIXME time bug: %N specifier doesn't parse millis correctly (https://github.com/time-rs/time/issues/329)
OffsetDateTime ::parse ( s , " %Y-%m-%d %H:%M:%S:%N %z " )
}
2021-06-12 03:42:02 +08:00
_ = > {
2021-06-12 18:04:10 +08:00
// FIXME time bug: %N specifier doesn't parse millis correctly (https://github.com/time-rs/time/issues/329)
// FIXME time bug: %z does not support ':' (https://github.com/time-rs/time/issues/241)
OffsetDateTime ::parse ( s , " %Y-%m-%d %H:%M:%S.%N%z " ) . or_else ( | err | {
PrimitiveDateTime ::parse ( s , " %Y-%m-%d %H:%M:%S.%N " )
. map ( | d | d . assume_utc ( ) )
. map_err ( | _ | err )
} )
2021-06-12 03:42:02 +08:00
}
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 } ;
2020-07-11 23:59:29 +08:00
use std ::time ::Duration ;
2021-06-12 18:04:10 +08:00
use time ::{ date , offset , OffsetDateTime , Time } ;
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! [ ] ;
2020-07-11 23:59:29 +08:00
let make_datetime =
| secs , nanos | OffsetDateTime ::from_unix_timestamp ( secs ) + Duration ::from_nanos ( nanos ) ;
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 " ,
Ok ( date! ( 2013 - 10 - 07 )
. with_time (
Time ::/* FIXME time bug try_from_hms_milli */ try_from_hms_nano (
8 , 23 , 19 , 120 ,
)
. unwrap ( ) ,
)
. assume_utc ( ) ) ,
) ,
(
" 2013-10-07 08:23:19.120Z " ,
Ok ( date! ( 2013 - 10 - 07 )
. with_time (
Time ::/* FIXME time bug try_from_hms_milli */ try_from_hms_nano (
8 , 23 , 19 , 120 ,
)
. unwrap ( ) ,
)
. 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 " ,
Ok ( date! ( 2013 - 10 - 07 )
. with_time (
Time ::/* FIXME time bug try_from_hms_milli */ try_from_hms_nano (
4 , 23 , 19 , 120 ,
)
. unwrap ( ) ,
)
. assume_offset ( offset! ( - 4 ) ) ) ,
) ,
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
}