use std::ops::Sub;
use thiserror::Error;
use time::{Duration, Time, ext::NumericalDuration};
#[derive(Error, Debug)]
pub enum TimeError {
#[error("Invalid time format. Expected HH:MM or H:MM, got: {0}")]
InvalidFormat(String),
#[error("Invalid time components: {0}:{1}")]
InvalidComponents(u8, u8),
#[error("Failed to reset seconds for time: {0:?}")]
ResetSecondsError(Time),
#[error("Invalid seconds value: {0}")]
InvalidSeconds(i64),
#[error("Invalid alignment unit: {0}")]
InvalidAlignmentUnit(u64),
#[error("Failed to add time: {0:?}")]
AddTimeError(Time),
}
pub trait ExtTime {
fn to_shorten(&self) -> String;
fn from_str(time_str: &str) -> Result<Time, TimeError>;
fn sub_ext(&self, right: Time) -> Duration;
fn reset_minute(&self) -> Result<Time, TimeError>;
fn is_same_minute(&self, other: &Time) -> bool;
fn is_between(&self, start: Time, end: Time) -> bool;
fn add_minutes(&self, minutes: i64) -> Time;
fn from_seconds(seconds: i64) -> Result<Time, TimeError>;
fn to_seconds(&self) -> i64;
fn align_to(&self, interval: i64) -> Result<Time, TimeError>;
fn next_day(&self) -> Time;
fn next_hour(&self) -> Time;
fn next_minute(&self) -> Time;
fn next_second(&self) -> Time;
fn to_hour_seconds(&self) -> i64;
fn to_minute_seconds(&self) -> i64;
}
impl ExtTime for Time {
fn to_shorten(&self) -> String {
format!("{}:{:02}", self.hour(), self.minute())
}
fn from_str(time_str: &str) -> Result<Time, TimeError> {
let parts: Vec<&str> = time_str.split(':').collect();
if parts.len() == 2 {
if let (Ok(hour), Ok(minute)) = (parts[0].parse::<u8>(), parts[1].parse::<u8>()) {
if hour < 24 && minute < 60 {
return Time::from_hms(hour, minute, 0)
.map_err(|_| TimeError::InvalidComponents(hour, minute));
}
}
}
Err(TimeError::InvalidFormat(time_str.to_string()))
}
fn sub_ext(&self, right: Time) -> Duration {
let diff = self.clone().sub(right);
if diff.is_negative() {
24.hours() + diff
} else {
diff
}
}
fn reset_minute(&self) -> Result<Time, TimeError> {
Time::from_hms(self.hour(), self.minute(), 0)
.map_err(|_| TimeError::ResetSecondsError(*self))
}
fn is_same_minute(&self, other: &Time) -> bool {
self.minute() == other.minute() && self.hour() == other.hour()
}
fn is_between(&self, start: Time, end: Time) -> bool {
if start <= end {
*self >= start && *self <= end
} else {
*self >= start || *self <= end
}
}
fn add_minutes(&self, minutes: i64) -> Time {
let total_minutes = self.hour() as i64 * 60 + self.minute() as i64 + minutes;
let normalized_minutes = total_minutes.rem_euclid(24 * 60);
let hours = (normalized_minutes / 60) as u8;
let minutes = (normalized_minutes % 60) as u8;
Time::from_hms(hours, minutes, self.second()).unwrap()
}
fn from_seconds(seconds: i64) -> Result<Time, TimeError> {
if seconds < 0 || seconds >= 24 * 3600 {
return Err(TimeError::InvalidSeconds(seconds));
}
let hours = (seconds / 3600) as u8;
let minutes = ((seconds % 3600) / 60) as u8;
let secs = (seconds % 60) as u8;
Time::from_hms(hours, minutes, secs)
.map_err(|_| TimeError::InvalidComponents(hours, minutes))
}
fn to_seconds(&self) -> i64 {
self.hour() as i64 * 3600 + self.minute() as i64 * 60 + self.second() as i64
}
fn align_to(&self, interval: i64) -> Result<Time, TimeError> {
if interval == 0 {
return Err(TimeError::InvalidAlignmentUnit(interval.abs() as u64));
}
let total_seconds = self.to_seconds();
let aligned_seconds = (total_seconds / interval) * interval;
Time::from_seconds(aligned_seconds).map_err(|_| TimeError::InvalidSeconds(aligned_seconds))
}
fn next_day(&self) -> Time {
*self
}
fn next_hour(&self) -> Time {
let next_hour = (self.hour() + 1) % 24;
Time::from_hms(next_hour, self.minute(), self.second()).unwrap()
}
fn next_minute(&self) -> Time {
if self.minute() == 59 {
let next_hour = (self.hour() + 1) % 24;
Time::from_hms(next_hour, 0, self.second()).unwrap()
} else {
Time::from_hms(self.hour(), self.minute() + 1, self.second()).unwrap()
}
}
fn next_second(&self) -> Time {
if self.second() == 59 {
if self.minute() == 59 {
let next_hour = (self.hour() + 1) % 24;
Time::from_hms(next_hour, 0, 0).unwrap()
} else {
Time::from_hms(self.hour(), self.minute() + 1, 0).unwrap()
}
} else {
Time::from_hms(self.hour(), self.minute(), self.second() + 1).unwrap()
}
}
fn to_hour_seconds(&self) -> i64 {
self.hour() as i64 * 3600
}
fn to_minute_seconds(&self) -> i64 {
self.hour() as i64 * 3600 + self.minute() as i64 * 60
}
}