Skip to content

Commit 0d63eeb

Browse files
committed
Add optional chrono support
1 parent 569ff87 commit 0d63eeb

File tree

7 files changed

+247
-7
lines changed

7 files changed

+247
-7
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ uuid = { version = "0.1", optional = true }
3535
unix_socket = { version = "0.3", optional = true }
3636
time = { version = "0.1.14", optional = true }
3737
serde = { version = "0.3", optional = true }
38+
chrono = { version = "0.2", optional = true }
3839

3940
[dev-dependencies]
4041
url = "0.2"

README.md

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,9 +208,34 @@ types. The driver currently supports the following conversions:
208208
<tr>
209209
<td>
210210
<a href="https://github.com/rust-lang/time">time::Timespec</a>
211+
and
212+
<a href="https://github.com/lifthrasiir/rust-chrono">chrono::NaiveDateTime</a>
213+
(<a href="#optional-features">optional</a>)
214+
</td>
215+
<td>TIMESTAMP</td>
216+
</tr>
217+
<tr>
218+
<td>
219+
<a href="https://github.com/rust-lang/time">time::Timespec</a>
220+
and
221+
<a href="https://github.com/lifthrasiir/rust-chrono">chrono::DateTime&lt;UTC&gt;</a>
222+
(<a href="#optional-features">optional</a>)
223+
</td>
224+
<td>TIMESTAMP WITH TIME ZONE</td>
225+
</tr>
226+
<tr>
227+
<td>
228+
<a href="https://github.com/lifthrasiir/rust-chrono">chrono::NaiveDate</a>
229+
(<a href="#optional-features">optional</a>)
230+
</td>
231+
<td>DATE</td>
232+
</tr>
233+
<tr>
234+
<td>
235+
<a href="https://github.com/lifthrasiir/rust-chrono">chrono::NaiveTime</a>
211236
(<a href="#optional-features">optional</a>)
212237
</td>
213-
<td>TIMESTAMP, TIMESTAMP WITH TIME ZONE</td>
238+
<td>TIME</td>
214239
</tr>
215240
<tr>
216241
<td>
@@ -260,8 +285,10 @@ support is provided optionally by the `rustc-serialize` feature, which adds
260285
the `serde` feature, which adds implementations for `serde`'s `json::Value`
261286
type.
262287

263-
### TIMESTAMP/TIMESTAMPTZ types
288+
### TIMESTAMP/TIMESTAMPTZ/DATE/TIME types
264289

265-
[TIMESTAMP and TIMESTAMPTZ](http://www.postgresql.org/docs/9.1/static/datatype-datetime.html)
290+
[Date and Time](http://www.postgresql.org/docs/9.1/static/datatype-datetime.html)
266291
support is provided optionally by the `time` feature, which adds `ToSql` and
267-
`FromSql` implementations for `time`'s `Timespec` type.
292+
`FromSql` implementations for `time`'s `Timespec` type, or the `chrono`
293+
feature, which adds `ToSql` and `FromSql` implementations for `chrono`'s
294+
`DateTime`, `NaiveDateTime`, `NaiveDate` and `NaiveTime` types.

src/types/chrono.rs

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
extern crate chrono;
2+
3+
use std::io::prelude::*;
4+
use byteorder::{ReadBytesExt, WriteBytesExt, BigEndian};
5+
use self::chrono::{Timelike, Datelike, NaiveDate, NaiveTime, NaiveDateTime, DateTime, UTC};
6+
7+
use Result;
8+
use types::{FromSql, ToSql, IsNull, Type};
9+
10+
const USEC_PER_SEC: i64 = 1_000_000;
11+
const NSEC_PER_USEC: i64 = 1_000;
12+
13+
// Number of seconds from 1970-01-01 to 2000-01-01
14+
const TIME_SEC_CONVERSION: i64 = 946684800;
15+
16+
const POSTGRES_EPOCH_JDATE: i32 = 2451545;
17+
18+
fn base() -> NaiveDateTime {
19+
NaiveDate::from_ymd(2000, 1, 1).and_hms(0, 0, 0)
20+
}
21+
22+
impl FromSql for NaiveDateTime {
23+
fn from_sql<R: Read>(_: &Type, raw: &mut R) -> Result<NaiveDateTime> {
24+
// FIXME should be this, blocked on lifthrasiir/rust-chrono#37
25+
/*
26+
let t = try!(raw.read_i64::<BigEndian>());
27+
Ok(base() + Duration::microseconds(t))
28+
*/
29+
30+
let t = try!(raw.read_i64::<BigEndian>());
31+
let mut sec = t / USEC_PER_SEC + TIME_SEC_CONVERSION;
32+
let mut usec = t % USEC_PER_SEC;
33+
34+
if usec < 0 {
35+
sec -= 1;
36+
usec = USEC_PER_SEC + usec;
37+
}
38+
39+
Ok(NaiveDateTime::from_timestamp(sec, (usec * NSEC_PER_USEC) as u32))
40+
}
41+
42+
accepts!(Type::Timestamp);
43+
}
44+
45+
impl ToSql for NaiveDateTime {
46+
fn to_sql<W: Write+?Sized>(&self, _: &Type, mut w: &mut W) -> Result<IsNull> {
47+
let t = (*self - base()).num_microseconds().unwrap();
48+
try!(w.write_i64::<BigEndian>(t));
49+
Ok(IsNull::No)
50+
}
51+
52+
accepts!(Type::Timestamp);
53+
to_sql_checked!();
54+
}
55+
56+
impl FromSql for DateTime<UTC> {
57+
fn from_sql<R: Read>(type_: &Type, raw: &mut R) -> Result<DateTime<UTC>> {
58+
let naive = try!(NaiveDateTime::from_sql(type_, raw));
59+
Ok(DateTime::from_utc(naive, UTC))
60+
}
61+
62+
accepts!(Type::TimestampTZ);
63+
}
64+
65+
impl ToSql for DateTime<UTC> {
66+
fn to_sql<W: Write+?Sized>(&self, type_: &Type, mut w: &mut W) -> Result<IsNull> {
67+
self.naive_utc().to_sql(type_, w)
68+
}
69+
70+
accepts!(Type::TimestampTZ);
71+
to_sql_checked!();
72+
}
73+
74+
impl FromSql for NaiveDate {
75+
// adapted from j2date in src/interfaces/ecpg/pgtypeslib/dt_common.c
76+
fn from_sql<R: Read>(_: &Type, raw: &mut R) -> Result<NaiveDate> {
77+
let jd = try!(raw.read_i32::<BigEndian>());
78+
79+
let mut julian: u32 = (jd + POSTGRES_EPOCH_JDATE) as u32;
80+
julian += 32044;
81+
let mut quad: u32 = julian / 146097;
82+
let extra: u32 = (julian - quad * 146097) * 4 + 3;
83+
julian += 60 + quad * 3 + extra / 146097;
84+
quad = julian / 1461;
85+
julian -= quad * 1461;
86+
let mut y: i32 = (julian * 4 / 1461) as i32;
87+
julian = if y != 0 { (julian + 305) % 365 } else { (julian + 306) % 366 } + 123;
88+
y += (quad * 4) as i32;
89+
let year = y - 4800;
90+
quad = julian * 2141 / 65536;
91+
let day = julian - 7834 * quad / 256;
92+
let month = (quad + 10) % 12 + 1;
93+
94+
Ok(NaiveDate::from_ymd(year, month as u32, day as u32))
95+
}
96+
97+
accepts!(Type::Date);
98+
}
99+
100+
impl ToSql for NaiveDate {
101+
// adapted from date2j in src/interfaces/ecpg/pgtypeslib/dt_common.c
102+
fn to_sql<W: Write+?Sized>(&self, _: &Type, mut w: &mut W) -> Result<IsNull> {
103+
let mut y = self.year();
104+
let mut m = self.month() as i32;
105+
let d = self.day() as i32;
106+
107+
if m > 2 {
108+
m += 1;
109+
y += 4800;
110+
} else {
111+
m += 13;
112+
y += 4799;
113+
}
114+
115+
let century = y / 100;
116+
let mut julian = y * 365 - 32167;
117+
julian += y / 4 - century + century / 4;
118+
julian += 7834 * m / 256 + d;
119+
120+
try!(w.write_i32::<BigEndian>(julian - POSTGRES_EPOCH_JDATE));
121+
Ok(IsNull::No)
122+
}
123+
124+
accepts!(Type::Date);
125+
to_sql_checked!();
126+
}
127+
128+
impl FromSql for NaiveTime {
129+
fn from_sql<R: Read>(_: &Type, raw: &mut R) -> Result<NaiveTime> {
130+
let mut usec = try!(raw.read_i64::<BigEndian>());
131+
let mut sec = usec / USEC_PER_SEC;
132+
usec -= sec * USEC_PER_SEC;
133+
let mut min = sec / 60;
134+
sec -= min * 60;
135+
let hr = min / 60;
136+
min -= hr * 60;
137+
138+
Ok(NaiveTime::from_hms_micro(hr as u32, min as u32, sec as u32, usec as u32))
139+
}
140+
141+
accepts!(Type::Time);
142+
}
143+
144+
impl ToSql for NaiveTime {
145+
fn to_sql<W: Write+?Sized>(&self, _: &Type, mut w: &mut W) -> Result<IsNull> {
146+
let hr = self.hour() as i64;
147+
let min = hr * 60 + self.minute() as i64;
148+
let sec = min * 60 + self.second() as i64;
149+
let usec = sec * USEC_PER_SEC + self.nanosecond() as i64 / NSEC_PER_USEC;
150+
151+
try!(w.write_i64::<BigEndian>(usec));
152+
Ok(IsNull::No)
153+
}
154+
155+
accepts!(Type::Time);
156+
to_sql_checked!();
157+
}

src/types/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ mod slice;
5151
mod rustc_serialize;
5252
#[cfg(feature = "serde")]
5353
mod serde;
54+
#[cfg(feature = "chrono")]
55+
mod chrono;
5456

5557
/// A Postgres OID.
5658
pub type Oid = u32;

tests/types/chrono.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
extern crate chrono;
2+
3+
use self::chrono::{TimeZone, NaiveDate, NaiveTime, NaiveDateTime, DateTime, UTC};
4+
use types::test_type;
5+
6+
#[test]
7+
fn test_naive_date_time_params() {
8+
fn make_check<'a>(time: &'a str) -> (Option<NaiveDateTime>, &'a str) {
9+
(Some(NaiveDateTime::parse_from_str(time, "'%Y-%m-%d %H:%M:%S.%f'").unwrap()), time)
10+
}
11+
test_type("TIMESTAMP",
12+
&[make_check("'1970-01-01 00:00:00.010000000'"),
13+
make_check("'1965-09-25 11:19:33.100314000'"),
14+
make_check("'2010-02-09 23:11:45.120200000'"),
15+
(None, "NULL")]);
16+
}
17+
18+
#[test]
19+
fn test_date_time_params() {
20+
fn make_check<'a>(time: &'a str) -> (Option<DateTime<UTC>>, &'a str) {
21+
(Some(UTC.datetime_from_str(time, "'%Y-%m-%d %H:%M:%S.%f'").unwrap()), time)
22+
}
23+
test_type("TIMESTAMP WITH TIME ZONE",
24+
&[make_check("'1970-01-01 00:00:00.010000000'"),
25+
make_check("'1965-09-25 11:19:33.100314000'"),
26+
make_check("'2010-02-09 23:11:45.120200000'"),
27+
(None, "NULL")]);
28+
}
29+
30+
#[test]
31+
fn test_date_params() {
32+
fn make_check<'a>(time: &'a str) -> (Option<NaiveDate>, &'a str) {
33+
(Some(NaiveDate::parse_from_str(time, "'%Y-%m-%d'").unwrap()), time)
34+
}
35+
test_type("DATE",
36+
&[make_check("'1970-01-01'"),
37+
make_check("'1965-09-25'"),
38+
make_check("'2010-02-09'"),
39+
(None, "NULL")]);
40+
}
41+
42+
#[test]
43+
fn test_time_params() {
44+
fn make_check<'a>(time: &'a str) -> (Option<NaiveTime>, &'a str) {
45+
(Some(NaiveTime::parse_from_str(time, "'%H:%M:%S.%f'").unwrap()), time)
46+
}
47+
test_type("TIME",
48+
&[make_check("'00:00:00.010000000'"),
49+
make_check("'11:19:33.100314000'"),
50+
make_check("'23:11:45.120200000'"),
51+
(None, "NULL")]);
52+
}

tests/types/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,19 @@ mod time;
1414
mod rustc_serialize;
1515
#[cfg(feature = "serde")]
1616
mod serde;
17+
#[cfg(feature = "chrono")]
18+
mod chrono;
1719

1820
fn test_type<T: PartialEq+FromSql+ToSql, S: fmt::Display>(sql_type: &str, checks: &[(T, S)]) {
1921
let conn = or_panic!(Connection::connect("postgres://postgres@localhost", &SslMode::None));
2022
for &(ref val, ref repr) in checks.iter() {
2123
let stmt = or_panic!(conn.prepare(&*format!("SELECT {}::{}", *repr, sql_type)));
2224
let result = or_panic!(stmt.query(&[])).iter().next().unwrap().get(0);
23-
assert!(val == &result);
25+
assert_eq!(val, &result);
2426

2527
let stmt = or_panic!(conn.prepare(&*format!("SELECT $1::{}", sql_type)));
2628
let result = or_panic!(stmt.query(&[val])).iter().next().unwrap().get(0);
27-
assert!(val == &result);
29+
assert_eq!(val, &result);
2830
}
2931
}
3032

tests/types/time.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,3 @@ fn test_tm_params() {
1919
make_check("'2010-02-09 23:11:45.1202'"),
2020
(None, "NULL")]);
2121
}
22-

0 commit comments

Comments
 (0)