1#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
4#[repr(C)]
5pub struct Interval {
6 pub months: i32,
7 pub days: i32,
8 pub micros: i64,
9}
10
11impl Interval {
12 pub const ZERO: Self = Self {
13 months: 0,
14 days: 0,
15 micros: 0,
16 };
17
18 pub const fn new(months: i32, days: i32, micros: i64) -> Self {
19 Self {
20 months,
21 days,
22 micros,
23 }
24 }
25
26 pub const fn from_months(months: i32) -> Self {
27 Self::new(months, 0, 0)
28 }
29
30 pub const fn from_days(days: i32) -> Self {
31 Self::new(0, days, 0)
32 }
33
34 pub const fn from_micros(micros: i64) -> Self {
35 Self::new(0, 0, micros)
36 }
37}
38
39impl std::fmt::Display for Interval {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 let mut parts = Vec::new();
42 if self.months != 0 {
43 let years = self.months / 12;
44 let months = self.months % 12;
45 if years != 0 {
46 parts.push(format!(
47 "{years} year{}",
48 if years.abs() != 1 { "s" } else { "" }
49 ));
50 }
51 if months != 0 {
52 parts.push(format!(
53 "{months} month{}",
54 if months.abs() != 1 { "s" } else { "" }
55 ));
56 }
57 }
58 if self.days != 0 {
59 parts.push(format!(
60 "{} day{}",
61 self.days,
62 if self.days.abs() != 1 { "s" } else { "" }
63 ));
64 }
65 if self.micros != 0 || parts.is_empty() {
66 let total_secs = self.micros / 1_000_000;
67 let remaining_micros = self.micros % 1_000_000;
68 let hours = total_secs / 3600;
69 let mins = (total_secs % 3600) / 60;
70 let secs = total_secs % 60;
71 if remaining_micros != 0 {
72 parts.push(format!(
73 "{hours:02}:{mins:02}:{secs:02}.{remaining_micros:06}"
74 ));
75 } else {
76 parts.push(format!("{hours:02}:{mins:02}:{secs:02}"));
77 }
78 }
79 write!(f, "{}", parts.join(" "))
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn size_is_16_bytes() {
89 assert_eq!(std::mem::size_of::<Interval>(), 16);
90 }
91
92 #[test]
93 fn zero() {
94 let z = Interval::ZERO;
95 assert_eq!(z.months, 0);
96 assert_eq!(z.days, 0);
97 assert_eq!(z.micros, 0);
98 }
99
100 #[test]
101 fn constructors() {
102 let m = Interval::from_months(14);
103 assert_eq!(m.months, 14);
104 assert_eq!(m.days, 0);
105
106 let d = Interval::from_days(30);
107 assert_eq!(d.days, 30);
108
109 let u = Interval::from_micros(1_000_000);
110 assert_eq!(u.micros, 1_000_000);
111 }
112
113 #[test]
114 fn display_zero() {
115 assert_eq!(Interval::ZERO.to_string(), "00:00:00");
116 }
117
118 #[test]
119 fn display_complex() {
120 let iv = Interval::new(14, 5, 3_723_000_000);
121 let s = iv.to_string();
122 assert!(s.contains("1 year"));
123 assert!(s.contains("2 months"));
124 assert!(s.contains("5 days"));
125 assert!(s.contains("01:02:03"));
126 }
127
128 #[test]
129 fn equality_and_hash() {
130 let a = Interval::new(1, 2, 3);
131 let b = Interval::new(1, 2, 3);
132 let c = Interval::new(1, 2, 4);
133 assert_eq!(a, b);
134 assert_ne!(a, c);
135 }
136}