use thiserror::Error;
use time::{
Duration, OffsetDateTime, Time, UtcOffset,
format_description::{self},
macros::format_description as fd,
};
#[derive(Error, Debug)]
pub enum OffsetDateTimeError {
#[error("Invalid offset hours: {0}")]
InvalidOffsetHours(i8),
#[error("Invalid timestamp: {0}")]
InvalidTimestamp(i64),
#[error("Invalid milliseconds: {0}")]
InvalidMilliseconds(u16),
#[error("Failed to parse datetime: {0}")]
ParseError(String),
#[error("Failed to format datetime: {0}")]
FormatError(String),
#[error("Invalid seconds value: {0}")]
InvalidSeconds(i64),
#[error("Invalid alignment unit: {0}")]
InvalidAlignmentUnit(u64),
#[error("Failed to add time: {0:?}")]
AddTimeError(OffsetDateTime),
}
pub trait ExtOffsetDateTime {
fn is_same_minute(&self, b: &OffsetDateTime) -> bool;
fn reset_minute(&self) -> OffsetDateTime;
fn milli_timestamp(&self) -> i64;
fn to_display_string(&self, offset_hours: i8) -> String;
fn to_chinese_string(&self) -> String;
fn from_milliseconds(
timestamp: u64,
offset_hours: i8,
) -> Result<OffsetDateTime, OffsetDateTimeError>;
fn from_seconds(
timestamp: u64,
offset_hours: i8,
) -> Result<OffsetDateTime, OffsetDateTimeError>;
fn from_date_time(
date: &str,
time: &str,
milli: u64,
offset_hours: i8,
) -> Result<OffsetDateTime, OffsetDateTimeError>;
fn from_simple(dt: &str, offset_hours: i8) -> Result<OffsetDateTime, OffsetDateTimeError>;
fn convert_to_dot_date(input: &str) -> Result<String, OffsetDateTimeError>;
fn now_with_offset(offset_hours: i8) -> OffsetDateTime {
OffsetDateTime::now_utc().to_offset(UtcOffset::from_hms(offset_hours, 0, 0).unwrap())
}
fn replace_time_with_seconds(
&self,
seconds: i64,
) -> Result<OffsetDateTime, OffsetDateTimeError>;
fn align_to(&self, interval: i64) -> Result<OffsetDateTime, OffsetDateTimeError>;
fn next_day(&self) -> OffsetDateTime;
fn next_hour(&self) -> OffsetDateTime;
fn next_minute(&self) -> OffsetDateTime;
fn next_second(&self) -> OffsetDateTime;
fn to_hour_seconds(&self) -> i64;
fn to_minute_seconds(&self) -> i64;
fn duration_to_time(&self, target_hour: u8, target_minute: u8, target_second: u8) -> Duration;
}
impl ExtOffsetDateTime for OffsetDateTime {
fn is_same_minute(&self, b: &OffsetDateTime) -> bool {
self.hour() == b.hour() && self.minute() == b.minute()
}
fn reset_minute(&self) -> OffsetDateTime {
let time = Time::from_hms(self.hour(), self.minute(), 0).expect("Invalid time components");
self.replace_time(time)
}
fn milli_timestamp(&self) -> i64 {
(self.unix_timestamp() as i64) * 1000 + self.millisecond() as i64
}
fn to_display_string(&self, offset_hours: i8) -> String {
let offset = UtcOffset::from_hms(offset_hours, 0, 0).expect("Invalid offset hours");
self.to_offset(offset)
.format(
&format_description::parse(
"[year]-[month]-[day] [hour repr:24]:[minute]:[second][offset_hour sign:mandatory]:[offset_minute]"
)
.unwrap(),
)
.expect("Failed to format datetime")
}
fn to_chinese_string(&self) -> String {
let offset = UtcOffset::from_hms(8, 0, 0).expect("Invalid offset hours");
let format = format_description::parse(
"[year]年[month]月[day]日 [hour]时[minute]分[second]秒 [offset_hour sign:mandatory]:[offset_minute]",
)
.expect("parse");
self.to_offset(offset)
.format(&format)
.expect("Failed to format datetime")
}
fn from_milliseconds(
timestamp: u64,
offset_hours: i8,
) -> Result<OffsetDateTime, OffsetDateTimeError> {
let seconds = timestamp / 1000;
let millis = timestamp % 1000;
let offset = UtcOffset::from_hms(offset_hours, 0, 0)
.map_err(|_| OffsetDateTimeError::InvalidOffsetHours(offset_hours))?;
let dt = OffsetDateTime::from_unix_timestamp(seconds as i64)
.map_err(|_| OffsetDateTimeError::InvalidTimestamp(seconds as i64))?;
let dt = dt
.replace_millisecond(millis as u16)
.map_err(|_| OffsetDateTimeError::InvalidMilliseconds(millis as u16))?;
Ok(dt.to_offset(offset))
}
fn from_seconds(
timestamp: u64,
offset_hours: i8,
) -> Result<OffsetDateTime, OffsetDateTimeError> {
let offset = UtcOffset::from_hms(offset_hours, 0, 0)
.map_err(|_| OffsetDateTimeError::InvalidOffsetHours(offset_hours))?;
let dt = OffsetDateTime::from_unix_timestamp(timestamp as i64)
.map_err(|_| OffsetDateTimeError::InvalidTimestamp(timestamp as i64))?;
Ok(dt.to_offset(offset))
}
fn from_date_time(
date_str: &str,
time_str: &str,
milli: u64,
offset_hours: i8,
) -> Result<OffsetDateTime, OffsetDateTimeError> {
let format = fd!(
"[year][month][day] [hour]:[minute]:[second].[subsecond digits:3] [offset_hour \
sign:mandatory]:[offset_minute]:[offset_second]"
);
let dt = format!(
"{} {}.{:03} {:+03}:00:00",
date_str, time_str, milli, offset_hours
);
OffsetDateTime::parse(&dt, &format)
.map_err(|e| OffsetDateTimeError::ParseError(e.to_string()))
}
fn from_simple(dt: &str, offset_hours: i8) -> Result<OffsetDateTime, OffsetDateTimeError> {
let format = fd!("[year][month][day]_[hour][minute] [offset_hour sign:mandatory]");
let dt = format!("{} {:+03}", dt, offset_hours);
OffsetDateTime::parse(&dt, &format)
.map_err(|e| OffsetDateTimeError::ParseError(e.to_string()))
}
fn convert_to_dot_date(input: &str) -> Result<String, OffsetDateTimeError> {
let parse_format = fd!("[year][month][day]");
let date = time::Date::parse(input, &parse_format)
.map_err(|e| OffsetDateTimeError::ParseError(e.to_string()))?;
let output_format = fd!("[year].[month].[day]");
date.format(&output_format)
.map_err(|e| OffsetDateTimeError::FormatError(e.to_string()))
}
fn replace_time_with_seconds(
&self,
seconds: i64,
) -> Result<OffsetDateTime, OffsetDateTimeError> {
if seconds < 0 || seconds >= 24 * 3600 {
return Err(OffsetDateTimeError::InvalidSeconds(seconds));
}
let hours = (seconds / 3600) as u8;
let minutes = ((seconds % 3600) / 60) as u8;
let secs = (seconds % 60) as u8;
let time = Time::from_hms(hours, minutes, secs)
.map_err(|_| OffsetDateTimeError::InvalidSeconds(seconds))?;
Ok(self.replace_time(time))
}
fn align_to(&self, interval: i64) -> Result<OffsetDateTime, OffsetDateTimeError> {
if interval == 0 {
return Err(OffsetDateTimeError::InvalidAlignmentUnit(
interval.abs() as u64
));
}
let total_seconds =
self.hour() as i64 * 3600 + self.minute() as i64 * 60 + self.second() as i64;
let aligned_seconds = (total_seconds / interval) * interval;
let hours = (aligned_seconds / 3600) as u8;
let minutes = ((aligned_seconds % 3600) / 60) as u8;
let secs = (aligned_seconds % 60) as u8;
let time = Time::from_hms(hours, minutes, secs)
.map_err(|_| OffsetDateTimeError::InvalidSeconds(aligned_seconds))?;
Ok(self.replace_time(time))
}
fn next_day(&self) -> OffsetDateTime {
self.clone() + Duration::days(1)
}
fn next_hour(&self) -> OffsetDateTime {
self.clone() + Duration::hours(1)
}
fn next_minute(&self) -> OffsetDateTime {
self.clone() + Duration::minutes(1)
}
fn next_second(&self) -> OffsetDateTime {
self.clone() + Duration::seconds(1)
}
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
}
fn duration_to_time(&self, target_hour: u8, target_minute: u8, target_second: u8) -> Duration {
let target_time = Time::from_hms(target_hour, target_minute, target_second)
.expect("Invalid target time components");
let target_today = self.replace_time(target_time);
let duration_to_today = target_today - *self;
if duration_to_today.is_positive() || duration_to_today.is_zero() {
duration_to_today
} else {
let target_tomorrow = target_today + Duration::days(1);
target_tomorrow - *self
}
}
}