Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
helgee committed Jan 5, 2025
1 parent 3bab969 commit f085b12
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 277 deletions.
2 changes: 0 additions & 2 deletions crates/lox-orbits/src/analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ use lox_math::series::{Series, SeriesError};
use lox_math::types::units::Radians;
use lox_time::deltas::TimeDelta;
use lox_time::time_scales::Tdb;
use lox_time::transformations::TryToScale;
use lox_time::TimeLike;
use thiserror::Error;

Expand Down Expand Up @@ -175,7 +174,6 @@ mod tests {
use lox_math::assert_close;
use lox_math::is_close::IsClose;
use lox_time::time_scales::Tai;
use lox_time::transformations::ToTai;
use lox_time::utc::Utc;
use lox_time::Time;
use std::iter::zip;
Expand Down
33 changes: 26 additions & 7 deletions crates/lox-time/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ use std::str::FromStr;
use itertools::Itertools;
use lox_math::is_close::IsClose;
use num::ToPrimitive;
use prelude::offsets::{FromScale, TryFromScale};
use thiserror::Error;
use time_scales::offsets::TryToScale;

Expand Down Expand Up @@ -309,6 +310,31 @@ impl<T: TimeScale> Time<T> {
Time::new(scale, self.seconds, self.subsecond)
}

pub fn try_from_scale<S, P>(
scale: T,
time: Time<S>,
provider: Option<&P>,
) -> Result<Time<T>, T::Error>
where
S: TimeScale,
T: TimeScale + TryFromScale<S, P> + Copy,
{
let dt = time.to_delta();
Ok(Time::from_delta(
scale,
dt + scale.try_offset_from(time.scale, dt, provider)?,
))
}

pub fn from_scale<S>(scale: T, time: Time<S>) -> Time<T>
where
S: TimeScale,
T: TimeScale + FromScale<S> + Copy,
{
let dt = time.to_delta();
Time::from_delta(scale, dt + scale.offset_from(time.scale, dt))
}

pub fn try_to_scale<S, P>(
&self,
scale: S,
Expand Down Expand Up @@ -339,13 +365,6 @@ impl<T: TimeScale> Time<T> {
Time::from_delta(scale, self.to_delta() + delta)
}

pub fn to_delta(&self) -> TimeDelta {
TimeDelta {
seconds: self.seconds,
subsecond: self.subsecond,
}
}

/// Returns the Julian epoch as a [Time] in the given [TimeScale].
pub fn jd0(scale: T) -> Self {
Self::from_epoch(scale, Epoch::JulianDate)
Expand Down
62 changes: 34 additions & 28 deletions crates/lox-time/src/python/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ use lox_math::is_close::IsClose;
use crate::calendar_dates::{CalendarDate, Date};
use crate::deltas::{TimeDelta, ToDelta};
use crate::julian_dates::{Epoch, JulianDate, Unit};
use crate::prelude::{CivilTime, Tai, Tcb, Tcg, Tdb, TimeScale, Tt, Ut1};
use crate::prelude::{CivilTime, DynTimeScale, Tai, Tcb, Tcg, Tdb, TimeScale, Tt, Ut1};
use crate::python::deltas::PyTimeDelta;
use crate::python::time_scales::PyTimeScale;
use crate::python::ut1::{PyDeltaUt1Provider, PyUt1Provider};
use crate::python::utc::PyUtc;
use crate::subsecond::{InvalidSubsecond, Subsecond};
use crate::time_of_day::TimeOfDay;
use crate::utc::transformations::ToUtc;
use crate::{Time, TimeError, TimeLike};
use crate::{DynTime, Time, TimeError, TimeLike};

use super::ut1::PyNoOpOffsetProvider;

Expand Down Expand Up @@ -72,22 +72,22 @@ impl FromStr for Unit {

#[pyclass(name = "Time", module = "lox_space", frozen)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PyTime(pub Time<PyTimeScale>);
pub struct PyTime(pub DynTime);

#[pymethods]
impl PyTime {
#[new]
#[pyo3(signature=(scale, year, month, day, hour = 0, minute = 0, seconds = 0.0))]
pub fn new(
scale: &str,
scale: &Bound<'_, PyAny>,
year: i64,
month: u8,
day: u8,
hour: u8,
minute: u8,
seconds: f64,
) -> PyResult<PyTime> {
let scale: PyTimeScale = scale.parse()?;
let scale: DynTimeScale = scale.try_into()?;
let time = Time::builder_with_scale(scale)
.with_ymd(year, month, day)
.with_hms(hour, minute, seconds)
Expand All @@ -99,38 +99,38 @@ impl PyTime {
#[pyo3(signature = (scale, jd, epoch = "jd"))]
pub fn from_julian_date(
_cls: &Bound<'_, PyType>,
scale: &str,
scale: &Bound<'_, PyAny>,
jd: f64,
epoch: &str,
) -> PyResult<Self> {
let scale: PyTimeScale = scale.parse()?;
let scale: DynTimeScale = scale.try_into()?;
let epoch: Epoch = epoch.parse()?;
Ok(Self(Time::from_julian_date(scale, jd, epoch)?))
}

#[classmethod]
pub fn from_two_part_julian_date(
_cls: &Bound<'_, PyType>,
scale: &str,
scale: &Bound<'_, PyAny>,
jd1: f64,
jd2: f64,
) -> PyResult<Self> {
let scale: PyTimeScale = scale.parse()?;
let scale: DynTimeScale = scale.try_into()?;
Ok(Self(Time::from_two_part_julian_date(scale, jd1, jd2)?))
}

#[classmethod]
#[pyo3(signature=(scale, year, day, hour=0, minute=0, seconds=0.0))]
pub fn from_day_of_year(
_cls: &Bound<'_, PyType>,
scale: &str,
scale: &Bound<'_, PyAny>,
year: i64,
day: u16,
hour: u8,
minute: u8,
seconds: f64,
) -> PyResult<PyTime> {
let scale: PyTimeScale = scale.parse()?;
let scale: DynTimeScale = scale.try_into()?;
let time = Time::builder_with_scale(scale)
.with_doy(year, day)
.with_hms(hour, minute, seconds)
Expand All @@ -140,26 +140,25 @@ impl PyTime {

#[classmethod]
#[pyo3(signature = (iso, scale=None))]
pub fn from_iso(_cls: &Bound<'_, PyType>, iso: &str, scale: Option<&str>) -> PyResult<PyTime> {
let scale: PyTimeScale = match scale {
Some(scale) => scale.parse()?,
None => match iso.split_once(char::is_whitespace) {
Some((_, scale)) => scale.parse()?,
None => PyTimeScale::Tai,
},
};
pub fn from_iso(
_cls: &Bound<'_, PyType>,
iso: &str,
scale: Option<&Bound<'_, PyAny>>,
) -> PyResult<PyTime> {
let scale: DynTimeScale =
scale.map_or(Ok(DynTimeScale::default()), |scale| scale.try_into())?;
let time = Time::from_iso(scale, iso)?;
Ok(PyTime(time))
}

#[classmethod]
pub fn from_seconds(
_cls: &Bound<'_, PyType>,
scale: &str,
scale: &Bound<'_, PyAny>,
seconds: i64,
subsecond: f64,
) -> PyResult<PyTime> {
let scale: PyTimeScale = scale.parse()?;
let scale: DynTimeScale = scale.try_into()?;
let subsecond = Subsecond::new(subsecond)?;
let time = Time::new(scale, seconds, subsecond);
Ok(PyTime(time))
Expand Down Expand Up @@ -504,17 +503,23 @@ impl IsClose for PyTime {
#[cfg(test)]
mod tests {
use float_eq::assert_float_eq;
use pyo3::{types::PyDict, Python};
use pyo3::{types::PyDict, IntoPyObjectExt, Python};

use lox_math::assert_close;

use crate::test_helpers::data_dir;

use super::*;

fn py_tai<'py>(py: Python<'py>) -> Bound<'py, PyAny> {
"TAI".into_bound_py_any(py).unwrap()
}

#[test]
fn test_pytime() {
let time = PyTime::new("TAI", 2000, 1, 1, 0, 0, 12.123456789123).unwrap();
let time = Python::with_gil(|py| {
PyTime::new(&py_tai(py), 2000, 1, 1, 0, 0, 12.123456789123).unwrap()
});
assert_eq!(
time.__repr__(),
"Time(\"TAI\", 2000, 1, 1, 0, 0, 12.123456789123)"
Expand All @@ -538,28 +543,29 @@ mod tests {
#[test]
#[should_panic(expected = "invalid date")]
fn test_pytime_invalid_date() {
PyTime::new("TAI", 2000, 13, 1, 0, 0, 0.0).unwrap();
Python::with_gil(|py| PyTime::new(&py_tai(py), 2000, 13, 1, 0, 0, 0.0).unwrap());
}

#[test]
#[should_panic(expected = "hour must be in the range")]
fn test_pytime_invalid_time() {
PyTime::new("TAI", 2000, 12, 1, 24, 0, 0.0).unwrap();
Python::with_gil(|py| PyTime::new(&py_tai(py), 2000, 12, 1, 24, 0, 0.0).unwrap());
}

#[test]
fn test_pytime_ops() {
Python::with_gil(|py| {
let t0 = PyTime::new("TAI", 2000, 1, 1, 0, 0, 0.0).unwrap();
let t0 = PyTime::new(&py_tai(py), 2000, 1, 1, 0, 0, 0.0).unwrap();
let dt = PyTimeDelta::new(1.0).unwrap();
let t1 = PyTime::new("TAI", 2000, 1, 1, 0, 0, 1.0).unwrap();
let t1 = PyTime::new(&py_tai(py), 2000, 1, 1, 0, 0, 1.0).unwrap();
assert_eq!(t0.__add__(dt.clone()), t1.clone());
let dtb = Bound::new(py, PyTimeDelta::new(1.0).unwrap()).unwrap();
assert_eq!(
t1.__sub__(py, &dtb).unwrap().extract::<PyTime>().unwrap(),
t0
);
let t0b = Bound::new(py, PyTime::new("TAI", 2000, 1, 1, 0, 0, 0.0).unwrap()).unwrap();
let t0b =
Bound::new(py, PyTime::new(&py_tai(py), 2000, 1, 1, 0, 0, 0.0).unwrap()).unwrap();
assert_eq!(
t1.__sub__(py, &t0b)
.unwrap()
Expand Down
96 changes: 19 additions & 77 deletions crates/lox-time/src/python/time_scales.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,89 +6,31 @@
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*/

use crate::prelude::{Tai, Tcb, Tcg, Tdb, TimeScale, Tt, Ut1};
use pyo3::exceptions::PyValueError;
use pyo3::PyErr;
use std::fmt::{Display, Formatter};
use std::str::FromStr;
use pyo3::{exceptions::PyValueError, pyclass, types::PyAnyMethods, Bound, PyAny, PyErr};

#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum PyTimeScale {
Tai,
Tcb,
Tcg,
Tdb,
Tt,
Ut1,
}
use crate::prelude::{DynTimeScale, UnknownTimeScaleError};

impl FromStr for PyTimeScale {
type Err = PyErr;

fn from_str(name: &str) -> Result<Self, Self::Err> {
match name {
"TAI" => Ok(PyTimeScale::Tai),
"TCB" => Ok(PyTimeScale::Tcb),
"TCG" => Ok(PyTimeScale::Tcg),
"TDB" => Ok(PyTimeScale::Tdb),
"TT" => Ok(PyTimeScale::Tt),
"UT1" => Ok(PyTimeScale::Ut1),
_ => Err(PyValueError::new_err(format!(
"invalid time scale: {}",
name
))),
}
impl From<UnknownTimeScaleError> for PyErr {
fn from(err: UnknownTimeScaleError) -> Self {
PyValueError::new_err(err.to_string())
}
}

impl TimeScale for PyTimeScale {
fn abbreviation(&self) -> &'static str {
match self {
PyTimeScale::Tai => Tai.abbreviation(),
PyTimeScale::Tcb => Tcb.abbreviation(),
PyTimeScale::Tcg => Tcg.abbreviation(),
PyTimeScale::Tdb => Tdb.abbreviation(),
PyTimeScale::Tt => Tt.abbreviation(),
PyTimeScale::Ut1 => Ut1.abbreviation(),
}
}

fn name(&self) -> &'static str {
match self {
PyTimeScale::Tai => Tai.name(),
PyTimeScale::Tcb => Tcb.name(),
PyTimeScale::Tcg => Tcg.name(),
PyTimeScale::Tdb => Tdb.name(),
PyTimeScale::Tt => Tt.name(),
PyTimeScale::Ut1 => Ut1.name(),
}
}
}

impl Display for PyTimeScale {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.abbreviation())
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[pyclass]
pub struct PyTimeScale(pub DynTimeScale);

#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
impl TryFrom<&Bound<'_, PyAny>> for DynTimeScale {
type Error = PyErr;

#[rstest]
#[case("TAI", "International Atomic Time")]
#[case("TT", "Terrestrial Time")]
#[case("TCG", "Geocentric Coordinate Time")]
#[case("TCB", "Barycentric Coordinate Time")]
#[case("TDB", "Barycentric Dynamical Time")]
#[case("UT1", "Universal Time")]
#[should_panic(expected = "invalid time scale: NotATimeScale")]
#[case("NotATimeScale", "not a time scale")]
fn test_pytimescale(#[case] abbreviation: &'static str, #[case] name: &'static str) {
let scale = PyTimeScale::from_str(abbreviation).unwrap();
assert_eq!(scale.abbreviation(), abbreviation);
assert_eq!(scale.name(), name);
assert_eq!(scale.to_string(), abbreviation);
fn try_from(value: &Bound<'_, PyAny>) -> Result<Self, Self::Error> {
if let Ok(name) = value.extract::<&str>() {
return Ok(name.parse()?);
} else if let Ok(scale) = value.extract::<PyTimeScale>() {
return Ok(scale.0);
}
Err(PyValueError::new_err(
"'scale' argument must either a string or a 'TimeScale' instance",
))
}
}
2 changes: 1 addition & 1 deletion crates/lox-time/src/time_scales.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,10 @@ impl Display for Ut1 {

#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)]
pub enum DynTimeScale {
#[default]
Tai,
Tcb,
Tcg,
#[default]
Tdb,
Tt,
Ut1,
Expand Down
2 changes: 1 addition & 1 deletion crates/lox-time/src/time_scales/offsets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ pub trait ToScale<T: TimeScale> {
fn offset(&self, scale: T, dt: TimeDelta) -> TimeDelta;
}

trait FromScale<T: TimeScale> {
pub trait FromScale<T: TimeScale> {
fn offset_from(&self, scale: T, dt: TimeDelta) -> TimeDelta;
}

Expand Down
Loading

0 comments on commit f085b12

Please sign in to comment.