radnelac 0.0.2

Calculations in a variety of different timekeeping systems.
Documentation
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use crate::calendar::CommonDate;
use crate::calendar::CommonWeekOfYear;
use crate::calendar::GuaranteedMonth;
use crate::calendar::HasEpagemonae;
use crate::calendar::HasLeapYears;
use crate::calendar::OrdinalDate;
use crate::calendar::Perennial;
use crate::calendar::Quarter;
use crate::calendar::ToFromCommonDate;
use crate::calendar::ToFromOrdinalDate;
use crate::clock::ClockTime;
use crate::clock::TimeOfDay;
use crate::day_count::BoundedDayCount;
use crate::day_count::EffectiveBound;
use crate::day_count::Epoch;
use crate::day_count::Fixed;
use crate::day_count::FromFixed;
use crate::day_count::ToFixed;
use crate::day_cycle::Weekday;
use crate::CalendarError;
use num_traits::FromPrimitive;
use num_traits::ToPrimitive;
use std::num::NonZero;

/// Represents an instant in time using calendar system T
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)]
pub struct CalendarMoment<T> {
    date: T,
    time: ClockTime,
}

impl<T> CalendarMoment<T> {
    /// Create a CalendarMoment with the given date and time
    pub fn new(date: T, t: TimeOfDay) -> Self {
        Self {
            date: date,
            time: t.to_clock(),
        }
    }

    /// Date of the given CalendarMoment
    pub fn date(self) -> T {
        self.date
    }

    /// Time of day of the given CalendarMoment
    pub fn time_of_day(self) -> ClockTime {
        self.time
    }
}

impl<T: FromFixed> FromFixed for CalendarMoment<T> {
    fn from_fixed(fixed_date: Fixed) -> Self {
        Self::new(T::from_fixed(fixed_date), TimeOfDay::from_fixed(fixed_date))
    }
}

impl<T: ToFixed> ToFixed for CalendarMoment<T> {
    fn to_fixed(self) -> Fixed {
        let fd = self.date.to_fixed();
        let ft = TimeOfDay::try_from_clock(self.time).expect("Guaranteed valid");
        Fixed::new(fd.to_day().get() + ft.get())
    }
}

impl<T: Epoch> Epoch for CalendarMoment<T> {
    fn epoch() -> Fixed {
        T::epoch()
    }
}

impl<T: EffectiveBound> EffectiveBound for CalendarMoment<T> {
    fn effective_min() -> Self {
        Self::new(T::effective_min(), TimeOfDay::midnight())
    }
    fn effective_max() -> Self {
        Self::new(T::effective_max(), TimeOfDay::midnight())
    }
}

impl<T: HasLeapYears> HasLeapYears for CalendarMoment<T> {
    fn is_leap(year: i32) -> bool {
        T::is_leap(year)
    }
}

impl<T, U> ToFromCommonDate<T> for CalendarMoment<U>
where
    T: FromPrimitive,
    U: ToFromCommonDate<T> + EffectiveBound,
{
    fn to_common_date(self) -> CommonDate {
        self.date.to_common_date()
    }

    fn from_common_date_unchecked(d: CommonDate) -> Self {
        Self::new(U::from_common_date_unchecked(d), TimeOfDay::midnight())
    }

    fn valid_ymd(d: CommonDate) -> Result<(), CalendarError> {
        U::valid_ymd(d)
    }

    fn year_start_date(year: i32) -> CommonDate {
        U::year_start_date(year)
    }

    fn year_end_date(year: i32) -> CommonDate {
        U::year_end_date(year)
    }

    fn month_length(year: i32, month: T) -> u8 {
        U::month_length(year, month)
    }

    fn in_effective_bounds(d: CommonDate) -> bool {
        U::in_effective_bounds(d)
    }

    fn try_from_common_date(d: CommonDate) -> Result<Self, CalendarError> {
        let d = U::try_from_common_date(d)?;
        Ok(Self::new(d, TimeOfDay::midnight()))
    }

    fn try_year_start(year: i32) -> Result<Self, CalendarError> {
        let d = U::try_year_start(year)?;
        Ok(Self::new(d, TimeOfDay::midnight()))
    }

    fn try_year_end(year: i32) -> Result<Self, CalendarError> {
        let d = U::try_year_end(year)?;
        Ok(Self::new(d, TimeOfDay::midnight()))
    }

    fn day(self) -> u8 {
        self.date.day()
    }

    fn try_month(self) -> Option<T> {
        self.date.try_month()
    }

    fn year(self) -> i32 {
        self.date.year()
    }
}

impl<T, U> GuaranteedMonth<T> for CalendarMoment<U>
where
    T: FromPrimitive + ToPrimitive,
    U: GuaranteedMonth<T>,
{
    fn month(self) -> T {
        self.date.month()
    }

    fn try_new(year: i32, month: T, day: u8) -> Result<Self, CalendarError> {
        let d = U::try_new(year, month, day)?;
        Ok(Self::new(d, TimeOfDay::midnight()))
    }
}

impl<T, U> HasEpagemonae<T> for CalendarMoment<U>
where
    T: FromPrimitive + ToPrimitive,
    U: HasEpagemonae<T>,
{
    fn epagomenae(self) -> Option<T> {
        self.date.epagomenae()
    }

    fn epagomenae_count(p_year: i32) -> u8 {
        U::epagomenae_count(p_year)
    }
}

impl<S, T, U> Perennial<S, T> for CalendarMoment<U>
where
    S: FromPrimitive + ToPrimitive,
    T: FromPrimitive + ToPrimitive,
    U: Perennial<S, T>,
{
    fn weekday(self) -> Option<T> {
        self.date.weekday()
    }

    fn days_per_week() -> u8 {
        U::days_per_week()
    }

    fn weeks_per_month() -> u8 {
        U::weeks_per_month()
    }

    fn try_week_of_year(self) -> Option<u8> {
        self.date.try_week_of_year()
    }
}

impl<T: Quarter> Quarter for CalendarMoment<T> {
    fn quarter(self) -> NonZero<u8> {
        self.date.quarter()
    }
}

impl<T, U> CommonWeekOfYear<T> for CalendarMoment<U>
where
    T: FromPrimitive,
    U: CommonWeekOfYear<T> + ToFixed,
{
    fn week_of_year(self) -> u8 {
        self.date.week_of_year()
    }

    fn nth_kday(self, nz: NonZero<i16>, k: Weekday) -> Fixed {
        self.date.nth_kday(nz, k)
    }
}

impl<T: ToFromOrdinalDate> ToFromOrdinalDate for CalendarMoment<T> {
    fn valid_ordinal(ord: OrdinalDate) -> Result<(), CalendarError> {
        T::valid_ordinal(ord)
    }

    fn ordinal_from_fixed(fixed_date: Fixed) -> OrdinalDate {
        T::ordinal_from_fixed(fixed_date)
    }

    fn to_ordinal(self) -> OrdinalDate {
        self.date.to_ordinal()
    }

    fn from_ordinal_unchecked(ord: OrdinalDate) -> Self {
        Self::new(T::from_ordinal_unchecked(ord), TimeOfDay::midnight())
    }
}