use core::ops::{Add, Sub};
use crate::{
ConvertUnit, Date, Days, Duration, HalfDays, Month, TryIntoExact,
errors::{InvalidGregorianDate, InvalidHistoricDate, InvalidJulianDate},
units::{SecondsPerDay, SecondsPerHalfDay},
};
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct JulianDay<Representation, Period: ?Sized = SecondsPerHalfDay> {
time_since_epoch: Duration<Representation, Period>,
}
const JULIAN_DAY_UNIX_EPOCH: HalfDays<i32> = HalfDays::new(4881175);
impl<Representation> JulianDay<Representation, SecondsPerDay> {
pub const fn new(jd: Representation) -> Self {
Self::from_time_since_epoch(Days::new(jd))
}
}
impl<Representation, Period: ?Sized> JulianDay<Representation, Period> {
pub const fn from_time_since_epoch(time_since_epoch: Duration<Representation, Period>) -> Self {
Self { time_since_epoch }
}
pub const fn time_since_epoch(&self) -> Duration<Representation, Period>
where
Representation: Copy,
{
self.time_since_epoch
}
pub fn from_date(date: Date<Representation>) -> Self
where
Representation: Copy
+ From<i32>
+ Add<Representation, Output = Representation>
+ ConvertUnit<SecondsPerDay, Period>
+ ConvertUnit<SecondsPerHalfDay, Period>,
{
Self {
time_since_epoch: date.time_since_epoch().into_unit()
+ JULIAN_DAY_UNIX_EPOCH.cast().into_unit(),
}
}
pub fn into_date(&self) -> Date<Representation>
where
Representation: Copy
+ From<i32>
+ Sub<Representation, Output = Representation>
+ ConvertUnit<Period, SecondsPerDay>
+ ConvertUnit<SecondsPerHalfDay, SecondsPerDay>,
{
Date::from_time_since_epoch(
self.time_since_epoch.into_unit() - JULIAN_DAY_UNIX_EPOCH.cast().into_unit(),
)
}
pub fn cast<Target>(self) -> JulianDay<Target, Period>
where
Duration<Representation, Period>: Into<Duration<Target, Period>>,
{
JulianDay::from_time_since_epoch(self.time_since_epoch.into())
}
pub fn try_cast<Target>(
self,
) -> Result<JulianDay<Target, Period>, <Representation as TryIntoExact<Target>>::Error>
where
Representation: TryIntoExact<Target>,
{
Ok(JulianDay::from_time_since_epoch(
self.time_since_epoch.try_cast()?,
))
}
}
impl JulianDay<i32, SecondsPerHalfDay> {
pub fn from_historic_date(
year: i32,
month: Month,
day: u8,
) -> Result<Self, InvalidHistoricDate> {
match Date::from_historic_date(year, month, day) {
Ok(date) => Ok(Self::from_date(date)),
Err(error) => Err(error),
}
}
pub fn from_gregorian_date(
year: i32,
month: Month,
day: u8,
) -> Result<Self, InvalidGregorianDate> {
match Date::from_gregorian_date(year, month, day) {
Ok(date) => Ok(Self::from_date(date)),
Err(error) => Err(error),
}
}
pub fn from_julian_date(year: i32, month: Month, day: u8) -> Result<Self, InvalidJulianDate> {
match Date::from_julian_date(year, month, day) {
Ok(date) => Ok(Self::from_date(date)),
Err(error) => Err(error),
}
}
}
impl<Representation, Period: ?Sized> From<JulianDay<Representation, Period>>
for Date<Representation>
where
Representation: Copy
+ From<i32>
+ Sub<Representation, Output = Representation>
+ ConvertUnit<Period, SecondsPerDay>
+ ConvertUnit<SecondsPerHalfDay, SecondsPerDay>,
{
fn from(value: JulianDay<Representation, Period>) -> Self {
value.into_date()
}
}
impl<Representation, Period: ?Sized> From<Date<Representation>>
for JulianDay<Representation, Period>
where
Representation: Copy
+ From<i32>
+ Add<Representation, Output = Representation>
+ ConvertUnit<SecondsPerDay, Period>
+ ConvertUnit<SecondsPerHalfDay, Period>,
{
fn from(value: Date<Representation>) -> Self {
Self::from_date(value)
}
}
#[cfg(test)]
fn check_historic_julian_day(year: i32, month: Month, day: u8, time_since_epoch: HalfDays<i32>) {
assert_eq!(
JulianDay::from_historic_date(year, month, day)
.unwrap()
.time_since_epoch(),
time_since_epoch,
);
}
#[test]
fn historic_dates_from_meeus() {
use crate::Month::*;
check_historic_julian_day(2000, January, 1, HalfDays::new(4903089));
check_historic_julian_day(1999, January, 1, HalfDays::new(4902359));
check_historic_julian_day(1987, January, 27, HalfDays::new(4893645));
check_historic_julian_day(1987, June, 19, HalfDays::new(4893931));
check_historic_julian_day(1988, January, 27, HalfDays::new(4894375));
check_historic_julian_day(1988, June, 19, HalfDays::new(4894663));
check_historic_julian_day(1900, January, 1, HalfDays::new(4830041));
check_historic_julian_day(1600, January, 1, HalfDays::new(4610895));
check_historic_julian_day(1600, December, 31, HalfDays::new(4611625));
check_historic_julian_day(837, April, 10, HalfDays::new(4053743));
check_historic_julian_day(-123, December, 31, HalfDays::new(3352993));
check_historic_julian_day(-122, January, 1, HalfDays::new(3352995));
check_historic_julian_day(-1000, July, 12, HalfDays::new(2712001));
check_historic_julian_day(-1000, February, 29, HalfDays::new(2711733));
check_historic_julian_day(-1001, August, 17, HalfDays::new(2711341));
check_historic_julian_day(-4712, January, 1, -HalfDays::new(1));
}
#[cfg(kani)]
mod proof_harness {
use super::*;
#[kani::proof]
fn from_date_never_panics() {
let date: Date<i32> = kani::any();
kani::assume(
date.time_since_epoch().count() <= i32::MAX / 2 - JULIAN_DAY_UNIX_EPOCH.count(),
);
kani::assume(date.time_since_epoch().count() >= i32::MIN / 2);
let _ = JulianDay::<i32>::from_date(date);
}
}