use core::cmp::{Ord, Ordering, PartialEq, PartialOrd};
use core::hash::{Hash, Hasher};
use core::ops::{Add, AddAssign, Sub, SubAssign};
use crate::{
errors::HifitimeError, Duration, Epoch, Polynomial, TimeScale, Unit, Weekday,
NANOSECONDS_PER_DAY,
};
#[cfg(feature = "python")]
use pyo3::prelude::*;
#[cfg(not(feature = "std"))]
#[allow(unused_imports)] use num_traits::Float;
use super::rem_euclid_f64;
#[cfg_attr(feature = "python", pymethods)]
impl Epoch {
pub fn min(&self, other: Self) -> Self {
if *self < other {
*self
} else {
other
}
}
pub fn max(&self, other: Self) -> Self {
if *self > other {
*self
} else {
other
}
}
#[must_use]
pub fn floor(&self, duration: Duration) -> Self {
Self::from_duration(self.duration.floor(duration), self.time_scale)
}
#[must_use]
pub fn ceil(&self, duration: Duration) -> Self {
Self::from_duration(self.duration.ceil(duration), self.time_scale)
}
#[must_use]
pub fn round(&self, duration: Duration) -> Self {
Self::from_duration(self.duration.round(duration), self.time_scale)
}
#[must_use]
pub fn to_time_of_week(&self) -> (u32, u64) {
let total_nanoseconds = self.duration.total_nanoseconds();
let weeks = total_nanoseconds / NANOSECONDS_PER_DAY as i128 / Weekday::DAYS_PER_WEEK_I128;
let nanoseconds =
total_nanoseconds - weeks * NANOSECONDS_PER_DAY as i128 * Weekday::DAYS_PER_WEEK_I128;
(weeks as u32, nanoseconds as u64)
}
pub fn precise_timescale_conversion(
&self,
forward: bool,
reference_epoch: Self,
polynomial: Polynomial,
target: TimeScale,
) -> Result<Self, HifitimeError> {
if self.time_scale == target {
return Err(HifitimeError::SystemTimeError);
}
let reference_epoch = reference_epoch.to_time_scale(self.time_scale);
let dt = *self - reference_epoch;
let correction = polynomial.correction_duration(dt);
let converted = self.to_time_scale(target);
if forward {
Ok(converted - correction)
} else {
Ok(converted + correction)
}
}
#[must_use]
pub fn weekday_in_time_scale(&self, time_scale: TimeScale) -> Weekday {
(rem_euclid_f64(
self.to_duration_in_time_scale(time_scale)
.to_unit(Unit::Day),
Weekday::DAYS_PER_WEEK,
)
.floor() as u8)
.into()
}
#[must_use]
#[cfg_attr(kani, kani::ensures(|result: &Weekday| (*result as u8) < 7))]
pub fn weekday(&self) -> Weekday {
self.weekday_in_time_scale(TimeScale::TAI)
}
#[must_use]
pub fn weekday_utc(&self) -> Weekday {
self.weekday_in_time_scale(TimeScale::UTC)
}
#[must_use]
pub fn next(&self, weekday: Weekday) -> Self {
let delta_days = self.weekday() - weekday;
if delta_days == Duration::ZERO {
*self + 7 * Unit::Day
} else {
*self + delta_days
}
}
#[must_use]
pub fn next_weekday_at_midnight(&self, weekday: Weekday) -> Self {
self.next(weekday).with_hms_strict(0, 0, 0)
}
#[must_use]
pub fn next_weekday_at_noon(&self, weekday: Weekday) -> Self {
self.next(weekday).with_hms_strict(12, 0, 0)
}
#[must_use]
pub fn previous(&self, weekday: Weekday) -> Self {
let delta_days = weekday - self.weekday();
if delta_days == Duration::ZERO {
*self - 7 * Unit::Day
} else {
*self - delta_days
}
}
#[must_use]
pub fn previous_weekday_at_midnight(&self, weekday: Weekday) -> Self {
self.previous(weekday).with_hms_strict(0, 0, 0)
}
#[must_use]
pub fn previous_weekday_at_noon(&self, weekday: Weekday) -> Self {
self.previous(weekday).with_hms_strict(12, 0, 0)
}
}
impl Sub for Epoch {
type Output = Duration;
fn sub(self, other: Self) -> Duration {
self.duration - other.to_time_scale(self.time_scale).duration
}
}
impl SubAssign<Duration> for Epoch {
fn sub_assign(&mut self, duration: Duration) {
*self = *self - duration;
}
}
impl Sub<Duration> for Epoch {
type Output = Self;
fn sub(self, duration: Duration) -> Self {
Self {
duration: self.duration - duration,
time_scale: self.time_scale,
}
}
}
impl Add<f64> for Epoch {
type Output = Self;
fn add(self, seconds: f64) -> Self {
Self {
duration: self.duration + seconds * Unit::Second,
time_scale: self.time_scale,
}
}
}
impl Add<Duration> for Epoch {
type Output = Self;
fn add(self, duration: Duration) -> Self {
Self {
duration: self.duration + duration,
time_scale: self.time_scale,
}
}
}
impl AddAssign<Unit> for Epoch {
#[allow(clippy::identity_op)]
fn add_assign(&mut self, unit: Unit) {
*self = *self + unit * 1;
}
}
impl SubAssign<Unit> for Epoch {
#[allow(clippy::identity_op)]
fn sub_assign(&mut self, unit: Unit) {
*self = *self - unit * 1;
}
}
impl Sub<Unit> for Epoch {
type Output = Self;
#[allow(clippy::identity_op)]
fn sub(self, unit: Unit) -> Self {
Self {
duration: self.duration - unit * 1,
time_scale: self.time_scale,
}
}
}
impl Add<Unit> for Epoch {
type Output = Self;
#[allow(clippy::identity_op)]
fn add(self, unit: Unit) -> Self {
Self {
duration: self.duration + unit * 1,
time_scale: self.time_scale,
}
}
}
impl AddAssign<Duration> for Epoch {
fn add_assign(&mut self, duration: Duration) {
*self = *self + duration;
}
}
impl PartialEq for Epoch {
fn eq(&self, other: &Self) -> bool {
if self.time_scale == other.time_scale {
self.duration.to_parts() == other.duration.to_parts()
} else if self.time_scale.uses_leap_seconds() != other.time_scale.uses_leap_seconds() {
if self.time_scale.uses_leap_seconds() {
self.to_time_scale(other.time_scale).duration.to_parts()
== other.duration.to_parts()
} else {
self.duration.to_parts() == other.to_time_scale(self.time_scale).duration.to_parts()
}
} else {
self.duration.to_parts() == other.to_time_scale(self.time_scale).duration.to_parts()
}
}
}
impl PartialOrd for Epoch {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Epoch {
fn cmp(&self, other: &Self) -> Ordering {
self.to_tai_duration()
.to_parts()
.cmp(&other.to_tai_duration().to_parts())
}
}
impl Hash for Epoch {
fn hash<H: Hasher>(&self, state: &mut H) {
self.to_tai_duration().hash(state);
}
}