1use std::cmp::Ordering;
16use std::hash::{Hash, Hasher};
17
18use chrono::{NaiveDateTime, NaiveTime, TimeZone as ChronoTimeZone, Utc};
19use serde::{Deserialize, Serialize};
20
21use crate::timestamp::TimeUnit;
22use crate::timezone::{get_timezone, Timezone};
23
24#[derive(Debug, Clone, Default, Copy, Serialize, Deserialize)]
26pub struct Time {
27 value: i64,
28 unit: TimeUnit,
29}
30
31impl Time {
32 pub fn new(value: i64, unit: TimeUnit) -> Self {
34 Self { value, unit }
35 }
36
37 pub fn new_nanosecond(value: i64) -> Self {
39 Self {
40 value,
41 unit: TimeUnit::Nanosecond,
42 }
43 }
44
45 pub fn new_second(value: i64) -> Self {
47 Self {
48 value,
49 unit: TimeUnit::Second,
50 }
51 }
52
53 pub fn new_millisecond(value: i64) -> Self {
55 Self {
56 value,
57 unit: TimeUnit::Millisecond,
58 }
59 }
60
61 pub fn new_microsecond(value: i64) -> Self {
63 Self {
64 value,
65 unit: TimeUnit::Microsecond,
66 }
67 }
68
69 pub fn unit(&self) -> &TimeUnit {
71 &self.unit
72 }
73
74 pub fn value(&self) -> i64 {
76 self.value
77 }
78
79 pub fn convert_to(&self, unit: TimeUnit) -> Option<Time> {
82 if self.unit().factor() >= unit.factor() {
83 let mul = self.unit().factor() / unit.factor();
84 let value = self.value.checked_mul(mul as i64)?;
85 Some(Time::new(value, unit))
86 } else {
87 let mul = unit.factor() / self.unit().factor();
88 Some(Time::new(self.value.div_euclid(mul as i64), unit))
89 }
90 }
91
92 fn split(&self) -> (i64, u32) {
95 let sec_mul = (TimeUnit::Second.factor() / self.unit.factor()) as i64;
96 let nsec_mul = (self.unit.factor() / TimeUnit::Nanosecond.factor()) as i64;
97
98 let sec_div = self.value.div_euclid(sec_mul);
99 let sec_mod = self.value.rem_euclid(sec_mul);
100 let nsec = u32::try_from(sec_mod * nsec_mul).unwrap();
102 (sec_div, nsec)
103 }
104
105 pub fn to_iso8601_string(&self) -> String {
108 self.as_formatted_string("%H:%M:%S%.f%z", None)
109 }
110
111 pub fn to_timezone_aware_string(&self, tz: Option<&Timezone>) -> String {
114 self.as_formatted_string("%H:%M:%S%.f", tz)
115 }
116
117 fn as_formatted_string(self, pattern: &str, timezone: Option<&Timezone>) -> String {
118 if let Some(time) = self.to_chrono_time() {
119 let date = Utc::now().date_naive();
120 let datetime = NaiveDateTime::new(date, time);
121 match get_timezone(timezone) {
122 Timezone::Offset(offset) => {
123 format!("{}", offset.from_utc_datetime(&datetime).format(pattern))
124 }
125 Timezone::Named(tz) => {
126 format!("{}", tz.from_utc_datetime(&datetime).format(pattern))
127 }
128 }
129 } else {
130 format!("[Time{}: {}]", self.unit, self.value)
131 }
132 }
133
134 pub fn to_chrono_time(&self) -> Option<NaiveTime> {
136 let (sec, nsec) = self.split();
137 if let Ok(sec) = u32::try_from(sec) {
138 NaiveTime::from_num_seconds_from_midnight_opt(sec, nsec)
139 } else {
140 None
141 }
142 }
143
144 pub fn negative(mut self) -> Self {
145 self.value = -self.value;
146 self
147 }
148}
149
150impl From<i64> for Time {
151 fn from(v: i64) -> Self {
152 Self {
153 value: v,
154 unit: TimeUnit::Millisecond,
155 }
156 }
157}
158
159impl From<Time> for i64 {
160 fn from(t: Time) -> Self {
161 t.value
162 }
163}
164
165impl From<Time> for i32 {
166 fn from(t: Time) -> Self {
167 t.value as i32
168 }
169}
170
171impl From<Time> for serde_json::Value {
172 fn from(d: Time) -> Self {
173 serde_json::Value::String(d.to_iso8601_string())
174 }
175}
176
177impl PartialOrd for Time {
178 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
179 Some(self.cmp(other))
180 }
181}
182
183impl Ord for Time {
184 fn cmp(&self, other: &Self) -> Ordering {
185 if self.unit == other.unit {
187 return self.value.cmp(&other.value);
188 }
189
190 let (s_sec, s_nsec) = self.split();
191 let (o_sec, o_nsec) = other.split();
192 match s_sec.cmp(&o_sec) {
193 Ordering::Less => Ordering::Less,
194 Ordering::Greater => Ordering::Greater,
195 Ordering::Equal => s_nsec.cmp(&o_nsec),
196 }
197 }
198}
199
200impl PartialEq for Time {
201 fn eq(&self, other: &Self) -> bool {
202 self.cmp(other) == Ordering::Equal
203 }
204}
205
206impl Eq for Time {}
207
208impl Hash for Time {
209 fn hash<H: Hasher>(&self, state: &mut H) {
210 let (sec, nsec) = self.split();
211 state.write_i64(sec);
212 state.write_u32(nsec);
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use std::collections::hash_map::DefaultHasher;
219
220 use serde_json::Value;
221
222 use super::*;
223 use crate::timezone::set_default_timezone;
224
225 #[test]
226 fn test_time() {
227 let t = Time::new(1, TimeUnit::Millisecond);
228 assert_eq!(TimeUnit::Millisecond, *t.unit());
229 assert_eq!(1, t.value());
230 assert_eq!(Time::new(1000, TimeUnit::Microsecond), t);
231 assert!(t > Time::new(999, TimeUnit::Microsecond));
232 }
233
234 #[test]
235 fn test_cmp_time() {
236 let t1 = Time::new(0, TimeUnit::Millisecond);
237 let t2 = Time::new(0, TimeUnit::Second);
238 assert_eq!(t2, t1);
239
240 let t1 = Time::new(100_100, TimeUnit::Millisecond);
241 let t2 = Time::new(100, TimeUnit::Second);
242 assert!(t1 > t2);
243
244 let t1 = Time::new(10_010_001, TimeUnit::Millisecond);
245 let t2 = Time::new(100, TimeUnit::Second);
246 assert!(t1 > t2);
247
248 let t1 = Time::new(10_010_001, TimeUnit::Nanosecond);
249 let t2 = Time::new(100, TimeUnit::Second);
250 assert!(t1 < t2);
251
252 let t1 = Time::new(i64::MAX / 1000 * 1000, TimeUnit::Millisecond);
253 let t2 = Time::new(i64::MAX / 1000, TimeUnit::Second);
254 assert_eq!(t2, t1);
255
256 let t1 = Time::new(i64::MAX, TimeUnit::Millisecond);
257 let t2 = Time::new(i64::MAX / 1000 + 1, TimeUnit::Second);
258 assert!(t2 > t1);
259
260 let t1 = Time::new(i64::MAX, TimeUnit::Millisecond);
261 let t2 = Time::new(i64::MAX / 1000, TimeUnit::Second);
262 assert!(t2 < t1);
263
264 let t1 = Time::new(10_010_001, TimeUnit::Millisecond);
265 let t2 = Time::new(100, TimeUnit::Second);
266 assert!(t1 > t2);
267
268 let t1 = Time::new(-100 * 10_001, TimeUnit::Millisecond);
269 let t2 = Time::new(-100, TimeUnit::Second);
270 assert!(t2 > t1);
271 }
272
273 fn check_hash_eq(t1: Time, t2: Time) {
274 let mut hasher = DefaultHasher::new();
275 t1.hash(&mut hasher);
276 let t1_hash = hasher.finish();
277
278 let mut hasher = DefaultHasher::new();
279 t2.hash(&mut hasher);
280 let t2_hash = hasher.finish();
281 assert_eq!(t2_hash, t1_hash);
282 }
283
284 #[test]
285 fn test_hash() {
286 check_hash_eq(
287 Time::new(0, TimeUnit::Millisecond),
288 Time::new(0, TimeUnit::Second),
289 );
290 check_hash_eq(
291 Time::new(1000, TimeUnit::Millisecond),
292 Time::new(1, TimeUnit::Second),
293 );
294 check_hash_eq(
295 Time::new(1_000_000, TimeUnit::Microsecond),
296 Time::new(1, TimeUnit::Second),
297 );
298 check_hash_eq(
299 Time::new(1_000_000_000, TimeUnit::Nanosecond),
300 Time::new(1, TimeUnit::Second),
301 );
302 }
303
304 #[test]
305 pub fn test_from_i64() {
306 let t: Time = 42.into();
307 assert_eq!(42, t.value());
308 assert_eq!(TimeUnit::Millisecond, *t.unit());
309 }
310
311 #[test]
312 fn test_to_iso8601_string() {
313 set_default_timezone(Some("+10:00")).unwrap();
314 let time_millis = 1000001;
315 let ts = Time::new_millisecond(time_millis);
316 assert_eq!("10:16:40.001+1000", ts.to_iso8601_string());
317
318 let time_millis = 1000;
319 let ts = Time::new_millisecond(time_millis);
320 assert_eq!("10:00:01+1000", ts.to_iso8601_string());
321
322 let time_millis = 1;
323 let ts = Time::new_millisecond(time_millis);
324 assert_eq!("10:00:00.001+1000", ts.to_iso8601_string());
325
326 let time_seconds = 9 * 3600;
327 let ts = Time::new_second(time_seconds);
328 assert_eq!("19:00:00+1000", ts.to_iso8601_string());
329
330 let time_seconds = 23 * 3600;
331 let ts = Time::new_second(time_seconds);
332 assert_eq!("09:00:00+1000", ts.to_iso8601_string());
333 }
334
335 #[test]
336 fn test_serialize_to_json_value() {
337 set_default_timezone(Some("+10:00")).unwrap();
338 assert_eq!(
339 "10:00:01+1000",
340 match serde_json::Value::from(Time::new(1, TimeUnit::Second)) {
341 Value::String(s) => s,
342 _ => unreachable!(),
343 }
344 );
345
346 assert_eq!(
347 "10:00:00.001+1000",
348 match serde_json::Value::from(Time::new(1, TimeUnit::Millisecond)) {
349 Value::String(s) => s,
350 _ => unreachable!(),
351 }
352 );
353
354 assert_eq!(
355 "10:00:00.000001+1000",
356 match serde_json::Value::from(Time::new(1, TimeUnit::Microsecond)) {
357 Value::String(s) => s,
358 _ => unreachable!(),
359 }
360 );
361
362 assert_eq!(
363 "10:00:00.000000001+1000",
364 match serde_json::Value::from(Time::new(1, TimeUnit::Nanosecond)) {
365 Value::String(s) => s,
366 _ => unreachable!(),
367 }
368 );
369 }
370
371 #[test]
372 fn test_to_timezone_aware_string() {
373 set_default_timezone(Some("+10:00")).unwrap();
374
375 assert_eq!(
376 "10:00:00.001",
377 Time::new(1, TimeUnit::Millisecond).to_timezone_aware_string(None)
378 );
379 std::env::set_var("TZ", "Asia/Shanghai");
380 assert_eq!(
381 "08:00:00.001",
382 Time::new(1, TimeUnit::Millisecond)
383 .to_timezone_aware_string(Some(&Timezone::from_tz_string("SYSTEM").unwrap()))
384 );
385 assert_eq!(
386 "08:00:00.001",
387 Time::new(1, TimeUnit::Millisecond)
388 .to_timezone_aware_string(Some(&Timezone::from_tz_string("+08:00").unwrap()))
389 );
390 assert_eq!(
391 "07:00:00.001",
392 Time::new(1, TimeUnit::Millisecond)
393 .to_timezone_aware_string(Some(&Timezone::from_tz_string("+07:00").unwrap()))
394 );
395 assert_eq!(
396 "23:00:00.001",
397 Time::new(1, TimeUnit::Millisecond)
398 .to_timezone_aware_string(Some(&Timezone::from_tz_string("-01:00").unwrap()))
399 );
400 assert_eq!(
401 "08:00:00.001",
402 Time::new(1, TimeUnit::Millisecond).to_timezone_aware_string(Some(
403 &Timezone::from_tz_string("Asia/Shanghai").unwrap()
404 ))
405 );
406 assert_eq!(
407 "00:00:00.001",
408 Time::new(1, TimeUnit::Millisecond)
409 .to_timezone_aware_string(Some(&Timezone::from_tz_string("UTC").unwrap()))
410 );
411 assert_eq!(
412 "03:00:00.001",
413 Time::new(1, TimeUnit::Millisecond).to_timezone_aware_string(Some(
414 &Timezone::from_tz_string("Europe/Moscow").unwrap()
415 ))
416 );
417 }
418}