baseunits_rs/time/
time_unit.rs

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  /// このユニットの名前を返す。
73  pub fn name(&self) -> &'static str {
74    self.name
75  }
76
77  /// この単位で表される値を、指定した単位に変換できるかどうかを検証する。
78  /// 例えば、分単位はミリ秒単位に変換できるが、四半期単位は(一ヶ月の長さが毎月異なるため)日単位に変換できない。
79  ///
80  /// `param` other 変換先単位
81  /// `return` 変換できる場合は`true`、そうでない場合は`false`
82  pub fn is_convertible_to(&self, other: &Self) -> bool {
83    self.value_base_type == other.value_base_type
84  }
85
86  /// この単位で表される値を、ミリ秒単位に変換できるかどうかを検証する。
87  /// 例えば、分単位はミリ秒単位に変換できるが、四半期単位は(一ヶ月の長さが毎月異なるため)ミリ秒単位に変換できない。
88  ///
89  /// `return` 変換できる場合は`true`、そうでない場合は`false`
90  pub fn is_convertible_to_milliseconds(&self) -> bool {
91    self.is_convertible_to(&TimeUnit::MILLISECOND)
92  }
93
94  /// この単位の計数の基数とすることができる最小の単位を取得する。
95  /// 例えば、分単位はミリ秒単位で計数できるが、四半期単位は(一ヶ月の長さが毎月異なるため)月単位までしか計数できない。
96  ///
97  /// `return` この単位の計数の基数とすることができる最小の単位
98  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}