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::{Timezone, get_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 pub fn checked_negative(mut self) -> Option<Self> {
150 self.value = self.value.checked_neg()?;
151 Some(self)
152 }
153}
154
155impl From<i64> for Time {
156 fn from(v: i64) -> Self {
157 Self {
158 value: v,
159 unit: TimeUnit::Millisecond,
160 }
161 }
162}
163
164impl From<Time> for i64 {
165 fn from(t: Time) -> Self {
166 t.value
167 }
168}
169
170impl From<Time> for i32 {
171 fn from(t: Time) -> Self {
172 t.value as i32
173 }
174}
175
176impl From<Time> for serde_json::Value {
177 fn from(d: Time) -> Self {
178 serde_json::Value::String(d.to_iso8601_string())
179 }
180}
181
182impl PartialOrd for Time {
183 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
184 Some(self.cmp(other))
185 }
186}
187
188impl Ord for Time {
189 fn cmp(&self, other: &Self) -> Ordering {
190 if self.unit == other.unit {
192 return self.value.cmp(&other.value);
193 }
194
195 let (s_sec, s_nsec) = self.split();
196 let (o_sec, o_nsec) = other.split();
197 match s_sec.cmp(&o_sec) {
198 Ordering::Less => Ordering::Less,
199 Ordering::Greater => Ordering::Greater,
200 Ordering::Equal => s_nsec.cmp(&o_nsec),
201 }
202 }
203}
204
205impl PartialEq for Time {
206 fn eq(&self, other: &Self) -> bool {
207 self.cmp(other) == Ordering::Equal
208 }
209}
210
211impl Eq for Time {}
212
213impl Hash for Time {
214 fn hash<H: Hasher>(&self, state: &mut H) {
215 let (sec, nsec) = self.split();
216 state.write_i64(sec);
217 state.write_u32(nsec);
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use std::collections::hash_map::DefaultHasher;
224
225 use serde_json::Value;
226
227 use super::*;
228 use crate::timezone::set_default_timezone;
229
230 #[test]
231 fn test_time() {
232 let t = Time::new(1, TimeUnit::Millisecond);
233 assert_eq!(TimeUnit::Millisecond, *t.unit());
234 assert_eq!(1, t.value());
235 assert_eq!(Time::new(1000, TimeUnit::Microsecond), t);
236 assert!(t > Time::new(999, TimeUnit::Microsecond));
237 }
238
239 #[test]
240 fn test_cmp_time() {
241 let t1 = Time::new(0, TimeUnit::Millisecond);
242 let t2 = Time::new(0, TimeUnit::Second);
243 assert_eq!(t2, t1);
244
245 let t1 = Time::new(100_100, TimeUnit::Millisecond);
246 let t2 = Time::new(100, TimeUnit::Second);
247 assert!(t1 > t2);
248
249 let t1 = Time::new(10_010_001, TimeUnit::Millisecond);
250 let t2 = Time::new(100, TimeUnit::Second);
251 assert!(t1 > t2);
252
253 let t1 = Time::new(10_010_001, TimeUnit::Nanosecond);
254 let t2 = Time::new(100, TimeUnit::Second);
255 assert!(t1 < t2);
256
257 let t1 = Time::new(i64::MAX / 1000 * 1000, TimeUnit::Millisecond);
258 let t2 = Time::new(i64::MAX / 1000, TimeUnit::Second);
259 assert_eq!(t2, t1);
260
261 let t1 = Time::new(i64::MAX, TimeUnit::Millisecond);
262 let t2 = Time::new(i64::MAX / 1000 + 1, TimeUnit::Second);
263 assert!(t2 > t1);
264
265 let t1 = Time::new(i64::MAX, TimeUnit::Millisecond);
266 let t2 = Time::new(i64::MAX / 1000, TimeUnit::Second);
267 assert!(t2 < t1);
268
269 let t1 = Time::new(10_010_001, TimeUnit::Millisecond);
270 let t2 = Time::new(100, TimeUnit::Second);
271 assert!(t1 > t2);
272
273 let t1 = Time::new(-100 * 10_001, TimeUnit::Millisecond);
274 let t2 = Time::new(-100, TimeUnit::Second);
275 assert!(t2 > t1);
276 }
277
278 fn check_hash_eq(t1: Time, t2: Time) {
279 let mut hasher = DefaultHasher::new();
280 t1.hash(&mut hasher);
281 let t1_hash = hasher.finish();
282
283 let mut hasher = DefaultHasher::new();
284 t2.hash(&mut hasher);
285 let t2_hash = hasher.finish();
286 assert_eq!(t2_hash, t1_hash);
287 }
288
289 #[test]
290 fn test_hash() {
291 check_hash_eq(
292 Time::new(0, TimeUnit::Millisecond),
293 Time::new(0, TimeUnit::Second),
294 );
295 check_hash_eq(
296 Time::new(1000, TimeUnit::Millisecond),
297 Time::new(1, TimeUnit::Second),
298 );
299 check_hash_eq(
300 Time::new(1_000_000, TimeUnit::Microsecond),
301 Time::new(1, TimeUnit::Second),
302 );
303 check_hash_eq(
304 Time::new(1_000_000_000, TimeUnit::Nanosecond),
305 Time::new(1, TimeUnit::Second),
306 );
307 }
308
309 #[test]
310 pub fn test_from_i64() {
311 let t: Time = 42.into();
312 assert_eq!(42, t.value());
313 assert_eq!(TimeUnit::Millisecond, *t.unit());
314 }
315
316 #[test]
317 fn test_to_iso8601_string() {
318 set_default_timezone(Some("+10:00")).unwrap();
319 let time_millis = 1000001;
320 let ts = Time::new_millisecond(time_millis);
321 assert_eq!("10:16:40.001+1000", ts.to_iso8601_string());
322
323 let time_millis = 1000;
324 let ts = Time::new_millisecond(time_millis);
325 assert_eq!("10:00:01+1000", ts.to_iso8601_string());
326
327 let time_millis = 1;
328 let ts = Time::new_millisecond(time_millis);
329 assert_eq!("10:00:00.001+1000", ts.to_iso8601_string());
330
331 let time_seconds = 9 * 3600;
332 let ts = Time::new_second(time_seconds);
333 assert_eq!("19:00:00+1000", ts.to_iso8601_string());
334
335 let time_seconds = 23 * 3600;
336 let ts = Time::new_second(time_seconds);
337 assert_eq!("09:00:00+1000", ts.to_iso8601_string());
338 }
339
340 #[test]
341 fn test_serialize_to_json_value() {
342 set_default_timezone(Some("+10:00")).unwrap();
343 assert_eq!(
344 "10:00:01+1000",
345 match serde_json::Value::from(Time::new(1, TimeUnit::Second)) {
346 Value::String(s) => s,
347 _ => unreachable!(),
348 }
349 );
350
351 assert_eq!(
352 "10:00:00.001+1000",
353 match serde_json::Value::from(Time::new(1, TimeUnit::Millisecond)) {
354 Value::String(s) => s,
355 _ => unreachable!(),
356 }
357 );
358
359 assert_eq!(
360 "10:00:00.000001+1000",
361 match serde_json::Value::from(Time::new(1, TimeUnit::Microsecond)) {
362 Value::String(s) => s,
363 _ => unreachable!(),
364 }
365 );
366
367 assert_eq!(
368 "10:00:00.000000001+1000",
369 match serde_json::Value::from(Time::new(1, TimeUnit::Nanosecond)) {
370 Value::String(s) => s,
371 _ => unreachable!(),
372 }
373 );
374 }
375
376 #[test]
377 fn test_to_timezone_aware_string() {
378 set_default_timezone(Some("+10:00")).unwrap();
379
380 assert_eq!(
381 "10:00:00.001",
382 Time::new(1, TimeUnit::Millisecond).to_timezone_aware_string(None)
383 );
384 unsafe {
385 std::env::set_var("TZ", "Asia/Shanghai");
386 }
387 assert_eq!(
388 "08:00:00.001",
389 Time::new(1, TimeUnit::Millisecond)
390 .to_timezone_aware_string(Some(&Timezone::from_tz_string("SYSTEM").unwrap()))
391 );
392 assert_eq!(
393 "08:00:00.001",
394 Time::new(1, TimeUnit::Millisecond)
395 .to_timezone_aware_string(Some(&Timezone::from_tz_string("+08:00").unwrap()))
396 );
397 assert_eq!(
398 "07:00:00.001",
399 Time::new(1, TimeUnit::Millisecond)
400 .to_timezone_aware_string(Some(&Timezone::from_tz_string("+07:00").unwrap()))
401 );
402 assert_eq!(
403 "23:00:00.001",
404 Time::new(1, TimeUnit::Millisecond)
405 .to_timezone_aware_string(Some(&Timezone::from_tz_string("-01:00").unwrap()))
406 );
407 assert_eq!(
408 "08:00:00.001",
409 Time::new(1, TimeUnit::Millisecond).to_timezone_aware_string(Some(
410 &Timezone::from_tz_string("Asia/Shanghai").unwrap()
411 ))
412 );
413 assert_eq!(
414 "00:00:00.001",
415 Time::new(1, TimeUnit::Millisecond)
416 .to_timezone_aware_string(Some(&Timezone::from_tz_string("UTC").unwrap()))
417 );
418 assert_eq!(
419 "03:00:00.001",
420 Time::new(1, TimeUnit::Millisecond).to_timezone_aware_string(Some(
421 &Timezone::from_tz_string("Europe/Moscow").unwrap()
422 ))
423 );
424 }
425}