import numpy as np
import pytest
from astropy import units as u
from astropy.time import Time
from astrora._core import Epoch
from astrora.bodies import Earth
from astrora.time import (
ASTROPY_AVAILABLE,
astropy_time_to_epoch,
epoch_to_astropy_time,
to_astropy_time,
to_epoch,
)
from astrora.twobody import Orbit
pytestmark = pytest.mark.skipif(not ASTROPY_AVAILABLE, reason="astropy not installed")
class TestAstropyTimeConversion:
def test_j2000_conversion(self):
t_astropy = Time("2000-01-01 12:00:00", scale="tt")
epoch = astropy_time_to_epoch(t_astropy)
assert abs(epoch.jd_tt - 2451545.0) < 1e-9
t_back = epoch_to_astropy_time(epoch, scale="tt")
assert abs(t_back.jd - t_astropy.jd) < 1e-9
def test_utc_conversion(self):
t_utc = Time("2024-10-22 14:30:45.123456", scale="utc")
epoch = astropy_time_to_epoch(t_utc)
t_back = epoch_to_astropy_time(epoch, scale="utc")
assert abs(t_back.jd - t_utc.jd) < 1e-9
def test_tai_conversion(self):
t_tai = Time("2020-06-15 10:30:00", scale="tai")
epoch = astropy_time_to_epoch(t_tai)
t_back = epoch_to_astropy_time(epoch, scale="tai")
assert abs(t_back.jd - t_tai.jd) < 1e-9
def test_tt_conversion(self):
t_tt = Time("2015-03-20 09:45:00", scale="tt")
epoch = astropy_time_to_epoch(t_tt)
t_back = epoch_to_astropy_time(epoch, scale="tt")
assert abs(t_back.jd - t_tt.jd) < 1e-9
def test_precision_preservation(self):
t_original = Time("2024-01-15 12:34:56.123456", scale="utc")
epoch = astropy_time_to_epoch(t_original)
t_roundtrip = epoch_to_astropy_time(epoch, scale="utc")
diff_days = abs(t_original.jd - t_roundtrip.jd)
diff_seconds = diff_days * 86400
assert diff_seconds < 1e-6
def test_different_epochs(self):
test_cases = [
("1970-01-01 00:00:00", "utc"), ("2000-01-01 00:00:00", "utc"), ("2024-12-31 23:59:59", "utc"), ("1980-01-06 00:00:00", "tai"), ]
for time_str, scale in test_cases:
t = Time(time_str, scale=scale)
epoch = astropy_time_to_epoch(t)
t_back = epoch_to_astropy_time(epoch, scale=scale)
assert abs(t_back.jd - t.jd) < 1e-9, f"Failed for {time_str} ({scale})"
class TestConvenienceFunctions:
def test_to_epoch_from_astropy_time(self):
t = Time("2020-06-15 10:30:00", scale="utc")
epoch = to_epoch(t)
assert isinstance(epoch, Epoch)
assert abs(epoch.jd_utc - t.jd) < 1e-9
def test_to_epoch_from_epoch(self):
epoch_in = Epoch.j2000_epoch()
epoch_out = to_epoch(epoch_in)
assert epoch_out == epoch_in
def test_to_epoch_from_none(self):
result = to_epoch(None)
assert result is None
def test_to_epoch_invalid_type(self):
with pytest.raises(TypeError):
to_epoch("2020-01-01")
def test_to_astropy_time_from_epoch(self):
epoch = Epoch.j2000_epoch()
t = to_astropy_time(epoch, scale="tt")
assert isinstance(t, Time)
assert t.scale == "tt"
assert abs(t.jd - 2451545.0) < 1e-9
def test_to_astropy_time_from_time(self):
t_in = Time("2020-06-15", scale="utc")
t_out = to_astropy_time(t_in)
assert t_out.jd == t_in.jd
assert t_out.scale == t_in.scale
def test_to_astropy_time_scale_conversion(self):
t_in = Time("2020-06-15", scale="utc")
t_out = to_astropy_time(t_in, scale="tt")
assert t_out.scale == "tt"
assert abs(t_out.utc.jd - t_in.jd) < 1e-9
class TestOrbitTimeIntegration:
def test_from_vectors_with_astropy_time(self):
r = np.array([7000e3, 0, 0])
v = np.array([0, 7546, 0])
t = Time("2024-10-22 14:30:00", scale="utc")
orbit = Orbit.from_vectors(Earth, r, v, epoch=t)
assert orbit.attractor == Earth
assert isinstance(orbit.epoch, Epoch)
def test_from_classical_with_astropy_time(self):
t = Time("2000-01-01 12:00:00", scale="tt")
orbit = Orbit.from_classical(
Earth,
a=7000 << u.km,
ecc=0.01 << u.one,
inc=51.6 << u.deg,
raan=0 << u.deg,
argp=0 << u.deg,
nu=0 << u.deg,
epoch=t,
)
assert orbit.attractor == Earth
assert isinstance(orbit.epoch, Epoch)
assert abs(orbit.epoch.jd_tt - 2451545.0) < 1e-9
def test_backward_compatibility_with_epoch(self):
r = np.array([7000e3, 0, 0])
v = np.array([0, 7546, 0])
epoch = Epoch.j2000_epoch()
orbit = Orbit.from_vectors(Earth, r, v, epoch=epoch)
assert orbit.epoch == epoch
def test_default_epoch_still_works(self):
r = np.array([7000e3, 0, 0])
v = np.array([0, 7546, 0])
orbit = Orbit.from_vectors(Earth, r, v)
assert abs(orbit.epoch.jd_tt - 2451545.0) < 1e-9
def test_time_scales_preserved_in_orbit(self):
r = np.array([7000e3, 0, 0])
v = np.array([0, 7546, 0])
for scale in ["utc", "tai", "tt"]:
t = Time("2020-06-15 10:30:00", scale=scale)
orbit = Orbit.from_vectors(Earth, r, v, epoch=t)
assert isinstance(orbit.epoch, Epoch)
def test_orbit_properties_with_astropy_time(self):
t = Time("2024-10-22", scale="utc")
orbit = Orbit.from_classical(
Earth,
a=7000 << u.km,
ecc=0.0 << u.one,
inc=0 << u.deg,
raan=0 << u.deg,
argp=0 << u.deg,
nu=0 << u.deg,
epoch=t,
)
assert abs(orbit.a.to_value(u.km) - 7000) < 1
assert orbit.ecc.value < 0.01
assert abs(orbit.period.to_value(u.hour) - 1.62) < 0.1
class TestEdgeCases:
def test_unsupported_time_scale_error(self):
pass
def test_very_old_epoch(self):
import warnings
from erfa import ErfaWarning
with warnings.catch_warnings():
warnings.simplefilter("ignore", ErfaWarning)
t = Time("1900-01-01 00:00:00", scale="utc")
epoch = astropy_time_to_epoch(t)
t_back = epoch_to_astropy_time(epoch, scale="utc")
assert abs(t_back.jd - t.jd) < 1e-8
def test_far_future_epoch(self):
import warnings
from erfa import ErfaWarning
with warnings.catch_warnings():
warnings.simplefilter("ignore", ErfaWarning)
t = Time("2100-12-31 23:59:59", scale="utc")
epoch = astropy_time_to_epoch(t)
t_back = epoch_to_astropy_time(epoch, scale="utc")
assert abs(t_back.jd - t.jd) < 1e-8
def test_leap_second_handling(self):
t_before = Time("2015-06-30 23:59:59", scale="utc")
epoch = astropy_time_to_epoch(t_before)
t_back = epoch_to_astropy_time(epoch, scale="utc")
assert abs(t_back.jd - t_before.jd) < 1e-9
def test_integration_summary():
t = Time("2024-10-22 14:30:00", scale="utc")
orbit = Orbit.from_classical(
Earth,
a=7000 << u.km,
ecc=0.01 << u.one,
inc=51.6 << u.deg,
raan=0 << u.deg,
argp=0 << u.deg,
nu=0 << u.deg,
epoch=t,
)
assert orbit.a.to_value(u.km) > 6000
assert orbit.ecc.value < 0.1
assert orbit.period.to_value(u.hour) > 1
assert isinstance(orbit.epoch, Epoch)
future = orbit.propagate(3600) assert isinstance(future, Orbit)