# PlanetaryTime Python library for representing and working with time on other bodies in the Solar System — similar in spirit to the standard `datetime` module. Time on a given body is expressed using a human-readable clock where **one hour is as close to one Earth hour as possible**. The number of hours per sol (day) is derived from the body's sidereal rotation period rounded to the nearest integer. ## Installation ```bash pip install planetarytime ``` ## Supported bodies ### Planets | Body | Hours per sol | Sols per year | |---------|--------------|---------------| | Mercury | 1408 | 2 | | Venus | 5833 | 1 | | Mars | 25 | 670 | | Jupiter | 10 | 10476 | | Saturn | 11 | 24491 | | Uranus | 17 | 42718 | | Neptune | 16 | 89667 | ### Moons Accessible via `Body.[index]`, ordered by distance from the planet. | Planet | Index | Moon | Hours per sol | Tidally locked | |---------|-------|-----------|--------------|----------------| | Mars | 0 | Phobos | 8 | yes | | Mars | 1 | Deimos | 30 | yes | | Jupiter | 0 | Io | 42 | yes | | Jupiter | 1 | Europa | 85 | yes | | Jupiter | 2 | Ganymede | 172 | yes | | Jupiter | 3 | Callisto | 401 | yes | | Saturn | 0 | Titan | 383 | yes | | Saturn | 1 | Enceladus | 33 | yes | | Uranus | 0 | Miranda | 34 | yes | | Uranus | 1 | Ariel | 60 | yes | | Uranus | 2 | Umbriel | 99 | yes | | Uranus | 3 | Titania | 209 | yes | | Uranus | 4 | Oberon | 323 | yes | | Neptune | 0 | Triton | 141 | yes | For tidally locked moons, one sol equals one year (one orbit around the parent planet). ## Usage ### Planets ```python from datetime import datetime, timezone from planetarytime import Body, EpochType, PlanetaryTime now = datetime.now(timezone.utc) # Mars time since discovery (Galileo, 1610) pt = PlanetaryTime.from_earth(now, Body.MARS, EpochType.DISCOVERY) print(pt) # Year 217, Sol 579, 11:00:00 (Mars / discovery epoch) print(pt.year) # 217 print(pt.sol) # 579 print(pt.hour) # 11 print(pt.minute) # 0 print(pt.second) # 0 print(pt.time) # "11:00:00" print(pt.date) # "Year 217, Sol 579" # Mars time since first contact (Viking 1, 1976) pt = PlanetaryTime.from_earth(now, Body.MARS, EpochType.CONTACT) print(pt) # Year 26, Sol 25, 15:00:00 (Mars / contact epoch) ``` ### Moons ```python # Titan (Saturn's largest moon) — accessible via Body.SATURN[0] titan = Body.SATURN[0] # Time since discovery (Christiaan Huygens, 1655) pt = PlanetaryTime.from_earth(now, titan, EpochType.DISCOVERY) print(pt) # Year 8492, Sol 0, 344:00:00 (Titan / discovery epoch) # Time since Huygens probe landing (2005-01-14) pt = PlanetaryTime.from_earth(now, titan, EpochType.CONTACT) print(pt) # Year 486, Sol 0, 282:00:00 (Titan / contact epoch) # Check if a moon is tidally locked print(titan.is_tidally_locked) # True ``` ### Epochs | EpochType | Meaning | |-----------------------|-------------------------------------------| | `EpochType.DISCOVERY` | First recorded observation of the body | | `EpochType.CONTACT` | First probe landing or crewed landing | `EpochUnavailableError` is raised when `CONTACT` is requested for a body that has not been visited yet. ### Exceptions ```python from planetarytime import EpochType, PlanetaryTime, Body from planetarytime.exceptions import EpochUnavailableError, DatetimePrecedesEpochError # Body with no contact yet try: PlanetaryTime.from_earth(now, Body.JUPITER, EpochType.CONTACT) except EpochUnavailableError as e: print(e) # No contact with Jupiter has occurred — contact epoch is unavailable. # Datetime before the epoch from datetime import datetime, timezone try: PlanetaryTime.from_earth(datetime(1600, 1, 1, tzinfo=timezone.utc), Body.MARS, EpochType.DISCOVERY) except DatetimePrecedesEpochError as e: print(e) ``` ## Logging This library uses [loguru](https://github.com/Delgan/loguru) for internal logging. By default, loguru has a sink to `stderr` enabled. If your application also uses loguru, library logs appear in your configured sinks automatically. ### Suppress library logs ```python from loguru import logger logger.disable("planetarytime") ``` ### Filter by level ```python from loguru import logger import sys logger.add(sys.stderr, filter={"planetarytime": "WARNING"}) ``` ## Refreshing data Rotation periods, orbital periods, and discovery/contact dates are stored in [`src/planetarytime/_data.py`](src/planetarytime/_data.py). To regenerate this file from Wikidata: ```bash python scripts/refresh_data.py # fetch and write _data.py python scripts/refresh_data.py --dry-run # preview without writing ``` The script requires only the Python standard library. Run your test suite afterwards to verify the updated values. ## License MIT