1use crate::sql_type::OracleType;
17use crate::util::Scanner;
18use crate::Error;
19use crate::ParseOracleTypeError;
20use crate::Result;
21use odpic_sys::dpiTimestamp;
22use std::cmp::{self, Ordering};
23use std::fmt;
24use std::result;
25use std::str;
26
27#[derive(Debug, Clone, Copy)]
115pub struct Timestamp {
116 pub(crate) ts: dpiTimestamp,
117 precision: u8,
118 with_tz: bool,
119}
120
121impl Timestamp {
122 fn check_ymd_hms_ns(
123 year: i32,
124 month: u32,
125 day: u32,
126 hour: u32,
127 minute: u32,
128 second: u32,
129 nanosecond: u32,
130 ) -> Result<()> {
131 let mut errmsg = "";
132 if !(-4713..=9999).contains(&year) {
133 errmsg = "year must be between -4713 and 9999";
134 } else if !(1..=12).contains(&month) {
135 errmsg = "month must be between 1 and 12";
136 } else if !(1..=31).contains(&day) {
137 errmsg = "day must be between 1 and 31";
138 } else if !(0..=23).contains(&hour) {
139 errmsg = "hour must be between 0 and 23";
140 } else if !(0..=59).contains(&minute) {
141 errmsg = "minute must be between 0 and 59";
142 } else if !(0..=59).contains(&second) {
143 errmsg = "second must be between 0 and 59";
144 } else if !(0..=999999999).contains(&nanosecond) {
145 errmsg = "nanosecond must be between 0 and 999_999_999";
146 }
147 if errmsg.is_empty() {
148 Ok(())
149 } else {
150 let year_width = if year >= 0 { 4 } else { 5 }; Err(Error::out_of_range(format!(
152 "{errmsg} but {year:0year_width$}-{month:02}-{day:02} {hour:02}:{minute:02}:{second:02}.{nanosecond:09}",
153 )))
154 }
155 }
156
157 fn check_tz_hm_offset(hour_offset: i32, minute_offset: i32) -> Result<()> {
158 if !(-59..=59).contains(&minute_offset) {
159 Err(Error::out_of_range(format!(
160 "minute_offset must be between -59 and 59 but {}",
161 minute_offset
162 )))
163 } else if hour_offset < 0 && minute_offset > 0 {
164 Err(Error::out_of_range(
165 "hour_offset is negative but minimum is positive",
166 ))
167 } else if hour_offset > 0 && minute_offset < 0 {
168 Err(Error::out_of_range(
169 "hour_offset is positive but minimum is negative",
170 ))
171 } else {
172 Ok(())
173 }
174 }
175
176 pub(crate) fn from_dpi_timestamp(ts: &dpiTimestamp, oratype: &OracleType) -> Timestamp {
177 let (precision, with_tz) = match *oratype {
178 OracleType::Timestamp(prec) => (prec, false),
179 OracleType::TimestampTZ(prec) => (prec, true),
180 OracleType::TimestampLTZ(prec) => (prec, true),
181 _ => (0, false),
182 };
183 Timestamp {
184 ts: *ts,
185 precision,
186 with_tz,
187 }
188 }
189
190 pub fn new(
205 year: i32,
206 month: u32,
207 day: u32,
208 hour: u32,
209 minute: u32,
210 second: u32,
211 nanosecond: u32,
212 ) -> Result<Timestamp> {
213 Self::check_ymd_hms_ns(year, month, day, hour, minute, second, nanosecond)?;
214 Ok(Timestamp {
215 ts: dpiTimestamp {
216 year: year as i16,
217 month: month as u8,
218 day: day as u8,
219 hour: hour as u8,
220 minute: minute as u8,
221 second: second as u8,
222 fsecond: nanosecond,
223 tzHourOffset: 0,
224 tzMinuteOffset: 0,
225 },
226 precision: 9,
227 with_tz: false,
228 })
229 }
230
231 #[inline]
235 pub fn and_tz_offset(&self, offset: i32) -> Result<Timestamp> {
236 self.and_tz_hm_offset(offset / 3600, offset % 3600 / 60)
237 }
238
239 #[inline]
245 pub fn and_tz_hm_offset(&self, hour_offset: i32, minute_offset: i32) -> Result<Timestamp> {
246 Self::check_tz_hm_offset(hour_offset, minute_offset)?;
247 Ok(Timestamp {
248 ts: dpiTimestamp {
249 tzHourOffset: hour_offset as i8,
250 tzMinuteOffset: minute_offset as i8,
251 ..self.ts
252 },
253 with_tz: true,
254 ..*self
255 })
256 }
257
258 #[inline]
263 pub fn and_prec(&self, precision: u8) -> Result<Timestamp> {
264 if precision > 9 {
265 Err(Error::out_of_range(format!(
266 "precision must be 0 to 9 but {}",
267 precision
268 )))
269 } else {
270 Ok(Timestamp { precision, ..*self })
271 }
272 }
273
274 pub fn year(&self) -> i32 {
276 self.ts.year.into()
277 }
278
279 pub fn month(&self) -> u32 {
281 self.ts.month.into()
282 }
283
284 pub fn day(&self) -> u32 {
286 self.ts.day.into()
287 }
288
289 pub fn hour(&self) -> u32 {
291 self.ts.hour.into()
292 }
293
294 pub fn minute(&self) -> u32 {
296 self.ts.minute.into()
297 }
298
299 pub fn second(&self) -> u32 {
301 self.ts.second.into()
302 }
303
304 pub fn nanosecond(&self) -> u32 {
306 self.ts.fsecond
307 }
308
309 pub fn tz_hour_offset(&self) -> i32 {
311 self.ts.tzHourOffset.into()
312 }
313
314 pub fn tz_minute_offset(&self) -> i32 {
316 self.ts.tzMinuteOffset.into()
317 }
318
319 pub fn precision(&self) -> u8 {
321 self.precision
322 }
323
324 pub fn with_tz(&self) -> bool {
327 self.with_tz
328 }
329
330 pub fn tz_offset(&self) -> i32 {
332 self.ts.tzHourOffset as i32 * 3600 + self.ts.tzMinuteOffset as i32 * 60
333 }
334}
335
336impl cmp::PartialEq for Timestamp {
337 fn eq(&self, other: &Self) -> bool {
338 self.ts.year == other.ts.year
339 && self.ts.month == other.ts.month
340 && self.ts.day == other.ts.day
341 && self.ts.hour == other.ts.hour
342 && self.ts.minute == other.ts.minute
343 && self.ts.second == other.ts.second
344 && self.ts.fsecond == other.ts.fsecond
345 && self.ts.tzHourOffset == other.ts.tzHourOffset
346 && self.ts.tzMinuteOffset == other.ts.tzMinuteOffset
347 }
348}
349
350impl fmt::Display for Timestamp {
351 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
352 let (ymd_sep, ymd_hms_sep, hms_sep, hms_tz_sep) = if !f.alternate() {
353 ("-", ' ', ":", " ")
355 } else if !f.sign_minus() {
356 ("-", 'T', ":", "")
358 } else {
359 ("", 'T', "", "")
361 };
362 let ts = &self.ts;
363 let year_width = if ts.year >= 0 { 4 } else { 5 };
364 write!(
365 f,
366 "{:0year_width$}{ymd_sep}{:02}{ymd_sep}{:02}{ymd_hms_sep}{:02}{hms_sep}{:02}{hms_sep}{:02}",
367 ts.year, ts.month, ts.day,
368 ts.hour, ts.minute, ts.second
369 )?;
370 let prec = cmp::min(f.precision().unwrap_or(self.precision.into()), 9);
371 if prec != 0 {
372 const SUBSEC_DIV: [u32; 9] = [
373 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1,
374 ];
375 write!(f, ".{:0prec$}", self.ts.fsecond / SUBSEC_DIV[prec - 1])?;
376 }
377 if self.with_tz {
378 let sign = if self.ts.tzHourOffset < 0 || self.ts.tzMinuteOffset < 0 {
379 '-'
380 } else {
381 '+'
382 };
383 write!(
384 f,
385 "{hms_tz_sep}{sign}{:02}{hms_sep}{:02}",
386 self.ts.tzHourOffset.abs(),
387 self.ts.tzMinuteOffset.abs()
388 )?;
389 }
390 Ok(())
391 }
392}
393
394impl str::FromStr for Timestamp {
395 type Err = ParseOracleTypeError;
396
397 fn from_str(s: &str) -> result::Result<Self, Self::Err> {
398 let err = || ParseOracleTypeError::new("Timestamp");
399 let mut s = Scanner::new(s);
400 let minus = if let Some('-') = s.char() {
401 s.next();
402 true
403 } else {
404 false
405 };
406 let mut year = s.read_digits().ok_or_else(err)?;
407 let mut month = 1;
408 let mut day = 1;
409 match s.char() {
410 Some('T') | Some(' ') | None => {
411 if year > 10000 {
412 day = year % 100;
413 month = (year / 100) % 100;
414 year /= 10000;
415 }
416 }
417 Some('-') => {
418 s.next();
419 month = s.read_digits().ok_or_else(err)?;
420 if let Some('-') = s.char() {
421 s.next();
422 day = s.read_digits().ok_or_else(err)?
423 }
424 }
425 _ => return Err(err()),
426 }
427 let mut hour = 0;
428 let mut min = 0;
429 let mut sec = 0;
430 let mut nsec = 0;
431 let mut tz_hour: i32 = 0;
432 let mut tz_min: i32 = 0;
433 let mut precision = 0;
434 let mut with_tz = false;
435 if let Some(c) = s.char() {
436 match c {
437 'T' | ' ' => {
438 s.next();
439 hour = s.read_digits().ok_or_else(err)?;
440 if let Some(':') = s.char() {
441 s.next();
442 min = s.read_digits().ok_or_else(err)?;
443 if let Some(':') = s.char() {
444 s.next();
445 sec = s.read_digits().ok_or_else(err)?;
446 }
447 } else if s.ndigits() == 6 {
448 sec = hour % 100;
450 min = (hour / 100) % 100;
451 hour /= 10000;
452 } else {
453 return Err(err());
454 }
455 }
456 _ => return Err(err()),
457 }
458 if let Some('.') = s.char() {
459 s.next();
460 nsec = s.read_digits().ok_or_else(err)?;
461 let ndigit = s.ndigits();
462 precision = ndigit;
463 match ndigit.cmp(&9) {
464 Ordering::Less => nsec *= 10u64.pow(9 - ndigit),
465 Ordering::Equal => (),
466 Ordering::Greater => {
467 nsec /= 10u64.pow(ndigit - 9);
468 precision = 9;
469 }
470 }
471 }
472 if let Some(' ') = s.char() {
473 s.next();
474 }
475 match s.char() {
476 Some('+') => {
477 s.next();
478 tz_hour = s.read_digits().ok_or_else(err)? as i32;
479 if let Some(':') = s.char() {
480 s.next();
481 tz_min = s.read_digits().ok_or_else(err)? as i32;
482 } else {
483 tz_min = tz_hour % 100;
484 tz_hour /= 100;
485 }
486 with_tz = true;
487 }
488 Some('-') => {
489 s.next();
490 tz_hour = s.read_digits().ok_or_else(err)? as i32;
491 if let Some(':') = s.char() {
492 s.next();
493 tz_min = s.read_digits().ok_or_else(err)? as i32;
494 } else {
495 tz_min = tz_hour % 100;
496 tz_hour /= 100;
497 }
498 tz_hour = -tz_hour;
499 tz_min = -tz_min;
500 with_tz = true;
501 }
502 Some('Z') => {
503 s.next();
504 with_tz = true;
505 }
506 _ => (),
507 }
508 if s.char().is_some() {
509 return Err(err());
510 }
511 }
512 let mut ts = Timestamp::new(
513 if minus { -(year as i32) } else { year as i32 },
514 month as u32,
515 day as u32,
516 hour as u32,
517 min as u32,
518 sec as u32,
519 nsec as u32,
520 )
521 .map_err(|_| err())?;
522 ts.precision = precision as u8;
523 if with_tz {
524 ts = ts.and_tz_hm_offset(tz_hour, tz_min).map_err(|_| err())?;
525 }
526 Ok(ts)
527 }
528}
529
530#[cfg(test)]
531mod tests {
532 use super::*;
533
534 #[test]
535 fn format_year() -> Result<()> {
536 let ts = Timestamp::new(1234, 3, 4, 5, 6, 7, 0)?;
537 assert_eq!(format!("{}", ts), "1234-03-04 05:06:07.000000000");
538 let ts = Timestamp::new(123, 3, 4, 5, 6, 7, 0)?;
539 assert_eq!(format!("{}", ts), "0123-03-04 05:06:07.000000000");
540 let ts = Timestamp::new(12, 3, 4, 5, 6, 7, 0)?;
541 assert_eq!(format!("{}", ts), "0012-03-04 05:06:07.000000000");
542 let ts = Timestamp::new(1, 3, 4, 5, 6, 7, 0)?;
543 assert_eq!(format!("{}", ts), "0001-03-04 05:06:07.000000000");
544 let ts = Timestamp::new(-1234, 3, 4, 5, 6, 7, 0)?;
545 assert_eq!(format!("{}", ts), "-1234-03-04 05:06:07.000000000");
546 let ts = Timestamp::new(-123, 3, 4, 5, 6, 7, 0)?;
547 assert_eq!(format!("{}", ts), "-0123-03-04 05:06:07.000000000");
548 let ts = Timestamp::new(-12, 3, 4, 5, 6, 7, 0)?;
549 assert_eq!(format!("{}", ts), "-0012-03-04 05:06:07.000000000");
550 let ts = Timestamp::new(-1, 3, 4, 5, 6, 7, 0)?;
551 assert_eq!(format!("{}", ts), "-0001-03-04 05:06:07.000000000");
552 Ok(())
553 }
554
555 #[test]
556 fn format_with_prec_in_fmt_param() -> Result<()> {
557 let ts = Timestamp::new(2012, 3, 4, 5, 6, 7, 0)?;
558 assert_eq!(format!("{:.0}", ts), "2012-03-04 05:06:07");
560 assert_eq!(format!("{:.1}", ts), "2012-03-04 05:06:07.0");
561 assert_eq!(format!("{:.2}", ts), "2012-03-04 05:06:07.00");
562 assert_eq!(format!("{:.3}", ts), "2012-03-04 05:06:07.000");
563 assert_eq!(format!("{:.4}", ts), "2012-03-04 05:06:07.0000");
564 assert_eq!(format!("{:.5}", ts), "2012-03-04 05:06:07.00000");
565 assert_eq!(format!("{:.6}", ts), "2012-03-04 05:06:07.000000");
566 assert_eq!(format!("{:.7}", ts), "2012-03-04 05:06:07.0000000");
567 assert_eq!(format!("{:.8}", ts), "2012-03-04 05:06:07.00000000");
568 assert_eq!(format!("{:.9}", ts), "2012-03-04 05:06:07.000000000");
569 assert_eq!(format!("{:.10}", ts), "2012-03-04 05:06:07.000000000");
571 assert_eq!(format!("{:.11}", ts), "2012-03-04 05:06:07.000000000");
572 Ok(())
573 }
574
575 #[test]
576 fn format_with_prec_in_type() -> Result<()> {
577 let ts = Timestamp::new(2012, 3, 4, 5, 6, 7, 0)?;
578 assert_eq!(format!("{}", ts.and_prec(0)?), "2012-03-04 05:06:07");
579 assert_eq!(format!("{}", ts.and_prec(1)?), "2012-03-04 05:06:07.0");
580 assert_eq!(format!("{}", ts.and_prec(2)?), "2012-03-04 05:06:07.00");
581 assert_eq!(format!("{}", ts.and_prec(3)?), "2012-03-04 05:06:07.000");
582 assert_eq!(format!("{}", ts.and_prec(4)?), "2012-03-04 05:06:07.0000");
583 assert_eq!(format!("{}", ts.and_prec(5)?), "2012-03-04 05:06:07.00000");
584 assert_eq!(format!("{}", ts.and_prec(6)?), "2012-03-04 05:06:07.000000");
585 assert_eq!(
586 format!("{}", ts.and_prec(7)?),
587 "2012-03-04 05:06:07.0000000"
588 );
589 assert_eq!(
590 format!("{}", ts.and_prec(8)?),
591 "2012-03-04 05:06:07.00000000"
592 );
593 assert_eq!(
594 format!("{}", ts.and_prec(9)?),
595 "2012-03-04 05:06:07.000000000"
596 );
597 Ok(())
598 }
599
600 #[test]
601 fn format_as_iso_8601() -> Result<()> {
602 let ts = Timestamp::new(2012, 3, 4, 5, 6, 7, 0)?.and_prec(0)?;
603 assert_eq!(format!("{}", ts), "2012-03-04 05:06:07");
604 assert_eq!(format!("{:#}", ts), "2012-03-04T05:06:07");
605 assert_eq!(format!("{:-#}", ts), "20120304T050607");
606 let ts = ts.and_prec(3)?;
607 assert_eq!(format!("{}", ts), "2012-03-04 05:06:07.000");
608 assert_eq!(format!("{:#}", ts), "2012-03-04T05:06:07.000");
609 assert_eq!(format!("{:-#}", ts), "20120304T050607.000");
610 let ts = ts.and_tz_hm_offset(9, 30)?;
611 assert_eq!(format!("{}", ts), "2012-03-04 05:06:07.000 +09:30");
612 assert_eq!(format!("{:#}", ts), "2012-03-04T05:06:07.000+09:30");
613 assert_eq!(format!("{:-#}", ts), "20120304T050607.000+0930");
614 let ts = ts.and_tz_hm_offset(-9, -30)?;
615 assert_eq!(format!("{}", ts), "2012-03-04 05:06:07.000 -09:30");
616 assert_eq!(format!("{:#}", ts), "2012-03-04T05:06:07.000-09:30");
617 assert_eq!(format!("{:-#}", ts), "20120304T050607.000-0930");
618 Ok(())
619 }
620
621 #[test]
622 fn parse() -> Result<()> {
623 let ts = Timestamp::new(2012, 1, 1, 0, 0, 0, 0)?;
624 assert_eq!("2012".parse(), Ok(ts));
625 assert_eq!("2012-01-01".parse(), Ok(ts));
626 assert_eq!("20120101".parse(), Ok(ts));
627 let ts = Timestamp::new(2012, 3, 4, 0, 0, 0, 0)?;
628 assert_eq!("2012-03-04".parse(), Ok(ts));
629 assert_eq!("20120304".parse(), Ok(ts));
630 let ts = Timestamp::new(2012, 3, 4, 5, 6, 7, 0)?;
631 assert_eq!("2012-03-04 05:06:07".parse(), Ok(ts));
632 assert_eq!("2012-03-04T05:06:07".parse(), Ok(ts));
633 assert_eq!("20120304T050607".parse(), Ok(ts));
634 let ts = Timestamp::new(2012, 3, 4, 5, 6, 7, 800_000_000)?;
635 assert_eq!("2012-03-04 05:06:07.8".parse(), Ok(ts));
636 assert_eq!("2012-03-04T05:06:07.8".parse(), Ok(ts));
637 assert_eq!("20120304T050607.8".parse(), Ok(ts));
638 let ts = Timestamp::new(2012, 3, 4, 5, 6, 7, 890_000_000)?;
639 assert_eq!("2012-03-04 05:06:07.89".parse(), Ok(ts));
640 assert_eq!("2012-03-04T05:06:07.89".parse(), Ok(ts));
641 assert_eq!("20120304T050607.89".parse(), Ok(ts));
642 let ts = Timestamp::new(2012, 3, 4, 5, 6, 7, 890_123_456)?;
643 assert_eq!("2012-03-04 05:06:07.890123456".parse(), Ok(ts));
644 assert_eq!("2012-03-04T05:06:07.890123456".parse(), Ok(ts));
645 assert_eq!("20120304T050607.890123456".parse(), Ok(ts));
646 let ts = Timestamp::new(2012, 3, 4, 5, 6, 7, 0)?.and_tz_hm_offset(0, 0)?;
647 assert_eq!("2012-03-04 05:06:07Z".parse(), Ok(ts));
648 assert_eq!("2012-03-04 05:06:07+00:00".parse(), Ok(ts));
649 assert_eq!("2012-03-04 05:06:07 +00:00".parse(), Ok(ts));
650 assert_eq!("2012-03-04 05:06:07+0000".parse(), Ok(ts));
651 assert_eq!("2012-03-04 05:06:07 +0000".parse(), Ok(ts));
652 let ts = Timestamp::new(2012, 3, 4, 5, 6, 7, 0)?.and_tz_hm_offset(9, 30)?;
653 assert_eq!("2012-03-04 05:06:07+09:30".parse(), Ok(ts));
654 assert_eq!("2012-03-04 05:06:07 +09:30".parse(), Ok(ts));
655 assert_eq!("2012-03-04 05:06:07+0930".parse(), Ok(ts));
656 assert_eq!("2012-03-04 05:06:07 +0930".parse(), Ok(ts));
657 let ts = Timestamp::new(2012, 3, 4, 5, 6, 7, 0)?.and_tz_hm_offset(-9, -30)?;
658 assert_eq!("2012-03-04 05:06:07-09:30".parse(), Ok(ts));
659 assert_eq!("2012-03-04 05:06:07 -09:30".parse(), Ok(ts));
660 assert_eq!("2012-03-04 05:06:07-0930".parse(), Ok(ts));
661 assert_eq!("2012-03-04 05:06:07 -0930".parse(), Ok(ts));
662 let ts = Timestamp::new(2012, 3, 4, 5, 6, 7, 123_000_000)?.and_tz_hm_offset(-9, -30)?;
663 assert_eq!("2012-03-04 05:06:07.123-09:30".parse(), Ok(ts));
664 assert_eq!("2012-03-04 05:06:07.123 -09:30".parse(), Ok(ts));
665 assert_eq!("2012-03-04 05:06:07.123-0930".parse(), Ok(ts));
666 assert_eq!("2012-03-04 05:06:07.123 -0930".parse(), Ok(ts));
667 Ok(())
668 }
669}