1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
use std::io::Write; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use deserialize::{self, FromSql}; use pg::Pg; use serialize::{self, Output, ToSql}; use sql_types; fn pg_epoch() -> SystemTime { let thirty_years = Duration::from_secs(946_684_800); UNIX_EPOCH + thirty_years } impl ToSql<sql_types::Timestamp, Pg> for SystemTime { fn to_sql<W: Write>(&self, out: &mut Output<W, Pg>) -> serialize::Result { let (before_epoch, duration) = match self.duration_since(pg_epoch()) { Ok(duration) => (false, duration), Err(time_err) => (true, time_err.duration()), }; let time_since_epoch = if before_epoch { -(duration_to_usecs(duration) as i64) } else { duration_to_usecs(duration) as i64 }; ToSql::<sql_types::BigInt, Pg>::to_sql(&time_since_epoch, out) } } impl FromSql<sql_types::Timestamp, Pg> for SystemTime { fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> { let usecs_passed = <i64 as FromSql<sql_types::BigInt, Pg>>::from_sql(bytes)?; let before_epoch = usecs_passed < 0; let time_passed = usecs_to_duration(usecs_passed.abs() as u64); if before_epoch { Ok(pg_epoch() - time_passed) } else { Ok(pg_epoch() + time_passed) } } } const USEC_PER_SEC: u64 = 1_000_000; const NANO_PER_USEC: u32 = 1_000; fn usecs_to_duration(usecs_passed: u64) -> Duration { let usecs_passed = usecs_passed; let seconds = usecs_passed / USEC_PER_SEC; let subsecond_usecs = usecs_passed % USEC_PER_SEC; let subseconds = subsecond_usecs as u32 * NANO_PER_USEC; Duration::new(seconds, subseconds) } fn duration_to_usecs(duration: Duration) -> u64 { let seconds = duration.as_secs() * USEC_PER_SEC; let subseconds = duration.subsec_micros(); seconds + u64::from(subseconds) } #[cfg(test)] mod tests { extern crate dotenv; use self::dotenv::dotenv; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use dsl::{now, sql}; use prelude::*; use select; use sql_types::Timestamp; fn connection() -> PgConnection { dotenv().ok(); let connection_url = ::std::env::var("PG_DATABASE_URL") .or_else(|_| ::std::env::var("DATABASE_URL")) .expect("DATABASE_URL must be set in order to run tests"); PgConnection::establish(&connection_url).unwrap() } #[test] fn unix_epoch_encodes_correctly() { let connection = connection(); let query = select(sql::<Timestamp>("'1970-01-01'").eq(UNIX_EPOCH)); assert!(query.get_result::<bool>(&connection).unwrap()); } #[test] fn unix_epoch_decodes_correctly() { let connection = connection(); let epoch_from_sql = select(sql::<Timestamp>("'1970-01-01'::timestamp")) .get_result::<SystemTime>(&connection); assert_eq!(Ok(UNIX_EPOCH), epoch_from_sql); } #[test] fn times_relative_to_now_encode_correctly() { let connection = connection(); let time = SystemTime::now() + Duration::from_secs(60); let query = select(now.at_time_zone("utc").lt(time)); assert!(query.get_result::<bool>(&connection).unwrap()); let time = SystemTime::now() - Duration::from_secs(60); let query = select(now.at_time_zone("utc").gt(time)); assert!(query.get_result::<bool>(&connection).unwrap()); } }