use pyo3::prelude::*;
use pyo3::Py;
use tempoch::qtty::Day;
use tempoch::{ModifiedJulianDate, JD, TT, UTC};
use crate::errors::{ensure_finite, map_conversion_error};
use crate::interop;
use crate::jd::PyJulianDate;
#[pyclass(name = "ModifiedJulianDate", module = "tempoch", from_py_object)]
#[derive(Clone, Copy)]
pub struct PyModifiedJulianDate {
pub(crate) inner: ModifiedJulianDate<TT>,
}
impl PyModifiedJulianDate {
pub fn from_inner(inner: ModifiedJulianDate<TT>) -> Self {
Self { inner }
}
}
#[allow(clippy::wrong_self_convention)]
#[pymethods]
impl PyModifiedJulianDate {
#[new]
fn new(value: f64) -> PyResult<Self> {
ensure_finite(value)?;
Ok(Self {
inner: ModifiedJulianDate::<TT>::new(value),
})
}
#[getter]
fn value(&self) -> f64 {
self.inner.value()
}
fn to_jd(&self) -> PyJulianDate {
PyJulianDate::from_inner(self.inner.to::<JD>())
}
fn to_utc(&self) -> PyResult<String> {
let dt = self
.inner
.to::<UTC>()
.try_to_chrono()
.map_err(map_conversion_error)?;
Ok(dt.to_rfc3339())
}
fn to_datetime<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
interop::time_to_datetime(py, self.inner.to::<UTC>().to_j2000s())
}
#[staticmethod]
fn from_utc(utc_str: &str) -> PyResult<Self> {
use chrono::{DateTime, FixedOffset, Utc};
let dt: DateTime<FixedOffset> = utc_str.parse().map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("Invalid UTC datetime: {e}"))
})?;
let utc = tempoch::Time::<UTC>::try_from_chrono(dt.with_timezone(&Utc))
.map_err(map_conversion_error)?;
Ok(Self {
inner: utc.to::<TT>().to::<tempoch::MJD>(),
})
}
#[staticmethod]
fn from_datetime(dt: &Bound<'_, PyAny>) -> PyResult<Self> {
let utc = interop::datetime_to_time(dt)?;
Ok(Self {
inner: utc.to::<TT>().to::<tempoch::MJD>(),
})
}
fn add_days(&self, days: f64) -> Self {
Self {
inner: self.inner + Day::new(days),
}
}
fn difference(&self, other: &PyModifiedJulianDate) -> f64 {
(self.inner - other.inner).value()
}
fn __add__(&self, days: f64) -> Self {
self.add_days(days)
}
fn __radd__(&self, days: f64) -> Self {
self.add_days(days)
}
fn __sub__<'py>(&self, other: &Bound<'py, PyAny>) -> PyResult<Py<PyAny>> {
let py = other.py();
if let Ok(other_mjd) = other.extract::<PyModifiedJulianDate>() {
Ok(self
.difference(&other_mjd)
.into_pyobject(py)?
.into_any()
.unbind())
} else if let Ok(days) = other.extract::<f64>() {
Ok(self.add_days(-days).into_pyobject(py)?.into_any().unbind())
} else {
Err(pyo3::exceptions::PyTypeError::new_err(
"Can only subtract a float (days) or another ModifiedJulianDate",
))
}
}
fn __eq__(&self, other: &PyModifiedJulianDate) -> bool {
self.inner == other.inner
}
fn __ne__(&self, other: &PyModifiedJulianDate) -> bool {
self.inner != other.inner
}
fn __lt__(&self, other: &PyModifiedJulianDate) -> bool {
self.inner < other.inner
}
fn __le__(&self, other: &PyModifiedJulianDate) -> bool {
self.inner <= other.inner
}
fn __gt__(&self, other: &PyModifiedJulianDate) -> bool {
self.inner > other.inner
}
fn __ge__(&self, other: &PyModifiedJulianDate) -> bool {
self.inner >= other.inner
}
fn __repr__(&self) -> String {
format!("ModifiedJulianDate({})", self.inner.value())
}
fn __str__(&self) -> String {
format!("ModifiedJulianDate({})", self.inner.value())
}
fn __hash__(&self) -> u64 {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
self.inner.value().to_bits().hash(&mut hasher);
hasher.finish()
}
fn __reduce__(&self, py: Python<'_>) -> PyResult<(Py<PyAny>, (f64,))> {
let cls = py.get_type::<Self>().into_any().unbind();
Ok((cls, (self.inner.value(),)))
}
fn __float__(&self) -> f64 {
self.inner.value()
}
}