1use crate::time::time_unit_conversion_factor::TimeUnitConversionFactor;
2use num::ToPrimitive;
3
4#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
5pub enum TimeUnitType {
6 Millisecond,
7 Second,
8 Minute,
9 Hour,
10 Day,
11 Week,
12 Month,
13 Quarter,
14 Year,
15}
16
17impl ToString for TimeUnitType {
18 fn to_string(&self) -> String {
19 match *self {
20 TimeUnitType::Millisecond => "millisecond".to_string(),
21 TimeUnitType::Second => "second".to_string(),
22 TimeUnitType::Minute => "minute".to_string(),
23 TimeUnitType::Hour => "hour".to_string(),
24 TimeUnitType::Day => "day".to_string(),
25 TimeUnitType::Week => "week".to_string(),
26 TimeUnitType::Month => "month".to_string(),
27 TimeUnitType::Quarter => "quarter".to_string(),
28 TimeUnitType::Year => "year".to_string(),
29 }
30 }
31}
32
33impl ToPrimitive for TimeUnitType {
34 fn to_i64(&self) -> Option<i64> {
35 match *self {
36 TimeUnitType::Millisecond => Some(1),
37 TimeUnitType::Second => Some(2),
38 TimeUnitType::Minute => Some(3),
39 TimeUnitType::Hour => Some(4),
40 TimeUnitType::Day => Some(5),
41 TimeUnitType::Week => Some(6),
42 TimeUnitType::Month => Some(7),
43 TimeUnitType::Quarter => Some(8),
44 TimeUnitType::Year => Some(9),
45 }
46 }
47
48 fn to_u64(&self) -> Option<u64> {
49 match *self {
50 TimeUnitType::Millisecond => Some(1),
51 TimeUnitType::Second => Some(2),
52 TimeUnitType::Minute => Some(3),
53 TimeUnitType::Hour => Some(4),
54 TimeUnitType::Day => Some(5),
55 TimeUnitType::Week => Some(6),
56 TimeUnitType::Month => Some(7),
57 TimeUnitType::Quarter => Some(8),
58 TimeUnitType::Year => Some(9),
59 }
60 }
61}
62
63#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
64pub struct TimeUnit {
65 name: &'static str,
66 value_type: TimeUnitType,
67 value_base_type: TimeUnitType,
68 factor: TimeUnitConversionFactor,
69}
70
71impl TimeUnit {
72 pub fn name(&self) -> &'static str {
74 self.name
75 }
76
77 pub fn is_convertible_to(&self, other: &Self) -> bool {
83 self.value_base_type == other.value_base_type
84 }
85
86 pub fn is_convertible_to_milliseconds(&self) -> bool {
91 self.is_convertible_to(&TimeUnit::MILLISECOND)
92 }
93
94 pub fn base_unit(&self) -> TimeUnit {
99 if self.value_base_type == TimeUnitType::Millisecond {
100 TimeUnit::MILLISECOND
101 } else {
102 TimeUnit::MONTH
103 }
104 }
105
106 pub fn descending_units(&self) -> Vec<TimeUnit> {
107 if self.is_convertible_to_milliseconds() {
108 Vec::from(TimeUnit::DESCENDING_MS_BASED)
109 } else {
110 Vec::from(TimeUnit::DESCENDING_MONTH_BASED)
111 }
112 }
113
114 pub fn descending_units_for_display(&self) -> Vec<TimeUnit> {
115 if self.is_convertible_to_milliseconds() {
116 Vec::from(TimeUnit::DESCENDING_MS_BASED_FOR_DISPLAY)
117 } else {
118 Vec::from(TimeUnit::DESCENDING_MONTH_BASED_FOR_DISPLAY)
119 }
120 }
121
122 pub(crate) fn factor(&self) -> i64 {
123 self.factor.value
124 }
125
126 pub fn next_finer_unit(&self) -> Option<TimeUnit> {
127 self
128 .descending_units()
129 .iter()
130 .zip(0..self.descending_units().len())
131 .find(|(t, _)| *t == self)
132 .and_then(|(_, i)| {
133 if i == self.descending_units().len() - 1 {
134 None
135 } else {
136 self.descending_units().get(i + 1).cloned()
137 }
138 })
139 }
140
141 pub(crate) fn to_string(&self, quantity: i64) -> String {
142 format!(
143 "{} {}{}",
144 quantity,
145 self.value_type.to_string(),
146 if quantity == 1 { "" } else { "s" }
147 )
148 }
149}
150
151impl TimeUnit {
152 const DESCENDING_MS_BASED: [TimeUnit; 6] = [
153 TimeUnit::WEEK,
154 TimeUnit::DAY,
155 TimeUnit::HOUR,
156 TimeUnit::MINUTE,
157 TimeUnit::SECOND,
158 TimeUnit::MILLISECOND,
159 ];
160
161 const DESCENDING_MS_BASED_FOR_DISPLAY: [TimeUnit; 5] = [
162 TimeUnit::DAY,
163 TimeUnit::HOUR,
164 TimeUnit::MINUTE,
165 TimeUnit::SECOND,
166 TimeUnit::MILLISECOND,
167 ];
168
169 const DESCENDING_MONTH_BASED: [TimeUnit; 3] =
170 [TimeUnit::YEAR, TimeUnit::QUARTER, TimeUnit::MONTH];
171
172 const DESCENDING_MONTH_BASED_FOR_DISPLAY: [TimeUnit; 2] = [TimeUnit::YEAR, TimeUnit::MONTH];
173
174 pub const MILLISECOND: TimeUnit = TimeUnit {
175 name: "millisecond",
176 value_type: TimeUnitType::Millisecond,
177 value_base_type: TimeUnitType::Millisecond,
178 factor: TimeUnitConversionFactor::IDENTICAL,
179 };
180
181 pub const SECOND: TimeUnit = TimeUnit {
182 name: "second",
183 value_type: TimeUnitType::Second,
184 value_base_type: TimeUnitType::Millisecond,
185 factor: TimeUnitConversionFactor::MILLISECONDS_PER_SECOND,
186 };
187
188 pub const MINUTE: TimeUnit = TimeUnit {
189 name: "minute",
190 value_type: TimeUnitType::Minute,
191 value_base_type: TimeUnitType::Millisecond,
192 factor: TimeUnitConversionFactor::MILLISECONDS_PER_MINUTE,
193 };
194
195 pub const HOUR: TimeUnit = TimeUnit {
196 name: "hour",
197 value_type: TimeUnitType::Hour,
198 value_base_type: TimeUnitType::Millisecond,
199 factor: TimeUnitConversionFactor::MILLISECONDS_PER_HOUR,
200 };
201
202 pub const DAY: TimeUnit = TimeUnit {
203 name: "day",
204 value_type: TimeUnitType::Day,
205 value_base_type: TimeUnitType::Millisecond,
206 factor: TimeUnitConversionFactor::MILLISECONDS_PER_DAY,
207 };
208
209 pub const WEEK: TimeUnit = TimeUnit {
210 name: "week",
211 value_type: TimeUnitType::Week,
212 value_base_type: TimeUnitType::Millisecond,
213 factor: TimeUnitConversionFactor::MILLISECONDS_PER_WEEK,
214 };
215
216 pub const MONTH: TimeUnit = TimeUnit {
217 name: "month",
218 value_type: TimeUnitType::Month,
219 value_base_type: TimeUnitType::Month,
220 factor: TimeUnitConversionFactor::IDENTICAL,
221 };
222
223 pub const QUARTER: TimeUnit = TimeUnit {
224 name: "quarter",
225 value_type: TimeUnitType::Quarter,
226 value_base_type: TimeUnitType::Month,
227 factor: TimeUnitConversionFactor::MONTHS_PER_QUARTER,
228 };
229
230 pub const YEAR: TimeUnit = TimeUnit {
231 name: "year",
232 value_type: TimeUnitType::Year,
233 value_base_type: TimeUnitType::Month,
234 factor: TimeUnitConversionFactor::MONTHS_PER_YEAR,
235 };
236}
237
238#[cfg(test)]
239mod tests {
240 use crate::time::TimeUnit;
241 use std::cmp::Ordering;
242
243 #[test]
244 fn test_convertible_to_milliseconds() {
245 assert_eq!(TimeUnit::MILLISECOND.is_convertible_to_milliseconds(), true);
246 assert_eq!(TimeUnit::HOUR.is_convertible_to_milliseconds(), true);
247 assert_eq!(TimeUnit::DAY.is_convertible_to_milliseconds(), true);
248 assert_eq!(TimeUnit::WEEK.is_convertible_to_milliseconds(), true);
249 assert_eq!(TimeUnit::MONTH.is_convertible_to_milliseconds(), false);
250 assert_eq!(TimeUnit::YEAR.is_convertible_to_milliseconds(), false);
251 }
252
253 #[test]
254 fn test_comparison() {
255 assert_eq!(TimeUnit::HOUR.cmp(&TimeUnit::HOUR), Ordering::Equal);
256 assert_eq!(TimeUnit::HOUR.cmp(&TimeUnit::MILLISECOND), Ordering::Less);
257 assert_eq!(
258 TimeUnit::MILLISECOND.cmp(&TimeUnit::HOUR),
259 Ordering::Greater
260 );
261 assert_eq!(TimeUnit::DAY.cmp(&TimeUnit::HOUR), Ordering::Less);
262 assert_eq!(TimeUnit::HOUR.cmp(&TimeUnit::DAY), Ordering::Greater);
263
264 assert_eq!(TimeUnit::MONTH.cmp(&TimeUnit::DAY), Ordering::Greater);
265 assert_eq!(TimeUnit::DAY.cmp(&TimeUnit::MONTH), Ordering::Less);
266 assert_eq!(TimeUnit::QUARTER.cmp(&TimeUnit::HOUR), Ordering::Greater);
267
268 assert_eq!(TimeUnit::MONTH.cmp(&TimeUnit::MONTH), Ordering::Equal);
269 assert_eq!(TimeUnit::QUARTER.cmp(&TimeUnit::YEAR), Ordering::Less);
270 assert_eq!(TimeUnit::YEAR.cmp(&TimeUnit::QUARTER), Ordering::Greater);
271 }
272
273 #[test]
274 fn test_is_convertible_to() {
275 assert_eq!(TimeUnit::HOUR.is_convertible_to(&TimeUnit::MINUTE), true);
276 assert_eq!(TimeUnit::MINUTE.is_convertible_to(&TimeUnit::HOUR), true);
277 assert_eq!(TimeUnit::YEAR.is_convertible_to(&TimeUnit::MONTH), true);
278 assert_eq!(TimeUnit::MONTH.is_convertible_to(&TimeUnit::YEAR), true);
279 assert_eq!(TimeUnit::MONTH.is_convertible_to(&TimeUnit::HOUR), false);
280 assert_eq!(TimeUnit::HOUR.is_convertible_to(&TimeUnit::MONTH), false);
281 }
282
283 #[test]
284 fn test_next_finer_unit() {
285 assert_eq!(TimeUnit::HOUR.next_finer_unit(), Some(TimeUnit::MINUTE));
286 assert_eq!(TimeUnit::QUARTER.next_finer_unit(), Some(TimeUnit::MONTH));
287 }
288}