@@ -5,6 +5,8 @@ use crate::{
55 ProtocolError ,
66} ;
77use byteorder:: { BigEndian , ByteOrder } ;
8+ #[ cfg( feature = "with-chrono" ) ]
9+ use chrono:: { NaiveDate , NaiveDateTime } ;
810use std:: backtrace:: Backtrace ;
911
1012/// This trait explains how to decode values from the protocol
@@ -122,6 +124,81 @@ impl FromProtocolValue for f64 {
122124 }
123125}
124126
127+ #[ cfg( feature = "with-chrono" ) ]
128+ impl FromProtocolValue for NaiveDateTime {
129+ fn from_text ( raw : & [ u8 ] ) -> Result < Self , ProtocolError > {
130+ let as_str = std:: str:: from_utf8 ( raw) . map_err ( |err| ProtocolError :: ErrorResponse {
131+ source : ErrorResponse :: error ( ErrorCode :: ProtocolViolation , err. to_string ( ) ) ,
132+ backtrace : Backtrace :: capture ( ) ,
133+ } ) ?;
134+
135+ // Parse timestamp strings in various common formats
136+ // Try PostgreSQL format first: "2022-04-25 16:25:01.164774"
137+ if let Ok ( dt) = NaiveDateTime :: parse_from_str ( as_str, "%Y-%m-%d %H:%M:%S%.f" ) {
138+ return Ok ( dt) ;
139+ }
140+
141+ // Try without microseconds: "2022-04-25 16:25:01"
142+ if let Ok ( dt) = NaiveDateTime :: parse_from_str ( as_str, "%Y-%m-%d %H:%M:%S" ) {
143+ return Ok ( dt) ;
144+ }
145+
146+ // Try ISO format: "2022-04-25T16:25:01.164774"
147+ if let Ok ( dt) = NaiveDateTime :: parse_from_str ( as_str, "%Y-%m-%dT%H:%M:%S%.f" ) {
148+ return Ok ( dt) ;
149+ }
150+
151+ // Try ISO format without microseconds: "2022-04-25T16:25:01"
152+ if let Ok ( dt) = NaiveDateTime :: parse_from_str ( as_str, "%Y-%m-%dT%H:%M:%S" ) {
153+ return Ok ( dt) ;
154+ }
155+
156+ Err ( ProtocolError :: ErrorResponse {
157+ source : ErrorResponse :: error (
158+ ErrorCode :: ProtocolViolation ,
159+ format ! ( "Unable to parse timestamp from text: {}" , as_str) ,
160+ ) ,
161+ backtrace : Backtrace :: capture ( ) ,
162+ } )
163+ }
164+
165+ fn from_binary ( raw : & [ u8 ] ) -> Result < Self , ProtocolError > {
166+ if raw. len ( ) != 8 {
167+ return Err ( ProtocolError :: ErrorResponse {
168+ source : ErrorResponse :: error (
169+ ErrorCode :: ProtocolViolation ,
170+ format ! (
171+ "Invalid binary timestamp length: expected 8, got {}" ,
172+ raw. len( )
173+ ) ,
174+ ) ,
175+ backtrace : Backtrace :: capture ( ) ,
176+ } ) ;
177+ }
178+
179+ // PostgreSQL timestamp is microseconds since 2000-01-01 00:00:00 UTC
180+ let microseconds = BigEndian :: read_i64 ( raw) ;
181+ let base_time = NaiveDate :: from_ymd_opt ( 2000 , 1 , 1 )
182+ . unwrap ( )
183+ . and_hms_opt ( 0 , 0 , 0 )
184+ . unwrap ( ) ;
185+
186+ let duration = chrono:: Duration :: microseconds ( microseconds) ;
187+ base_time
188+ . checked_add_signed ( duration)
189+ . ok_or_else ( || ProtocolError :: ErrorResponse {
190+ source : ErrorResponse :: error (
191+ ErrorCode :: ProtocolViolation ,
192+ format ! (
193+ "Timestamp overflow: {} microseconds from epoch" ,
194+ microseconds
195+ ) ,
196+ ) ,
197+ backtrace : Backtrace :: capture ( ) ,
198+ } )
199+ }
200+ }
201+
125202#[ cfg( test) ]
126203mod tests {
127204 use crate :: * ;
@@ -156,6 +233,16 @@ mod tests {
156233 assert_test_decode ( -std:: f64:: consts:: E , Format :: Text ) ?;
157234 assert_test_decode ( 0.0_f64 , Format :: Text ) ?;
158235
236+ #[ cfg( feature = "with-chrono" ) ]
237+ {
238+ use chrono:: NaiveDate ;
239+ let timestamp = NaiveDate :: from_ymd_opt ( 2022 , 4 , 25 )
240+ . unwrap ( )
241+ . and_hms_micro_opt ( 16 , 25 , 1 , 164774 )
242+ . unwrap ( ) ;
243+ assert_test_decode ( timestamp, Format :: Text ) ?;
244+ }
245+
159246 Ok ( ( ) )
160247 }
161248
@@ -170,6 +257,16 @@ mod tests {
170257 assert_test_decode ( -std:: f64:: consts:: E , Format :: Binary ) ?;
171258 assert_test_decode ( 0.0_f64 , Format :: Binary ) ?;
172259
260+ #[ cfg( feature = "with-chrono" ) ]
261+ {
262+ use chrono:: NaiveDate ;
263+ let timestamp = NaiveDate :: from_ymd_opt ( 2022 , 4 , 25 )
264+ . unwrap ( )
265+ . and_hms_micro_opt ( 16 , 25 , 1 , 164774 )
266+ . unwrap ( ) ;
267+ assert_test_decode ( timestamp, Format :: Binary ) ?;
268+ }
269+
173270 Ok ( ( ) )
174271 }
175272}
0 commit comments