1use super::{FromPg, ToPg, TypeError};
6use crate::protocol::types::oid;
7
8const PG_EPOCH_OFFSET_USEC: i64 = 946_684_800_000_000;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct Timestamp {
15 pub usec: i64,
17}
18
19impl Timestamp {
20 pub fn from_pg_usec(usec: i64) -> Self {
22 Self { usec }
23 }
24
25 pub fn from_unix_secs(secs: i64) -> Self {
27 Self {
28 usec: secs * 1_000_000 - PG_EPOCH_OFFSET_USEC,
29 }
30 }
31
32 pub fn to_unix_secs(&self) -> i64 {
34 (self.usec + PG_EPOCH_OFFSET_USEC) / 1_000_000
35 }
36
37 pub fn to_unix_usec(&self) -> i64 {
39 self.usec + PG_EPOCH_OFFSET_USEC
40 }
41}
42
43impl FromPg for Timestamp {
44 fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
45 if oid_val != oid::TIMESTAMP && oid_val != oid::TIMESTAMPTZ {
46 return Err(TypeError::UnexpectedOid {
47 expected: "timestamp",
48 got: oid_val,
49 });
50 }
51
52 if format == 1 {
53 if bytes.len() != 8 {
55 return Err(TypeError::InvalidData(
56 "Expected 8 bytes for timestamp".to_string(),
57 ));
58 }
59 let usec = i64::from_be_bytes([
60 bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
61 ]);
62 Ok(Timestamp::from_pg_usec(usec))
63 } else {
64 let s =
66 std::str::from_utf8(bytes).map_err(|e| TypeError::InvalidData(e.to_string()))?;
67 parse_timestamp_text(s)
68 }
69 }
70}
71
72impl ToPg for Timestamp {
73 fn to_pg(&self) -> (Vec<u8>, u32, i16) {
74 (self.usec.to_be_bytes().to_vec(), oid::TIMESTAMP, 1)
75 }
76}
77
78fn parse_timestamp_text(s: &str) -> Result<Timestamp, TypeError> {
80 let parts: Vec<&str> = s.split(&[' ', 'T'][..]).collect();
84 if parts.len() < 2 {
85 return Err(TypeError::InvalidData(format!("Invalid timestamp: {}", s)));
86 }
87
88 let date_parts: Vec<i32> = parts[0].split('-').filter_map(|p| p.parse().ok()).collect();
89
90 if date_parts.len() != 3 {
91 return Err(TypeError::InvalidData(format!(
92 "Invalid date: {}",
93 parts[0]
94 )));
95 }
96
97 let time_str =
98 parts[1].trim_end_matches(|c: char| c == '+' || c == '-' || c.is_ascii_digit() || c == ':');
99 let time_parts: Vec<&str> = time_str.split(':').collect();
100
101 let hour: i32 = time_parts.first().and_then(|s| s.parse().ok()).unwrap_or(0);
102 let minute: i32 = time_parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
103 let second_str = time_parts.get(2).unwrap_or(&"0");
104 let sec_parts: Vec<&str> = second_str.split('.').collect();
105 let second: i32 = sec_parts.first().and_then(|s| s.parse().ok()).unwrap_or(0);
106 let usec: i64 = sec_parts
107 .get(1)
108 .map(|s| {
109 let padded = format!("{:0<6}", s);
110 padded[..6].parse::<i64>().unwrap_or(0)
111 })
112 .unwrap_or(0);
113
114 let year = date_parts[0];
116 let month = date_parts[1];
117 let day = date_parts[2];
118
119 let days_since_epoch = days_from_ymd(year, month, day);
121
122 let total_usec = days_since_epoch as i64 * 86_400_000_000
123 + hour as i64 * 3_600_000_000
124 + minute as i64 * 60_000_000
125 + second as i64 * 1_000_000
126 + usec;
127
128 Ok(Timestamp::from_pg_usec(total_usec))
129}
130
131fn days_from_ymd(year: i32, month: i32, day: i32) -> i32 {
133 let mut days = 0;
135
136 for y in 2000..year {
138 days += if is_leap_year(y) { 366 } else { 365 };
139 }
140 for y in year..2000 {
141 days -= if is_leap_year(y) { 366 } else { 365 };
142 }
143
144 let days_in_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
146 for m in 1..month {
147 days += days_in_month[(m - 1) as usize];
148 if m == 2 && is_leap_year(year) {
149 days += 1;
150 }
151 }
152
153 days += day - 1;
155
156 days
157}
158
159fn is_leap_year(year: i32) -> bool {
160 (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
161}
162
163#[derive(Debug, Clone, Copy, PartialEq, Eq)]
165pub struct Date {
166 pub days: i32,
168}
169
170impl FromPg for Date {
171 fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
172 if oid_val != oid::DATE {
173 return Err(TypeError::UnexpectedOid {
174 expected: "date",
175 got: oid_val,
176 });
177 }
178
179 if format == 1 {
180 if bytes.len() != 4 {
182 return Err(TypeError::InvalidData(
183 "Expected 4 bytes for date".to_string(),
184 ));
185 }
186 let days = i32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
187 Ok(Date { days })
188 } else {
189 let s =
191 std::str::from_utf8(bytes).map_err(|e| TypeError::InvalidData(e.to_string()))?;
192 let parts: Vec<i32> = s.split('-').filter_map(|p| p.parse().ok()).collect();
193 if parts.len() != 3 {
194 return Err(TypeError::InvalidData(format!("Invalid date: {}", s)));
195 }
196 Ok(Date {
197 days: days_from_ymd(parts[0], parts[1], parts[2]),
198 })
199 }
200 }
201}
202
203impl ToPg for Date {
204 fn to_pg(&self) -> (Vec<u8>, u32, i16) {
205 (self.days.to_be_bytes().to_vec(), oid::DATE, 1)
206 }
207}
208
209#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211pub struct Time {
212 pub usec: i64,
214}
215
216impl Time {
217 pub fn new(hour: u8, minute: u8, second: u8, usec: u32) -> Self {
226 Self {
227 usec: hour as i64 * 3_600_000_000
228 + minute as i64 * 60_000_000
229 + second as i64 * 1_000_000
230 + usec as i64,
231 }
232 }
233
234 pub fn hour(&self) -> u8 {
236 ((self.usec / 3_600_000_000) % 24) as u8
237 }
238
239 pub fn minute(&self) -> u8 {
241 ((self.usec / 60_000_000) % 60) as u8
242 }
243
244 pub fn second(&self) -> u8 {
246 ((self.usec / 1_000_000) % 60) as u8
247 }
248
249 pub fn microsecond(&self) -> u32 {
251 (self.usec % 1_000_000) as u32
252 }
253}
254
255impl FromPg for Time {
256 fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
257 if oid_val != oid::TIME {
258 return Err(TypeError::UnexpectedOid {
259 expected: "time",
260 got: oid_val,
261 });
262 }
263
264 if format == 1 {
265 if bytes.len() != 8 {
267 return Err(TypeError::InvalidData(
268 "Expected 8 bytes for time".to_string(),
269 ));
270 }
271 let usec = i64::from_be_bytes([
272 bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
273 ]);
274 Ok(Time { usec })
275 } else {
276 let s =
278 std::str::from_utf8(bytes).map_err(|e| TypeError::InvalidData(e.to_string()))?;
279 parse_time_text(s)
280 }
281 }
282}
283
284impl ToPg for Time {
285 fn to_pg(&self) -> (Vec<u8>, u32, i16) {
286 (self.usec.to_be_bytes().to_vec(), oid::TIME, 1)
287 }
288}
289
290fn parse_time_text(s: &str) -> Result<Time, TypeError> {
292 let parts: Vec<&str> = s.split(':').collect();
293 if parts.len() < 2 {
294 return Err(TypeError::InvalidData(format!("Invalid time: {}", s)));
295 }
296
297 let hour: i64 = parts[0]
298 .parse()
299 .map_err(|_| TypeError::InvalidData("Invalid hour".to_string()))?;
300 let minute: i64 = parts[1]
301 .parse()
302 .map_err(|_| TypeError::InvalidData("Invalid minute".to_string()))?;
303
304 let (second, usec) = if parts.len() > 2 {
305 let sec_parts: Vec<&str> = parts[2].split('.').collect();
306 let sec: i64 = sec_parts[0].parse().unwrap_or(0);
307 let us: i64 = sec_parts
308 .get(1)
309 .map(|s| {
310 let padded = format!("{:0<6}", s);
311 padded[..6].parse::<i64>().unwrap_or(0)
312 })
313 .unwrap_or(0);
314 (sec, us)
315 } else {
316 (0, 0)
317 };
318
319 Ok(Time {
320 usec: hour * 3_600_000_000 + minute * 60_000_000 + second * 1_000_000 + usec,
321 })
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327
328 #[test]
329 fn test_timestamp_unix_conversion() {
330 let ts = Timestamp::from_unix_secs(1704067200);
332 let back = ts.to_unix_secs();
333 assert_eq!(back, 1704067200);
334 }
335
336 #[test]
337 fn test_timestamp_from_pg_binary() {
338 let usec: i64 = 789_012_345_678_900; let bytes = usec.to_be_bytes();
341 let ts = Timestamp::from_pg(&bytes, oid::TIMESTAMP, 1).unwrap();
342 assert_eq!(ts.usec, usec);
343 }
344
345 #[test]
346 fn test_date_from_pg_binary() {
347 let days: i32 = 8766;
349 let bytes = days.to_be_bytes();
350 let date = Date::from_pg(&bytes, oid::DATE, 1).unwrap();
351 assert_eq!(date.days, days);
352 }
353
354 #[test]
355 fn test_time_from_pg_binary() {
356 let usec: i64 = 12 * 3_600_000_000 + 30 * 60_000_000 + 45 * 1_000_000 + 123456;
358 let bytes = usec.to_be_bytes();
359 let time = Time::from_pg(&bytes, oid::TIME, 1).unwrap();
360 assert_eq!(time.hour(), 12);
361 assert_eq!(time.minute(), 30);
362 assert_eq!(time.second(), 45);
363 assert_eq!(time.microsecond(), 123456);
364 }
365
366 #[test]
367 fn test_time_from_pg_text() {
368 let time = parse_time_text("14:30:00").unwrap();
369 assert_eq!(time.hour(), 14);
370 assert_eq!(time.minute(), 30);
371 assert_eq!(time.second(), 0);
372 }
373}