1use serde::{Deserialize, Serialize};
19use std::fmt;
20use std::hash::Hash;
21use std::ops::{Add, Neg, Sub};
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
35pub struct IntervalValue {
36 pub months: i32,
38 pub days: i32,
40 pub microseconds: i64,
42}
43
44impl IntervalValue {
45 pub const fn new(months: i32, days: i32, microseconds: i64) -> Self {
47 Self {
48 months,
49 days,
50 microseconds,
51 }
52 }
53
54 pub const fn from_years(years: i32) -> Self {
56 Self {
57 months: years * 12,
58 days: 0,
59 microseconds: 0,
60 }
61 }
62
63 pub const fn from_months(months: i32) -> Self {
65 Self {
66 months,
67 days: 0,
68 microseconds: 0,
69 }
70 }
71
72 pub const fn from_days(days: i32) -> Self {
74 Self {
75 months: 0,
76 days,
77 microseconds: 0,
78 }
79 }
80
81 pub const fn from_hours(hours: i64) -> Self {
83 Self {
84 months: 0,
85 days: 0,
86 microseconds: hours * 3_600_000_000,
87 }
88 }
89
90 pub const fn from_minutes(minutes: i64) -> Self {
92 Self {
93 months: 0,
94 days: 0,
95 microseconds: minutes * 60_000_000,
96 }
97 }
98
99 pub const fn from_seconds(seconds: i64) -> Self {
101 Self {
102 months: 0,
103 days: 0,
104 microseconds: seconds * 1_000_000,
105 }
106 }
107
108 pub const fn from_milliseconds(millis: i64) -> Self {
110 Self {
111 months: 0,
112 days: 0,
113 microseconds: millis * 1_000,
114 }
115 }
116
117 pub const fn from_microseconds(micros: i64) -> Self {
119 Self {
120 months: 0,
121 days: 0,
122 microseconds: micros,
123 }
124 }
125
126 pub fn parse(s: &str) -> Result<Self, IntervalError> {
134 let s = s.trim();
135
136 if s.starts_with('P') {
138 return Self::parse_iso8601(s);
139 }
140
141 Self::parse_postgresql(s)
143 }
144
145 fn parse_iso8601(s: &str) -> Result<Self, IntervalError> {
147 if !s.starts_with('P') {
148 return Err(IntervalError::InvalidFormat(s.to_string()));
149 }
150
151 let mut months = 0i32;
152 let mut days = 0i32;
153 let mut microseconds = 0i64;
154
155 let chars = s[1..].chars().peekable();
156 let mut num_str = String::new();
157 let mut in_time_part = false;
158
159 for c in chars {
160 match c {
161 'T' => {
162 in_time_part = true;
163 num_str.clear();
164 }
165 'Y' => {
166 let num: i32 = num_str
167 .parse()
168 .map_err(|_| IntervalError::InvalidFormat(s.to_string()))?;
169 months += num * 12;
170 num_str.clear();
171 }
172 'M' if !in_time_part => {
173 let num: i32 = num_str
174 .parse()
175 .map_err(|_| IntervalError::InvalidFormat(s.to_string()))?;
176 months += num;
177 num_str.clear();
178 }
179 'D' => {
180 let num: i32 = num_str
181 .parse()
182 .map_err(|_| IntervalError::InvalidFormat(s.to_string()))?;
183 days += num;
184 num_str.clear();
185 }
186 'H' => {
187 let num: i64 = num_str
188 .parse()
189 .map_err(|_| IntervalError::InvalidFormat(s.to_string()))?;
190 microseconds += num * 3_600_000_000;
191 num_str.clear();
192 }
193 'M' if in_time_part => {
194 let num: i64 = num_str
195 .parse()
196 .map_err(|_| IntervalError::InvalidFormat(s.to_string()))?;
197 microseconds += num * 60_000_000;
198 num_str.clear();
199 }
200 'S' => {
201 let num: f64 = num_str
202 .parse()
203 .map_err(|_| IntervalError::InvalidFormat(s.to_string()))?;
204 microseconds += (num * 1_000_000.0) as i64;
205 num_str.clear();
206 }
207 c if c.is_ascii_digit() || c == '.' || c == '-' => {
208 num_str.push(c);
209 }
210 _ => return Err(IntervalError::InvalidFormat(s.to_string())),
211 }
212 }
213
214 Ok(Self::new(months, days, microseconds))
215 }
216
217 fn parse_postgresql(s: &str) -> Result<Self, IntervalError> {
219 let mut months = 0i32;
220 let mut days = 0i32;
221 let mut microseconds = 0i64;
222
223 if let Some(dash_pos) = s.find('-') {
225 let before = s[..dash_pos].trim();
226 let after = s[dash_pos + 1..].trim();
227 if before.parse::<i32>().is_ok() && after.parse::<i32>().is_ok() {
228 let years: i32 = before.parse().unwrap();
229 let mons: i32 = after.parse().unwrap();
230 return Ok(Self::new(years * 12 + mons, 0, 0));
231 }
232 }
233
234 if let Some(space_pos) = s.find(' ') {
236 let before = s[..space_pos].trim();
237 let after = s[space_pos + 1..].trim();
238 if let Ok(d) = before.parse::<i32>() {
239 if after.contains(':') {
240 days = d;
241 microseconds = Self::parse_time(after)?;
242 return Ok(Self::new(0, days, microseconds));
243 }
244 }
245 }
246
247 let parts: Vec<&str> = s.split_whitespace().collect();
249 let mut i = 0;
250
251 while i < parts.len() {
252 let num: f64 = parts[i]
253 .parse()
254 .map_err(|_| IntervalError::InvalidFormat(s.to_string()))?;
255 i += 1;
256
257 if i >= parts.len() {
258 return Err(IntervalError::InvalidFormat(s.to_string()));
259 }
260
261 let unit = parts[i].to_lowercase();
262 i += 1;
263
264 let unit = if unit.ends_with('s') && unit.len() > 1 {
266 &unit[..unit.len() - 1]
267 } else {
268 &unit
269 };
270
271 match unit {
272 "year" => months += (num * 12.0) as i32,
273 "month" => months += num as i32,
274 "day" => days += num as i32,
275 "hour" => microseconds += (num * 3_600_000_000.0) as i64,
276 "minute" => microseconds += (num * 60_000_000.0) as i64,
277 "second" => microseconds += (num * 1_000_000.0) as i64,
278 "millisecond" => microseconds += (num * 1_000.0) as i64,
279 "microsecond" => microseconds += num as i64,
280 "week" => days += (num * 7.0) as i32,
281 "century" | "centurie" => months += (num * 1200.0) as i32,
282 "decade" => months += (num * 120.0) as i32,
283 "millennium" => months += (num * 12000.0) as i32,
284 "quarter" => months += (num * 3.0) as i32,
285 _ => return Err(IntervalError::InvalidUnit(unit.to_string())),
286 }
287 }
288
289 Ok(Self::new(months, days, microseconds))
290 }
291
292 fn parse_time(s: &str) -> Result<i64, IntervalError> {
294 let parts: Vec<&str> = s.split(':').collect();
295 if parts.len() != 3 {
296 return Err(IntervalError::InvalidFormat(s.to_string()));
297 }
298
299 let hours: i64 = parts[0]
300 .parse()
301 .map_err(|_| IntervalError::InvalidFormat(s.to_string()))?;
302 let minutes: i64 = parts[1]
303 .parse()
304 .map_err(|_| IntervalError::InvalidFormat(s.to_string()))?;
305
306 let sec_parts: Vec<&str> = parts[2].split('.').collect();
308 let seconds: i64 = sec_parts[0]
309 .parse()
310 .map_err(|_| IntervalError::InvalidFormat(s.to_string()))?;
311
312 let mut micros = 0i64;
313 if sec_parts.len() > 1 {
314 let frac = format!("{:0<6}", sec_parts[1]);
315 let frac_6: i64 = frac[..6.min(frac.len())]
316 .parse()
317 .map_err(|_| IntervalError::InvalidFormat(s.to_string()))?;
318 micros = frac_6;
319 }
320
321 Ok(hours * 3_600_000_000 + minutes * 60_000_000 + seconds * 1_000_000 + micros)
322 }
323
324 pub const fn total_months(&self) -> i32 {
326 self.months
327 }
328
329 pub const fn total_days(&self) -> i32 {
331 self.days
332 }
333
334 pub const fn total_microseconds(&self) -> i64 {
336 self.microseconds
337 }
338
339 pub const fn years(&self) -> i32 {
341 self.months / 12
342 }
343
344 pub const fn months_component(&self) -> i32 {
346 self.months % 12
347 }
348
349 pub const fn hours(&self) -> i64 {
351 self.microseconds / 3_600_000_000
352 }
353
354 pub const fn minutes(&self) -> i64 {
356 (self.microseconds.abs() % 3_600_000_000) / 60_000_000
357 }
358
359 pub const fn seconds(&self) -> i64 {
361 (self.microseconds.abs() % 60_000_000) / 1_000_000
362 }
363
364 pub const fn fractional_seconds(&self) -> i64 {
366 self.microseconds.abs() % 1_000_000
367 }
368
369 pub fn is_positive(&self) -> bool {
371 self.months > 0 || self.days > 0 || self.microseconds > 0
372 }
373
374 pub fn is_negative(&self) -> bool {
376 self.months < 0 || self.days < 0 || self.microseconds < 0
377 }
378
379 pub fn is_zero(&self) -> bool {
381 self.months == 0 && self.days == 0 && self.microseconds == 0
382 }
383
384 pub fn normalize(&self) -> Self {
386 *self
389 }
390
391 pub const fn neg(&self) -> Self {
393 Self::new(-self.months, -self.days, -self.microseconds)
394 }
395
396 pub fn abs(&self) -> Self {
398 Self::new(self.months.abs(), self.days.abs(), self.microseconds.abs())
399 }
400
401 pub fn to_iso8601(&self) -> String {
403 let mut result = String::from("P");
404
405 let years = self.years();
406 let months = self.months_component();
407 let days = self.days;
408
409 if years != 0 {
410 result.push_str(&format!("{}Y", years));
411 }
412 if months != 0 {
413 result.push_str(&format!("{}M", months));
414 }
415 if days != 0 {
416 result.push_str(&format!("{}D", days));
417 }
418
419 let hours = self.hours();
420 let minutes = self.minutes();
421 let seconds = self.seconds();
422 let micros = self.fractional_seconds();
423
424 if hours != 0 || minutes != 0 || seconds != 0 || micros != 0 {
425 result.push('T');
426 if hours != 0 {
427 result.push_str(&format!("{}H", hours));
428 }
429 if minutes != 0 {
430 result.push_str(&format!("{}M", minutes));
431 }
432 if seconds != 0 || micros != 0 {
433 if micros != 0 {
434 result.push_str(&format!("{}.{:06}S", seconds, micros));
435 } else {
436 result.push_str(&format!("{}S", seconds));
437 }
438 }
439 }
440
441 if result == "P" {
443 result.push_str("T0S");
444 }
445
446 result
447 }
448
449 pub fn to_postgresql(&self) -> String {
451 let mut parts = Vec::new();
452
453 let years = self.years();
454 let months = self.months_component();
455 let days = self.days;
456 let hours = self.hours();
457 let minutes = self.minutes();
458 let seconds = self.seconds();
459 let micros = self.fractional_seconds();
460
461 if years != 0 {
462 parts.push(format!(
463 "{} year{}",
464 years,
465 if years == 1 { "" } else { "s" }
466 ));
467 }
468 if months != 0 {
469 parts.push(format!(
470 "{} mon{}",
471 months,
472 if months == 1 { "" } else { "s" }
473 ));
474 }
475 if days != 0 {
476 parts.push(format!("{} day{}", days, if days == 1 { "" } else { "s" }));
477 }
478
479 if hours != 0 || minutes != 0 || seconds != 0 || micros != 0 {
480 let time_str = if micros != 0 {
481 format!("{:02}:{:02}:{:02}.{:06}", hours, minutes, seconds, micros)
482 } else {
483 format!("{:02}:{:02}:{:02}", hours, minutes, seconds)
484 };
485 parts.push(time_str);
486 }
487
488 if parts.is_empty() {
489 "00:00:00".to_string()
490 } else {
491 parts.join(" ")
492 }
493 }
494
495 pub const fn estimated_size(&self) -> usize {
497 std::mem::size_of::<Self>()
498 }
499
500 pub const fn zero() -> Self {
502 Self::new(0, 0, 0)
503 }
504}
505
506impl Default for IntervalValue {
507 fn default() -> Self {
508 Self::zero()
509 }
510}
511
512impl fmt::Display for IntervalValue {
513 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
514 write!(f, "{}", self.to_postgresql())
515 }
516}
517
518impl Add for IntervalValue {
519 type Output = Self;
520
521 fn add(self, other: Self) -> Self {
522 Self::new(
523 self.months + other.months,
524 self.days + other.days,
525 self.microseconds + other.microseconds,
526 )
527 }
528}
529
530impl Sub for IntervalValue {
531 type Output = Self;
532
533 fn sub(self, other: Self) -> Self {
534 Self::new(
535 self.months - other.months,
536 self.days - other.days,
537 self.microseconds - other.microseconds,
538 )
539 }
540}
541
542impl Neg for IntervalValue {
543 type Output = Self;
544
545 fn neg(self) -> Self {
546 Self::new(-self.months, -self.days, -self.microseconds)
547 }
548}
549
550#[derive(Debug, Clone, PartialEq, Eq)]
552pub enum IntervalError {
553 InvalidFormat(String),
554 InvalidUnit(String),
555}
556
557impl fmt::Display for IntervalError {
558 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
559 match self {
560 IntervalError::InvalidFormat(s) => write!(f, "Invalid interval format: {}", s),
561 IntervalError::InvalidUnit(u) => write!(f, "Invalid interval unit: {}", u),
562 }
563 }
564}
565
566impl std::error::Error for IntervalError {}
567
568#[cfg(test)]
569mod tests {
570 use super::*;
571
572 #[test]
573 fn test_interval_from_components() {
574 let iv = IntervalValue::new(14, 3, 4_500_000_000);
575 assert_eq!(iv.months, 14);
576 assert_eq!(iv.days, 3);
577 assert_eq!(iv.microseconds, 4_500_000_000);
578 }
579
580 #[test]
581 fn test_interval_from_years() {
582 let iv = IntervalValue::from_years(2);
583 assert_eq!(iv.months, 24);
584 assert_eq!(iv.days, 0);
585 assert_eq!(iv.microseconds, 0);
586 }
587
588 #[test]
589 fn test_interval_from_days() {
590 let iv = IntervalValue::from_days(5);
591 assert_eq!(iv.months, 0);
592 assert_eq!(iv.days, 5);
593 assert_eq!(iv.microseconds, 0);
594 }
595
596 #[test]
597 fn test_interval_from_hours() {
598 let iv = IntervalValue::from_hours(3);
599 assert_eq!(iv.microseconds, 3 * 3_600_000_000);
600 }
601
602 #[test]
603 fn test_interval_parse_iso8601() {
604 let iv = IntervalValue::parse("P1Y2M3DT4H5M6S").unwrap();
605 assert_eq!(iv.months, 14);
606 assert_eq!(iv.days, 3);
607 assert_eq!(
608 iv.microseconds,
609 4 * 3_600_000_000 + 5 * 60_000_000 + 6 * 1_000_000
610 );
611 }
612
613 #[test]
614 fn test_interval_parse_iso8601_fractional() {
615 let iv = IntervalValue::parse("PT1.5S").unwrap();
616 assert_eq!(iv.microseconds, 1_500_000);
617 }
618
619 #[test]
620 fn test_interval_parse_postgresql() {
621 let iv =
622 IntervalValue::parse("1 year 2 months 3 days 4 hours 5 minutes 6 seconds").unwrap();
623 assert_eq!(iv.months, 14);
624 assert_eq!(iv.days, 3);
625 assert_eq!(
626 iv.microseconds,
627 4 * 3_600_000_000 + 5 * 60_000_000 + 6 * 1_000_000
628 );
629 }
630
631 #[test]
632 fn test_interval_parse_year_month() {
633 let iv = IntervalValue::parse("1-2").unwrap();
634 assert_eq!(iv.months, 14);
635 }
636
637 #[test]
638 fn test_interval_parse_day_time() {
639 let iv = IntervalValue::parse("3 4:05:06").unwrap();
640 assert_eq!(iv.days, 3);
641 assert_eq!(
642 iv.microseconds,
643 4 * 3_600_000_000 + 5 * 60_000_000 + 6 * 1_000_000
644 );
645 }
646
647 #[test]
648 fn test_interval_parse_plural() {
649 let iv = IntervalValue::parse("2 years 5 days").unwrap();
650 assert_eq!(iv.months, 24);
651 assert_eq!(iv.days, 5);
652 }
653
654 #[test]
655 fn test_interval_add() {
656 let iv1 = IntervalValue::from_days(3);
657 let iv2 = IntervalValue::from_hours(12);
658 let result = iv1 + iv2;
659 assert_eq!(result.days, 3);
660 assert_eq!(result.microseconds, 12 * 3_600_000_000);
661 }
662
663 #[test]
664 fn test_interval_sub() {
665 let iv1 = IntervalValue::from_days(5);
666 let iv2 = IntervalValue::from_days(2);
667 let result = iv1 - iv2;
668 assert_eq!(result.days, 3);
669 }
670
671 #[test]
672 fn test_interval_neg() {
673 let iv = IntervalValue::new(1, 2, 3);
674 let neg = iv.neg();
675 assert_eq!(neg.months, -1);
676 assert_eq!(neg.days, -2);
677 assert_eq!(neg.microseconds, -3);
678 }
679
680 #[test]
681 fn test_interval_to_iso8601() {
682 let iv = IntervalValue::new(14, 3, 4 * 3_600_000_000 + 5 * 60_000_000 + 6 * 1_000_000);
683 assert_eq!(iv.to_iso8601(), "P1Y2M3DT4H5M6S");
684 }
685
686 #[test]
687 fn test_interval_to_postgresql() {
688 let iv = IntervalValue::new(14, 3, 4 * 3_600_000_000 + 5 * 60_000_000 + 6 * 1_000_000);
689 assert_eq!(iv.to_postgresql(), "1 year 2 mons 3 days 04:05:06");
690 }
691
692 #[test]
693 fn test_interval_zero() {
694 let iv = IntervalValue::zero();
695 assert!(iv.is_zero());
696 assert_eq!(iv.to_iso8601(), "PT0S");
697 }
698
699 #[test]
700 fn test_interval_display() {
701 let iv = IntervalValue::from_days(5);
702 assert_eq!(format!("{}", iv), "5 days");
703 }
704
705 #[test]
706 fn test_interval_years_months_components() {
707 let iv = IntervalValue::new(26, 0, 0);
708 assert_eq!(iv.years(), 2);
709 assert_eq!(iv.months_component(), 2);
710 }
711
712 #[test]
713 fn test_interval_time_components() {
714 let iv = IntervalValue::from_hours(25)
715 + IntervalValue::from_minutes(30)
716 + IntervalValue::from_seconds(45);
717 assert_eq!(iv.hours(), 25);
718 assert_eq!(iv.minutes(), 30);
719 assert_eq!(iv.seconds(), 45);
720 }
721
722 #[test]
723 fn test_interval_abs() {
724 let iv = IntervalValue::new(-1, -2, -3);
725 let abs = iv.abs();
726 assert_eq!(abs.months, 1);
727 assert_eq!(abs.days, 2);
728 assert_eq!(abs.microseconds, 3);
729 }
730
731 #[test]
732 fn test_interval_parse_weeks() {
733 let iv = IntervalValue::parse("2 weeks").unwrap();
734 assert_eq!(iv.days, 14);
735 }
736
737 #[test]
738 fn test_interval_parse_quarter() {
739 let iv = IntervalValue::parse("1 quarter").unwrap();
740 assert_eq!(iv.months, 3);
741 }
742
743 #[test]
744 fn test_interval_parse_century() {
745 let iv = IntervalValue::parse("1 century").unwrap();
746 assert_eq!(iv.months, 1200);
747 }
748
749 #[test]
750 fn test_interval_parse_millennium() {
751 let iv = IntervalValue::parse("1 millennium").unwrap();
752 assert_eq!(iv.months, 12000);
753 }
754
755 #[test]
756 fn test_interval_parse_invalid() {
757 let result = IntervalValue::parse("invalid");
758 assert!(result.is_err());
759 }
760
761 #[test]
762 fn test_interval_parse_invalid_unit() {
763 let result = IntervalValue::parse("1 foo");
764 assert!(result.is_err());
765 }
766
767 #[test]
768 fn test_interval_equality() {
769 let iv1 = IntervalValue::from_days(3);
770 let iv2 = IntervalValue::from_days(3);
771 let iv3 = IntervalValue::from_days(4);
772 assert_eq!(iv1, iv2);
773 assert_ne!(iv1, iv3);
774 }
775}