use crate::constants::{
MICROSECONDS_IN_SECOND, MINUTES_IN_HOUR, SECONDS_IN_HOUR, SECONDS_IN_MINUTE,
};
use crate::date_error::{DateError, DateErrorKind};
use core::fmt;
use std::cmp::{Ord, Ordering};
use std::str::FromStr;
use std::time::Duration;
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct Time {
pub hour: u64,
pub minute: u64,
pub second: u64,
pub microsecond: u64,
}
#[cfg(feature = "serde")]
impl serde::Serialize for Time {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let microseconds = self.to_microseconds();
serializer.serialize_u64(microseconds)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Time {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let microseconds = u64::deserialize(deserializer)?;
let seconds = microseconds / MICROSECONDS_IN_SECOND;
let micros = microseconds % MICROSECONDS_IN_SECOND;
let time = Time::from_seconds(seconds);
Ok(Time::new_with_microseconds(time.hour, time.minute, time.second, micros))
}
}
impl Time {
pub fn new(hour: u64, minute: u64, second: u64) -> Self {
Self::new_with_microseconds(hour, minute, second, 0)
}
pub fn new_with_microseconds(hour: u64, minute: u64, second: u64, microseconds: u64) -> Self {
Time {
hour,
minute,
second,
microsecond: microseconds,
}
}
pub fn valid(&self) -> bool {
self.hour < 24 && self.minute < 60 && self.second < 60 && self.microsecond < 1000000
}
pub fn to_hh_mm_string(&self) -> String {
format!("{:02}:{:02}", self.hour, self.minute)
}
pub fn from_ms_dos_time(mut ms_dos_time: u16) -> Self {
let second = (ms_dos_time & 0x1f) << 1;
ms_dos_time >>= 5;
let minute = ms_dos_time & 0x3f;
ms_dos_time >>= 6;
Time::new(ms_dos_time as u64, minute as u64, second as u64)
}
pub fn from_seconds(mut seconds: u64) -> Self {
let hour = seconds / SECONDS_IN_HOUR;
seconds %= SECONDS_IN_HOUR;
let minute = seconds / SECONDS_IN_MINUTE;
seconds %= SECONDS_IN_MINUTE;
Time::new(hour, minute, seconds)
}
pub fn to_seconds(&self) -> u64 {
self.to_minutes() * SECONDS_IN_MINUTE + self.second
}
pub fn to_minutes(&self) -> u64 {
self.hour * MINUTES_IN_HOUR + self.minute
}
pub fn to_microseconds(&self) -> u64 {
self.to_seconds() * MICROSECONDS_IN_SECOND + self.microsecond
}
pub fn to_milliseconds(&self) -> u64 {
self.to_microseconds() / 1000
}
pub fn from_minutes(mut minutes: u64) -> Self {
let hour = minutes / MINUTES_IN_HOUR;
minutes %= MINUTES_IN_HOUR;
Time::new(hour, minutes, 0)
}
pub fn from_hours(hour: u64) -> Self {
Time::new(hour, 0, 0)
}
pub fn from_duration(duration: Duration) -> Time {
Self::from_seconds(duration.as_secs())
}
pub fn to_duration(&self) -> Duration {
Duration::from_secs(self.to_seconds())
}
pub fn normalize(&self) -> Time {
let mut second = self.microsecond / MICROSECONDS_IN_SECOND + self.second;
let microseconds = self.microsecond % MICROSECONDS_IN_SECOND;
let mut minute = second / SECONDS_IN_MINUTE + self.minute;
second = second % SECONDS_IN_MINUTE;
let hour = minute / MINUTES_IN_HOUR + self.hour;
minute = minute % MINUTES_IN_HOUR;
Self::new_with_microseconds(hour, minute, second, microseconds)
}
}
impl FromStr for Time {
type Err = DateError;
fn from_str(time_str: &str) -> Result<Self, Self::Err> {
let bytes = time_str.as_bytes();
let len = bytes.len();
if len < 8 || bytes[2] != b':' || bytes[5] != b':' {
return Err(DateErrorKind::WrongTimeStringFormat.into());
}
let hour = parse_digits(&bytes[0..2])?;
let minute = parse_digits(&bytes[3..5])?;
let (second, microseconds) = if len > 8 && bytes[8] == b'.' {
let second = parse_digits(&bytes[6..8])?;
let micro_str = &bytes[9..];
let micro_len = micro_str.len().min(6);
let mut microseconds = parse_digits(µ_str[..micro_len])?;
for _ in micro_len..6 {
microseconds *= 10;
}
(second, microseconds)
} else {
let second = parse_digits(&bytes[6..8])?;
(second, 0)
};
Ok(Time::new_with_microseconds(
hour,
minute,
second,
microseconds,
))
}
}
#[inline]
fn parse_digits(bytes: &[u8]) -> Result<u64, DateError> {
let mut result = 0u64;
for &byte in bytes {
if byte < b'0' || byte > b'9' {
return Err(DateErrorKind::WrongTimeStringFormat.into());
}
result = result * 10 + (byte - b'0') as u64;
}
Ok(result)
}
impl fmt::Display for Time {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:02}:{:02}:{:02}", self.hour, self.minute, self.second)?;
if self.microsecond > 0 {
let mut divider = 1;
while self.microsecond % (divider * 10) == 0 {
divider *= 10;
}
let zeros = divider.ilog10();
let zeros = 6 - zeros as usize;
write!(f, ".{:0zeros$}", self.microsecond / divider)?;
}
Ok(())
}
}
impl fmt::Debug for Time {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl Ord for Time {
fn cmp(&self, other: &Self) -> Ordering {
if self.hour != other.hour {
return self.hour.cmp(&other.hour);
}
if self.minute != other.minute {
return self.minute.cmp(&other.minute);
}
if self.second != other.second {
return self.second.cmp(&other.second);
}
self.microsecond.cmp(&other.microsecond)
}
}
impl PartialOrd for Time {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl std::ops::Sub for Time {
type Output = Time;
fn sub(self, rhs: Self) -> Self::Output {
let mut second = self.to_seconds() - rhs.to_seconds();
let mut microseconds = self.microsecond;
if self.microsecond < rhs.microsecond {
second -= 1;
microseconds += MICROSECONDS_IN_SECOND;
}
microseconds -= rhs.microsecond;
let mut result = Self::from_seconds(second);
result.microsecond = microseconds;
result
}
}
impl std::ops::Add for Time {
type Output = Time;
fn add(self, rhs: Self) -> Self::Output {
let mut total_microseconds = self.to_microseconds() + rhs.to_microseconds();
let microseconds = total_microseconds % MICROSECONDS_IN_SECOND;
total_microseconds /= MICROSECONDS_IN_SECOND;
let seconds = total_microseconds % SECONDS_IN_MINUTE;
total_microseconds /= SECONDS_IN_MINUTE;
let minutes = total_microseconds % MINUTES_IN_HOUR;
let hours = total_microseconds / MINUTES_IN_HOUR;
Time::new_with_microseconds(hours, minutes, seconds, microseconds)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ms_dos_time() {
assert_eq!(Time::from_ms_dos_time(0x7d1c), Time::new(15, 40, 56));
}
#[test]
fn test_time_cmp() {
assert!(Time::new(2, 12, 31) < Time::new(3, 1, 1));
assert!(Time::new(2, 2, 1) > Time::new(2, 1, 31));
assert!(Time::new(2, 3, 31) > Time::new(2, 3, 30));
assert_eq!(Time::new(2, 1, 1), Time::new(2, 1, 1));
assert!(
Time::new_with_microseconds(2, 3, 31, 11) > Time::new_with_microseconds(2, 3, 31, 10)
)
}
#[test]
fn test_from_str() -> Result<(), DateError> {
assert_eq!(
Time::from_str("21:10:05")?,
Time::new_with_microseconds(21, 10, 5, 0)
);
assert_eq!(
Time::from_str("21:10:05.779325")?,
Time::new_with_microseconds(21, 10, 5, 779325)
);
assert_eq!(
Time::from_str("21:10:05.77932599")?,
Time::new_with_microseconds(21, 10, 5, 779325)
);
assert_eq!(
Time::from_str("21:10:05.77932500")?,
Time::new_with_microseconds(21, 10, 5, 779325)
);
assert_eq!(
Time::from_str("21:10:05.779000")?,
Time::new_with_microseconds(21, 10, 5, 779000)
);
assert_eq!(
Time::from_str("21:10:05.779")?,
Time::new_with_microseconds(21, 10, 5, 779000)
);
assert_eq!(
Time::from_str("21:10:05.034104")?,
Time::new_with_microseconds(21, 10, 5, 034104)
);
Ok(())
}
#[test]
fn test_to_string() {
assert_eq!(
Time::new_with_microseconds(21, 10, 5, 0).to_string(),
"21:10:05"
);
assert_eq!(
Time::new_with_microseconds(21, 10, 5, 779325).to_string(),
"21:10:05.779325"
);
assert_eq!(
Time::new_with_microseconds(21, 10, 5, 779000).to_string(),
"21:10:05.779"
);
assert_eq!(
Time::new_with_microseconds(21, 10, 5, 779).to_string(),
"21:10:05.000779"
);
assert_eq!(
Time::new_with_microseconds(21, 10, 5, 34104).to_string(),
"21:10:05.034104"
);
}
#[test]
fn test_time_to_minutes() {
assert_eq!(Time::new_with_microseconds(2, 3, 31, 11).to_minutes(), 123);
}
#[test]
fn test_time_to_seconds() {
assert_eq!(Time::new_with_microseconds(2, 3, 31, 11).to_seconds(), 7411);
}
#[test]
fn test_time_to_microseconds() {
assert_eq!(
Time::new_with_microseconds(2, 3, 31, 11).to_microseconds(),
7411000011
);
assert_eq!(
Time::new_with_microseconds(2, 3, 31, 23560).to_microseconds(),
7411023560
);
}
#[test]
fn test_time_to_milliseconds() {
assert_eq!(
Time::new_with_microseconds(2, 3, 31, 23560).to_milliseconds(),
7411023
);
}
#[test]
fn test_time_normalize() {
assert_eq!(
Time::new_with_microseconds(42, 81, 74, 76543213).normalize(),
Time::new_with_microseconds(43, 23, 30, 543213)
);
}
#[test]
fn test_time_add() {
assert_eq!(
Time::new_with_microseconds(100, 59, 59, 999999)
+ Time::new_with_microseconds(0, 0, 1, 1),
Time::new(101, 0, 1)
);
}
#[test]
fn test_time_sub() {
assert_eq!(
Time::new_with_microseconds(100, 0, 0, 0) - Time::new_with_microseconds(0, 0, 0, 1),
Time::new_with_microseconds(99, 59, 59, 999999)
);
}
#[test]
fn test_time_validation() {
assert!(Time::new(0, 0, 0).valid());
assert!(Time::new(23, 59, 59).valid());
assert!(Time::new(12, 30, 45).valid());
assert!(!Time::new(24, 0, 0).valid()); assert!(!Time::new(25, 0, 0).valid()); assert!(!Time::new(12, 60, 0).valid()); assert!(!Time::new(12, 0, 60).valid()); assert!(!Time::new(12, 61, 0).valid()); assert!(!Time::new(12, 0, 61).valid()); }
#[test]
fn test_time_from_str_invalid() {
assert!("invalid".parse::<Time>().is_err());
assert!("12:00".parse::<Time>().is_err());
assert!("12".parse::<Time>().is_err());
assert!("12-00-00".parse::<Time>().is_err());
}
#[test]
fn test_time_conversions() {
let time = Time::new(2, 30, 45);
assert_eq!(time.to_minutes(), 150); assert_eq!(time.to_seconds(), 9045); assert_eq!(time.to_milliseconds(), 9045000);
assert_eq!(Time::from_minutes(150), Time::new(2, 30, 0));
assert_eq!(Time::from_hours(2), Time::new(2, 0, 0));
assert_eq!(Time::from_seconds(9045), Time::new(2, 30, 45));
}
#[test]
fn test_time_with_microseconds() {
let time = Time::new_with_microseconds(12, 30, 45, 123456);
assert_eq!(time.to_microseconds(), 45045123456);
assert_eq!(time.to_milliseconds(), 45045123);
assert_eq!(time.to_string(), "12:30:45.123456");
assert_eq!("12:30:45.123456".parse::<Time>().unwrap(), time);
}
#[test]
fn test_time_edge_cases() {
let midnight = Time::new(0, 0, 0);
assert_eq!(midnight.to_string(), "00:00:00");
assert_eq!(midnight.to_seconds(), 0);
let end_of_day = Time::new(23, 59, 59);
assert_eq!(end_of_day.to_string(), "23:59:59");
assert_eq!(end_of_day.to_seconds(), 86399);
let time_with_micros = Time::new_with_microseconds(0, 0, 0, 999999);
assert_eq!(time_with_micros.to_string(), "00:00:00.999999");
}
#[test]
fn test_time_duration_conversion() {
let time = Time::new(1, 30, 45);
let duration = time.to_duration();
assert_eq!(duration.as_secs(), 5445);
let from_duration = Time::from_duration(duration);
assert_eq!(from_duration, time);
}
#[test]
fn test_time_hh_mm_string() {
let time = Time::new(9, 5, 30);
assert_eq!(time.to_hh_mm_string(), "09:05");
let time2 = Time::new(23, 59, 0);
assert_eq!(time2.to_hh_mm_string(), "23:59");
}
#[test]
fn test_time_arithmetic_edge_cases() {
let time1 = Time::new(23, 59, 59);
let time2 = Time::new(0, 0, 1);
let result = time1 + time2;
assert_eq!(result, Time::new(24, 0, 0));
}
#[cfg(feature = "serde")]
mod serde_tests {
use super::*;
use serde_json;
#[test]
fn test_serde_microseconds() {
let time = Time::new(0, 0, 0);
let json = serde_json::to_string(&time).unwrap();
assert_eq!(json, "0");
let deserialized: Time = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, time);
let time = Time::new(12, 30, 45);
let expected_microseconds = time.to_microseconds();
let json = serde_json::to_string(&time).unwrap();
assert_eq!(json, expected_microseconds.to_string());
let deserialized: Time = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, time);
let time = Time::new(23, 59, 59);
let json = serde_json::to_string(&time).unwrap();
let deserialized: Time = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, time);
}
#[test]
fn test_serde_with_microseconds() {
let time = Time::new_with_microseconds(12, 30, 45, 123456);
let expected_microseconds = time.to_microseconds();
let json = serde_json::to_string(&time).unwrap();
assert_eq!(json, expected_microseconds.to_string());
let deserialized: Time = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, time);
let time = Time::new_with_microseconds(0, 0, 0, 999999);
let json = serde_json::to_string(&time).unwrap();
let deserialized: Time = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, time);
}
#[test]
fn test_serde_roundtrip() {
let times = vec![
Time::new(0, 0, 0),
Time::new(12, 0, 0),
Time::new(23, 59, 59),
Time::new_with_microseconds(12, 30, 45, 123456),
Time::new_with_microseconds(0, 0, 0, 999999),
];
for time in times {
let json = serde_json::to_string(&time).unwrap();
let deserialized: Time = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, time, "Failed roundtrip for time: {}", time);
}
}
#[test]
fn test_serde_microseconds_fits_in_56_bits() {
let time = Time::new(23, 59, 59);
let microseconds = time.to_microseconds();
assert!(microseconds < (1u64 << 56), "Microseconds should fit in 56 bits");
let json = serde_json::to_string(&time).unwrap();
let deserialized: Time = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, time);
}
}
}