use core::ops::Sub;
use crate::{
ConvertUnit, Date, Days, Duration, Fraction, Hours, Minutes, MulFloor, Seconds, TimePoint,
TryIntoExact,
errors::InvalidTimeOfDay,
time_scale::AbsoluteTimeScale,
units::{Second, SecondsPerDay, SecondsPerHour, SecondsPerMinute},
};
pub trait UniformDateTimeScale: AbsoluteTimeScale {}
pub trait FromDateTime: Sized {
type Error: core::error::Error;
fn from_datetime(
date: Date<i32>,
hour: u8,
minute: u8,
second: u8,
) -> Result<Self, Self::Error>;
}
impl<Scale> FromDateTime for TimePoint<Scale, i64, Second>
where
Scale: ?Sized + UniformDateTimeScale,
{
type Error = InvalidTimeOfDay;
fn from_datetime(
date: Date<i32>,
hour: u8,
minute: u8,
second: u8,
) -> Result<Self, Self::Error> {
if hour >= 24 || minute >= 60 || second >= 60 {
return Err(InvalidTimeOfDay {
hour,
minute,
second,
});
}
let days_since_scale_epoch = {
let days_since_1970 = date.time_since_epoch();
let epoch_days_since_1970 = Scale::EPOCH.time_since_epoch();
days_since_1970.cast() - epoch_days_since_1970.cast()
};
let hours = Hours::new(hour).cast();
let minutes = Minutes::new(minute).cast();
let seconds = Seconds::new(second).cast();
let time_since_epoch =
days_since_scale_epoch.into_unit() + hours.into_unit() + minutes.into_unit() + seconds;
Ok(TimePoint::from_time_since_epoch(time_since_epoch))
}
}
pub trait FromFineDateTime<Representation, Period: ?Sized>: Sized {
type Error: core::error::Error;
fn from_fine_datetime(
date: Date<i32>,
hour: u8,
minute: u8,
second: u8,
subseconds: Duration<Representation, Period>,
) -> Result<Self, Self::Error>;
}
pub trait IntoDateTime: Sized {
fn into_datetime(self) -> (Date<i32>, u8, u8, u8);
}
impl<Scale, Representation> IntoDateTime for TimePoint<Scale, Representation, Second>
where
Scale: ?Sized + UniformDateTimeScale,
Representation: Copy
+ ConvertUnit<SecondsPerMinute, Second>
+ ConvertUnit<SecondsPerHour, Second>
+ ConvertUnit<SecondsPerDay, Second>
+ MulFloor<Fraction, Output = Representation>
+ Sub<Representation, Output = Representation>
+ TryIntoExact<i32>
+ TryIntoExact<u8>,
{
fn into_datetime(self) -> (Date<i32>, u8, u8, u8) {
let seconds_since_scale_epoch = self.time_since_epoch();
let (days_since_scale_epoch, seconds_in_day) =
seconds_since_scale_epoch.factor_out::<SecondsPerDay>();
let days_since_scale_epoch: Days<i32> = days_since_scale_epoch
.try_cast()
.unwrap_or_else(|_| panic!("Call of `datetime_from_time_point` results in days since scale epoch outside of `i32` range"));
let (hour, seconds_in_hour) = seconds_in_day.factor_out::<SecondsPerHour>();
let (minute, second) = seconds_in_hour.factor_out::<SecondsPerMinute>();
let second = second.floor::<Second>();
let days_since_universal_epoch =
<Scale as AbsoluteTimeScale>::EPOCH.time_since_epoch() + days_since_scale_epoch;
let date = Date::from_time_since_epoch(days_since_universal_epoch);
(
date.try_cast()
.expect("Call of `datetime_from_time_point` results in date outside of representable range of `i32`"),
hour.count().try_into_exact().unwrap_or_else(|_| panic!("Call of `datetime_from_time_point` results in hour value that cannot be expressed as `u8`")),
minute.count().try_into_exact().unwrap_or_else(|_| panic!("Call of `datetime_from_time_point` results in minute value that cannot be expressed as `u8`")),
second.count().try_into_exact().unwrap_or_else(|_| panic!("Call of `datetime_from_time_point` results in second value that cannot be expressed as `u8`")),
)
}
}
pub trait IntoFineDateTime<Representation, Period: ?Sized> {
fn into_fine_datetime(self) -> (Date<i32>, u8, u8, u8, Duration<Representation, Period>);
}