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 108 109 110 111 112 113 114
use std::error::Error; use std::io::Write; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use pg::Pg; use types::{self, ToSql, FromSql, IsNull}; expression_impls! { Timestamp -> SystemTime, } queryable_impls! { Timestamp -> SystemTime, } fn pg_epoch() -> SystemTime { let thirty_years = Duration::from_secs(946684800); UNIX_EPOCH + thirty_years } impl ToSql<types::Timestamp, Pg> for SystemTime { fn to_sql<W: Write>(&self, out: &mut W) -> Result<IsNull, Box<Error+Send+Sync>> { 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::<types::BigInt, Pg>::to_sql(&time_since_epoch, out) } } impl FromSql<types::Timestamp, Pg> for SystemTime { fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<Error+Send+Sync>> { let usecs_passed = try!(<i64 as FromSql<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_nanos() / NANO_PER_USEC; seconds + subseconds as u64 } #[cfg(test)] mod tests { extern crate dotenv; use self::dotenv::dotenv; use std::time::{SystemTime, Duration, UNIX_EPOCH}; use ::select; use expression::dsl::{sql, now}; use pg::PgConnection; use prelude::*; use types::Timestamp; fn connection() -> PgConnection { dotenv().ok(); let connection_url = ::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()); } }