use std::cmp::Ordering;
use std::hash::{Hash, Hasher};
use chrono::{NaiveDateTime, NaiveTime, TimeZone as ChronoTimeZone, Utc};
use serde::{Deserialize, Serialize};
use crate::timestamp::TimeUnit;
use crate::timezone::{get_timezone, Timezone};
#[derive(Debug, Clone, Default, Copy, Serialize, Deserialize)]
pub struct Time {
value: i64,
unit: TimeUnit,
}
impl Time {
pub fn new(value: i64, unit: TimeUnit) -> Self {
Self { value, unit }
}
pub fn new_nanosecond(value: i64) -> Self {
Self {
value,
unit: TimeUnit::Nanosecond,
}
}
pub fn new_second(value: i64) -> Self {
Self {
value,
unit: TimeUnit::Second,
}
}
pub fn new_millisecond(value: i64) -> Self {
Self {
value,
unit: TimeUnit::Millisecond,
}
}
pub fn new_microsecond(value: i64) -> Self {
Self {
value,
unit: TimeUnit::Microsecond,
}
}
pub fn unit(&self) -> &TimeUnit {
&self.unit
}
pub fn value(&self) -> i64 {
self.value
}
pub fn convert_to(&self, unit: TimeUnit) -> Option<Time> {
if self.unit().factor() >= unit.factor() {
let mul = self.unit().factor() / unit.factor();
let value = self.value.checked_mul(mul as i64)?;
Some(Time::new(value, unit))
} else {
let mul = unit.factor() / self.unit().factor();
Some(Time::new(self.value.div_euclid(mul as i64), unit))
}
}
fn split(&self) -> (i64, u32) {
let sec_mul = (TimeUnit::Second.factor() / self.unit.factor()) as i64;
let nsec_mul = (self.unit.factor() / TimeUnit::Nanosecond.factor()) as i64;
let sec_div = self.value.div_euclid(sec_mul);
let sec_mod = self.value.rem_euclid(sec_mul);
let nsec = u32::try_from(sec_mod * nsec_mul).unwrap();
(sec_div, nsec)
}
pub fn to_iso8601_string(&self) -> String {
self.as_formatted_string("%H:%M:%S%.f%z", None)
}
pub fn to_system_tz_string(&self) -> String {
self.as_formatted_string("%H:%M:%S%.f", None)
}
pub fn to_timezone_aware_string(&self, tz: Option<&Timezone>) -> String {
self.as_formatted_string("%H:%M:%S%.f", tz)
}
fn as_formatted_string(self, pattern: &str, timezone: Option<&Timezone>) -> String {
if let Some(time) = self.to_chrono_time() {
let date = Utc::now().date_naive();
let datetime = NaiveDateTime::new(date, time);
match get_timezone(timezone) {
Timezone::Offset(offset) => {
format!("{}", offset.from_utc_datetime(&datetime).format(pattern))
}
Timezone::Named(tz) => {
format!("{}", tz.from_utc_datetime(&datetime).format(pattern))
}
}
} else {
format!("[Time{}: {}]", self.unit, self.value)
}
}
pub fn to_chrono_time(&self) -> Option<NaiveTime> {
let (sec, nsec) = self.split();
if let Ok(sec) = u32::try_from(sec) {
NaiveTime::from_num_seconds_from_midnight_opt(sec, nsec)
} else {
None
}
}
pub fn negative(mut self) -> Self {
self.value = -self.value;
self
}
}
impl From<i64> for Time {
fn from(v: i64) -> Self {
Self {
value: v,
unit: TimeUnit::Millisecond,
}
}
}
impl From<Time> for i64 {
fn from(t: Time) -> Self {
t.value
}
}
impl From<Time> for i32 {
fn from(t: Time) -> Self {
t.value as i32
}
}
impl From<Time> for serde_json::Value {
fn from(d: Time) -> Self {
serde_json::Value::String(d.to_iso8601_string())
}
}
impl PartialOrd for Time {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Time {
fn cmp(&self, other: &Self) -> Ordering {
if self.unit == other.unit {
return self.value.cmp(&other.value);
}
let (s_sec, s_nsec) = self.split();
let (o_sec, o_nsec) = other.split();
match s_sec.cmp(&o_sec) {
Ordering::Less => Ordering::Less,
Ordering::Greater => Ordering::Greater,
Ordering::Equal => s_nsec.cmp(&o_nsec),
}
}
}
impl PartialEq for Time {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl Eq for Time {}
impl Hash for Time {
fn hash<H: Hasher>(&self, state: &mut H) {
let (sec, nsec) = self.split();
state.write_i64(sec);
state.write_u32(nsec);
}
}
#[cfg(test)]
mod tests {
use std::collections::hash_map::DefaultHasher;
use serde_json::Value;
use super::*;
use crate::timezone::set_default_timezone;
#[test]
fn test_time() {
let t = Time::new(1, TimeUnit::Millisecond);
assert_eq!(TimeUnit::Millisecond, *t.unit());
assert_eq!(1, t.value());
assert_eq!(Time::new(1000, TimeUnit::Microsecond), t);
assert!(t > Time::new(999, TimeUnit::Microsecond));
}
#[test]
fn test_cmp_time() {
let t1 = Time::new(0, TimeUnit::Millisecond);
let t2 = Time::new(0, TimeUnit::Second);
assert_eq!(t2, t1);
let t1 = Time::new(100_100, TimeUnit::Millisecond);
let t2 = Time::new(100, TimeUnit::Second);
assert!(t1 > t2);
let t1 = Time::new(10_010_001, TimeUnit::Millisecond);
let t2 = Time::new(100, TimeUnit::Second);
assert!(t1 > t2);
let t1 = Time::new(10_010_001, TimeUnit::Nanosecond);
let t2 = Time::new(100, TimeUnit::Second);
assert!(t1 < t2);
let t1 = Time::new(i64::MAX / 1000 * 1000, TimeUnit::Millisecond);
let t2 = Time::new(i64::MAX / 1000, TimeUnit::Second);
assert_eq!(t2, t1);
let t1 = Time::new(i64::MAX, TimeUnit::Millisecond);
let t2 = Time::new(i64::MAX / 1000 + 1, TimeUnit::Second);
assert!(t2 > t1);
let t1 = Time::new(i64::MAX, TimeUnit::Millisecond);
let t2 = Time::new(i64::MAX / 1000, TimeUnit::Second);
assert!(t2 < t1);
let t1 = Time::new(10_010_001, TimeUnit::Millisecond);
let t2 = Time::new(100, TimeUnit::Second);
assert!(t1 > t2);
let t1 = Time::new(-100 * 10_001, TimeUnit::Millisecond);
let t2 = Time::new(-100, TimeUnit::Second);
assert!(t2 > t1);
}
fn check_hash_eq(t1: Time, t2: Time) {
let mut hasher = DefaultHasher::new();
t1.hash(&mut hasher);
let t1_hash = hasher.finish();
let mut hasher = DefaultHasher::new();
t2.hash(&mut hasher);
let t2_hash = hasher.finish();
assert_eq!(t2_hash, t1_hash);
}
#[test]
fn test_hash() {
check_hash_eq(
Time::new(0, TimeUnit::Millisecond),
Time::new(0, TimeUnit::Second),
);
check_hash_eq(
Time::new(1000, TimeUnit::Millisecond),
Time::new(1, TimeUnit::Second),
);
check_hash_eq(
Time::new(1_000_000, TimeUnit::Microsecond),
Time::new(1, TimeUnit::Second),
);
check_hash_eq(
Time::new(1_000_000_000, TimeUnit::Nanosecond),
Time::new(1, TimeUnit::Second),
);
}
#[test]
pub fn test_from_i64() {
let t: Time = 42.into();
assert_eq!(42, t.value());
assert_eq!(TimeUnit::Millisecond, *t.unit());
}
#[test]
fn test_to_iso8601_string() {
set_default_timezone(Some("+10:00")).unwrap();
let time_millis = 1000001;
let ts = Time::new_millisecond(time_millis);
assert_eq!("10:16:40.001+1000", ts.to_iso8601_string());
let time_millis = 1000;
let ts = Time::new_millisecond(time_millis);
assert_eq!("10:00:01+1000", ts.to_iso8601_string());
let time_millis = 1;
let ts = Time::new_millisecond(time_millis);
assert_eq!("10:00:00.001+1000", ts.to_iso8601_string());
let time_seconds = 9 * 3600;
let ts = Time::new_second(time_seconds);
assert_eq!("19:00:00+1000", ts.to_iso8601_string());
let time_seconds = 23 * 3600;
let ts = Time::new_second(time_seconds);
assert_eq!("09:00:00+1000", ts.to_iso8601_string());
}
#[test]
fn test_serialize_to_json_value() {
set_default_timezone(Some("+10:00")).unwrap();
assert_eq!(
"10:00:01+1000",
match serde_json::Value::from(Time::new(1, TimeUnit::Second)) {
Value::String(s) => s,
_ => unreachable!(),
}
);
assert_eq!(
"10:00:00.001+1000",
match serde_json::Value::from(Time::new(1, TimeUnit::Millisecond)) {
Value::String(s) => s,
_ => unreachable!(),
}
);
assert_eq!(
"10:00:00.000001+1000",
match serde_json::Value::from(Time::new(1, TimeUnit::Microsecond)) {
Value::String(s) => s,
_ => unreachable!(),
}
);
assert_eq!(
"10:00:00.000000001+1000",
match serde_json::Value::from(Time::new(1, TimeUnit::Nanosecond)) {
Value::String(s) => s,
_ => unreachable!(),
}
);
}
#[test]
fn test_to_timezone_aware_string() {
set_default_timezone(Some("+10:00")).unwrap();
assert_eq!(
"10:00:00.001",
Time::new(1, TimeUnit::Millisecond).to_timezone_aware_string(None)
);
std::env::set_var("TZ", "Asia/Shanghai");
assert_eq!(
"08:00:00.001",
Time::new(1, TimeUnit::Millisecond)
.to_timezone_aware_string(Some(&Timezone::from_tz_string("SYSTEM").unwrap()))
);
assert_eq!(
"08:00:00.001",
Time::new(1, TimeUnit::Millisecond)
.to_timezone_aware_string(Some(&Timezone::from_tz_string("+08:00").unwrap()))
);
assert_eq!(
"07:00:00.001",
Time::new(1, TimeUnit::Millisecond)
.to_timezone_aware_string(Some(&Timezone::from_tz_string("+07:00").unwrap()))
);
assert_eq!(
"23:00:00.001",
Time::new(1, TimeUnit::Millisecond)
.to_timezone_aware_string(Some(&Timezone::from_tz_string("-01:00").unwrap()))
);
assert_eq!(
"08:00:00.001",
Time::new(1, TimeUnit::Millisecond).to_timezone_aware_string(Some(
&Timezone::from_tz_string("Asia/Shanghai").unwrap()
))
);
assert_eq!(
"00:00:00.001",
Time::new(1, TimeUnit::Millisecond)
.to_timezone_aware_string(Some(&Timezone::from_tz_string("UTC").unwrap()))
);
assert_eq!(
"03:00:00.001",
Time::new(1, TimeUnit::Millisecond).to_timezone_aware_string(Some(
&Timezone::from_tz_string("Europe/Moscow").unwrap()
))
);
}
}