diff --git a/PROJECT.md b/PROJECT.md index 41591ba..d508809 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -83,7 +83,4 @@ Moon and dwarf planets (Pluto, Ceres, Eris) may be added later. - TODO: Implement core `PlanetaryTime` class - TODO: Implement conversion from Earth `datetime` - TODO: Implement `__str__` / `__repr__` -- TODO: Write tests for conversion accuracy -- TODO: Write tests for epoch switching -- TODO: Populate README with usage examples - TODO: Implement `scripts/refresh_data.py` — fetches rotation periods, orbital periods and discovery dates from Wikidata SPARQL endpoint and regenerates hardcoded data in `body.py`, `moon.py` and `epoch.py`; script is not part of the distributed package diff --git a/README.md b/README.md index 8da4c84..960e9b5 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,8 @@ print(pt.sol) # 668 print(pt.hour) # 14 print(pt.minute) # 22 print(pt.second) # 7 +print(pt.time) # "14:22:07" +print(pt.date) # "Year 415, Sol 668" # Mars time since first contact (Viking 1, 1976) pt = PlanetaryTime.from_earth(now, Body.MARS, EpochType.CONTACT) diff --git a/tests/test_planetary_time.py b/tests/test_planetary_time.py index 663c896..092295b 100644 --- a/tests/test_planetary_time.py +++ b/tests/test_planetary_time.py @@ -80,3 +80,91 @@ def test_repr_contains_year_and_sol() -> None: assert "PlanetaryTime(" in r assert "year=" in r assert "sol=" in r + + +# ------------------------------------------------------------------ +# Conversion accuracy +# ------------------------------------------------------------------ + +def test_conversion_accuracy_sol_hour_minute() -> None: + """26h 30m after epoch on Mars: sol 1, hour 1, minute 30.""" + epoch_dt = get_epoch_date(Body.MARS, EpochType.DISCOVERY) + # Mars sol = 25 h; 26h 30m = 1 sol + 1h 30m + pt = PlanetaryTime.from_earth(epoch_dt + timedelta(hours=26, minutes=30), Body.MARS, EpochType.DISCOVERY) + assert pt.year == 0 + assert pt.sol == 1 + assert pt.hour == 1 + assert pt.minute == 30 + assert pt.second == 0 + + +def test_conversion_accuracy_seconds() -> None: + """45 seconds after epoch: only second counter advances.""" + epoch_dt = get_epoch_date(Body.MARS, EpochType.DISCOVERY) + pt = PlanetaryTime.from_earth(epoch_dt + timedelta(seconds=45), Body.MARS, EpochType.DISCOVERY) + assert pt.year == 0 + assert pt.sol == 0 + assert pt.hour == 0 + assert pt.minute == 0 + assert pt.second == 45 + + +def test_conversion_accuracy_year_boundary() -> None: + """Exactly one Mars year after epoch lands on year 1, sol 0, 00:00:00.""" + epoch_dt = get_epoch_date(Body.MARS, EpochType.DISCOVERY) + one_year_seconds = Body.MARS.sols_per_year * Body.MARS.hours_per_sol * 3600 + pt = PlanetaryTime.from_earth(epoch_dt + timedelta(seconds=one_year_seconds), Body.MARS, EpochType.DISCOVERY) + assert pt.year == 1 + assert pt.sol == 0 + assert pt.hour == 0 + assert pt.minute == 0 + assert pt.second == 0 + + +# ------------------------------------------------------------------ +# Epoch switching +# ------------------------------------------------------------------ + +def test_epoch_switching_discovery_vs_contact_differ() -> None: + """Discovery (1610) and contact (1976) epochs give significantly different years.""" + dt = datetime(2024, 1, 1, tzinfo=timezone.utc) + pt_disc = PlanetaryTime.from_earth(dt, Body.MARS, EpochType.DISCOVERY) + pt_cont = PlanetaryTime.from_earth(dt, Body.MARS, EpochType.CONTACT) + assert pt_disc.year > pt_cont.year + + +def test_epoch_switching_preserves_body_and_epoch_type() -> None: + """Switching epoch type is reflected in the epoch_type property; body stays the same.""" + dt = datetime(2024, 1, 1, tzinfo=timezone.utc) + pt_disc = PlanetaryTime.from_earth(dt, Body.MARS, EpochType.DISCOVERY) + pt_cont = PlanetaryTime.from_earth(dt, Body.MARS, EpochType.CONTACT) + assert pt_disc.body is Body.MARS + assert pt_cont.body is Body.MARS + assert pt_disc.epoch_type is EpochType.DISCOVERY + assert pt_cont.epoch_type is EpochType.CONTACT + + +def test_epoch_switching_contact_sol_count_is_smaller() -> None: + """Contact epoch is later, so sol count from contact is smaller than from discovery.""" + dt = datetime(2024, 1, 1, tzinfo=timezone.utc) + pt_disc = PlanetaryTime.from_earth(dt, Body.MARS, EpochType.DISCOVERY) + pt_cont = PlanetaryTime.from_earth(dt, Body.MARS, EpochType.CONTACT) + total_sols_disc = pt_disc.year * Body.MARS.sols_per_year + pt_disc.sol + total_sols_cont = pt_cont.year * Body.MARS.sols_per_year + pt_cont.sol + assert total_sols_disc > total_sols_cont + + +# ------------------------------------------------------------------ +# time and date properties +# ------------------------------------------------------------------ + +def test_time_property_format() -> None: + epoch_dt = get_epoch_date(Body.MARS, EpochType.DISCOVERY) + pt = PlanetaryTime.from_earth(epoch_dt + timedelta(hours=3, minutes=7, seconds=9), Body.MARS, EpochType.DISCOVERY) + assert pt.time == "03:07:09" + + +def test_date_property_format() -> None: + epoch_dt = get_epoch_date(Body.MARS, EpochType.DISCOVERY) + pt = PlanetaryTime.from_earth(epoch_dt + timedelta(hours=Body.MARS.hours_per_sol + 1), Body.MARS, EpochType.DISCOVERY) + assert pt.date == "Year 0, Sol 1"