AstroEpochs

The AstroEpochs module provides time system implementations for astronomical applications. AstroEpochs supports high-precision time representations using dual-float Julian Date storage - parameterized for differentiability - and conversions between time scales and formats.

Key Features:

  • High-precision storage using dual Float64 values (jd1, jd2) to represent Julian Dates
  • Automatic scale conversion via property access (e.g., t.tt, t.utc, t.tdb)
  • Multiple input formats including Julian Date, Modified Julian Date, and ISO 8601 strings
  • Time arithmetic supporting addition and subtraction of time intervals
  • Type stability preserving numeric types through operations
  • Differentiability using standard packages such as FiniteDiff and Zygote

Acknowledgements

The API for AstroEpochs is inspired by Astropy.Time. The numerics are built on Julia Space Mission Design's Tempo.jl library. AstroEpochs.jl is tested against Astropy.Time.

Comparison with Other Julia Time-Keeping Libraries

Tempo.jl and AstroTime.jl are other high-quality Julia packages for astronomical time handling with distinct design philosophies. AstroTime.jl, developed by the JuliaAstro community, supports six astronomical time scales (TAI, TT, TCG, TCB, TDB, and UT1) using scale-specific types that change with each conversion. Tempo.jl supports UTC, TAI, TT, TDB, TCG, and TCB with efficient, allocation-free conversions and a type-stable architecture that allows time scale changes without changing the struct type—critical for performance in Epicycle's propagation and optimization algorithms. AstroEpochs.jl is an API built on Tempo.jl that provides type stability and seamless integration with the Epicycle ecosystem while using an interface styled after AstroPy.Time, a widely adopted standard in the astronomical community.

Quickstart

using AstroEpochs

# Create from Julian Date
t1 = Time(2451545.0, TT(), JD())

# Create from Modified Julian Date  
t2 = Time(51544.5, UTC(), MJD())

# Create from ISO string
t3 = Time("2000-01-01T12:00:00.000", TAI(), ISOT())

# Access different representations
t1.jd        # Julian Date value
t1.mjd       # Modified Julian Date value  
t1.isot      # ISO 8601 string

# Convert between scales (creates new Time object)
t_utc = t1.utc
t_tdb = t1.tdb

Time Struct

The Time struct is the core type for representing astronomical epochs with high precision. It uses a split Julian Date representation to maintain numerical accuracy over long time spans and supports automatic conversions between different time scales and formats.

Fields:

  • jd1 — Primary component of the split Julian Date (typically the integer part)
  • jd2 — Secondary component of the split Julian Date (typically the fractional part)
  • scale — Time scale tag struct (TT(), TAI(), UTC(), TDB(), TCB(), TCG())
  • format — Time format tag struct (JD(), MJD(), ISOT())

The split representation maintains precision by keeping jd2 small (∈ [-0.5, 0.5)) while jd1 carries the large offset. The complete Julian Date is jd1 + jd2.

Precision Guidelines: For maximum precision, follow these best practices:

  • Keep jd2 magnitude small (< 1.0 day) to preserve floating-point precision
  • Use jd1 for large epoch offsets (e.g., set jd1 to the integer Julian Date)
  • Avoid fractional parts in jd1 that have more than a few decimal significant figures
  • The internal _rebalance() function automatically maintains these constraints

Time Scales

AstroEpochs supports various astronomical time scales.

ScaleDescription
TAIInternational Atomic Time - Uniform atomic time scale based on cesium atomic clocks
TTTerrestrial Time - Theoretical uniform time scale for Earth-based observations (TT = TAI + 32.184s)
TDBBarycentric Dynamical Time - Time scale for solar system dynamics, corrected for relativistic effects
UTCCoordinated Universal Time - Civil time standard with leap seconds to maintain alignment with Earth rotation
TCBBarycentric Coordinate Time - Coordinate time in the barycentric reference system
TCGGeocentric Coordinate Time - Coordinate time in the geocentric reference system

The examples below illustrate how to create a time struct in various time scales.

using AstroEpochs

# Time using TAI
t_tai = Time(51545.0, TAI(), MJD())

# Time using TDB
t_tdb = Time(51545.0, TDB(), MJD())

# Time using UTC
t_utc = Time(51545.0, UTC(), MJD())

# Time using TCB
t_tcb = Time(51545.0, TCB(), MJD())

# Time using TCG
t_tcg = Time(51545.0, TCG(), MJD())

# View all supported scales
subtypes(AstroEpochs.AbstractTimeScale)

Converting between time scales creates a new Time object with the converted epoch:

# Convert from TAI to TT
t_tai = Time(51545.0, TAI(), MJD())
t_tt = t_tai.tt

# Convert from UTC to TDB
t_utc = Time(51545.0, UTC(), MJD())
t_tdb = t_utc.tdb

# Chain conversions while preserving format
t_final = t_utc.tai.tt.tdb

Time Formats

AstroEpochs supports multiple time formats for input and output:

FormatDescriptionExample
JDJulian Date - Days since January 1, 4713 BCE at noon UTC2451545.0
JD (precision)Julian Date with split representation for high precisionjd1=2451545.0, jd2=0.378264
MJDModified Julian Date - JD minus 2400000.551544.5
ISOTISO 8601 timestamp string"2000-01-01T12:00:00.000"

The examples below illustrate how to create time objects using different time formats.

using AstroEpochs

# Julian Date format
t_jd = Time(2451545.0, TT(), JD())

# Modified Julian Date format  
t_mjd = Time(51544.5, TT(), MJD())

# ISO 8601 string format
t_iso = Time("2000-01-01T12:00:00.000", TT(), ISOT())

# High-precision Julian Date using split representation
t_precise = Time(2451545.0, 0.37826388888889, TT(), JD())  

# View all supported formats
subtypes(AstroEpochs.AbstractTimeFormat)

Converting between formats (returns numeric values or strings, not new Time objects):

# Start with a time in JD format
t = Time(2451545.25, UTC(), JD())

# Access different format representations
jd_value = t.jd      # 2451545.25 (Julian Date)
mjd_value = t.mjd    # 51544.75 (Modified Julian Date)  
iso_string = t.isot  # "2000-01-01T18:00:00.000" (ISO string)

# Note: Format conversions return values, not new Time structs
# To create a new Time with different format, use the constructor
t_mjd_format = Time(t.mjd, UTC(), MJD())

Time Differentiation

AstroEpochs supports automatic differentiation for time-dependent calculations using standard Julia AD packages. The Time struct preserves numeric types through operations, enabling differentiation of functions that depend on time.

Using FiniteDiff.jl:

using AstroEpochs, FiniteDiff

# Define a function that depends on time
function time_dependent_function(jd_offset)
    t = Time(2451545.0 + jd_offset, TT(), JD())
    # Convert to TDB and extract Julian Date
    return t.tdb.jd
end

# Compute derivative with respect to Julian Date offset
jd_offset = 0.5  # half day offset
derivative = FiniteDiff.finite_difference_derivative(time_dependent_function, jd_offset)
println("d(TDB)/d(JD) ≈ $derivative")

Using Zygote.jl:

using AstroEpochs, Zygote

# Create base time object outside the differentiated function
t_base = Time(2451545.0, TAI(), JD())

# Define a function that adds time in a specific scale
function add_time_in_scale(seconds_offset, scale_sym)
    t_in_scale = getproperty(t_base, scale_sym)  # Convert to desired scale
    dt_days = seconds_offset / 86400.0           # Convert seconds to days
    t_future = t_in_scale + dt_days              # Add time offset
    t_back = getproperty(t_future, t_base.scale) # Convert back to original scale
    return t_back.jd                             # Return Julian Date
end

# Compute gradient with respect to seconds added in TDB scale
seconds = 3600.0  # 1 hour in seconds
grad = Zygote.gradient(s -> add_time_in_scale(s, :tdb), seconds)
println("∇(JD)/∇(TDB_seconds) = $(grad[1])")

Index

API Reference

AstroEpochs.TimeType
Time{T<:Real}

High-precision astronomical epoch represented as a split Julian Date with an associated time scale and format.

Fields

  • _jd1::Real — first component of the split Julian Date (access via t.jd1).
  • _jd2::Real — second component of the split Julian Date (access via t.jd2).
  • scale::Symbol — time scale tag, one of: :tt, :tai, :tdb, :utc, :tcg, :tcb.
  • format::Symbol — time format tag, one of: :jd, :mjd, :isot.

Notes:

  • Invariant: jd1 + jd2 equals the epoch’s Julian Date. Internally, _rebalance keeps _jd2 ∈ [-0.5, 0.5).

  • Property access performs on-demand conversions:

    • t.tt, t.tdb, t.utc, … return a new Time converted to that scale.
    • t.jd and t.mjd return numeric date values; t.isot returns an ISO 8601 string.
  • Constructors accept Symbols (shown) and also typed tags (e.g., TT(), TDB(), JD(), MJD(), ISOT()).

  • Time arithmetic uses days: t + 1.0 advances by one day; t2 - t1 returns a Real (days).

  • Validation is strict: scales and formats must be supported; ISO input strings must match YYYY-MM-DDTHH:MM:SS.sss.

  • Time currently mixes symbols and instances for time scales and formats. Future versions will standardize on typed tags (e.g., TT(), TDB(), JD()) to avoid ambiguity.

  • To maintain precision, jd2 should remain small (~< 1.0); use jd1 for large offsets.

    Examples

using AstroEpochs

# Construct from JD whole (TT scale)
t1 = Time(2451545.25, TT(), JD())

# Construct from JD parts (TT scale)
t1 = Time(2451545.0, 0.25, TT(), JD())

# Convert scale and format
t2 = t1.tdb
println(typeof(t2))
println(t2.mjd)      # numeric MJD
println(t2.isot)     # ISO 8601 string

# Construct from MJD value (TDB scale)
t3 = Time(58000.0, TDB(), MJD())
println(t3.jd)       # numeric JD

# Construct from ISO string (UTC scale), tagged as MJD format
t4 = Time("2017-01-01T00:00:00.000", UTC(), ISOT())
println(t4.jd)       # numeric JD

# Arithmetic (days)
t5 = t3 + 2.0
println(t5.jd - t3.jd)

# Difference (days), same scale/format required
dt = t5 - t3
println(dt)
source
AstroEpochs.TimeMethod
Time(jd1, jd2, scale, format)

Construct time given single-value Julian Date with validation.

source
AstroEpochs.TimeMethod
Time(value::Real, s::AbstractTimeScale, f::AbstractTimeFormat) -> Time

Construct time given single-value JD and typed scale/format tags.

source
AstroEpochs.TimeMethod
Time(jd1::Real, jd2::Real, s::AbstractTimeScale, f::AbstractTimeFormat) -> Time

Construct time given JD parts and typed scale/format tags.

source
AstroEpochs.TimeMethod
Time(jd1::Real, jd2::Real, scale::Symbol, format::Symbol)

Promotes to T = promote_type(typeof(jd1), typeof(jd2)) and constructs Time{T}.

source
AstroEpochs.TimeMethod
Time(value::Real, scale::Symbol, format::Symbol)

Construct time given single-value Julian Date.

source
AstroEpochs.TimeMethod
Time(isostr::String, s::AbstractTimeScale, f::AbstractTimeFormat) -> Time

Construct time given ISOT string and typed scale/format tags.

source
AstroEpochs.TimeMethod
Time(isostr::String, scale::Symbol, format::Symbol) → Time

Construct time given time in ISOT format.

source
AstroEpochs._from_formatMethod
_from_format(value::Real, scale::Symbol, format::Symbol) -> Time{T}

Construct a Time{T} from a JD/MJD numeric value, preserving T = typeof(value).

source
AstroEpochs._isot_to_dateMethod
_isot_to_date(isostr::String) → Tuple{Int, Int, Int, Int, Int, Real}

Validate and parse an ISO 8601 string of the form "YYYY-MM-DDTHH:MM:SS.sss". Returning calendar fields: (year, month, day, hour, minute, second).

source
AstroEpochs._rebalanceMethod
function _rebalance(jd1::Real, jd2::Real)

Rebalance jd1 and jd2 so that -0.5 ≤ jd2 ≤ 0.5, with the remainder placed in jd1.

source
AstroEpochs.calhms2jd_precMethod
calhms2jd_prec(Y::I, M::I, D::I, h::I, m::I, sec::N) where {I <: Integer, N <: Number}

Convert calendar date and time to Julian Date

source
Base.:+Method
function +(t::Time, dt::Real)

Add a scalar (in days) to a Time object, commutative version.

source
Base.:+Method
function +(t::Time, dt::Real)

Add a scalar (in days) to a Time object, returning a new Time struct.

source
Base.:-Method
function -(t2::Time, t1::Time)

Subtract two Time objects, returning the difference in days.

source
Base.:-Method
-(t::Time{T}, dt::Real) -> Time{PT}

Subtract days dt from t returning new time object.

source
Base.:==Method
==(a::Time, b::Time)

Equality operator for Time. Same scale and format, and identical jd1/jd2 values.

source
Base.getpropertyMethod
Base.getproperty(t::Time, name::Symbol)

Provide field access and on-demand conversions for Time via property syntax.

Arguments

  • t::Time — the epoch object.
  • name::Symbol — one of:
    • Raw fields: :jd1, :jd2, :scale, :format
    • Format accessors: :jd (Real), :mjd (Real), :isot (String)
    • Scale conversions: any of :tt, :tai, :tdb, :utc, :tcg, :tcb to return a new Time in that scale.

Returns

  • Real for :jd, :mjd, :jd1, :jd2
  • Symbol for :scale, :format
  • String for :isot
  • Time for scale symbols (converted, preserving current format)

Notes

  • Scale conversions are performed through apply_offsets following the configured conversion graph.
  • When converting scales, jd1/jd2 are rebalanced to keep invariants.
  • Unknown properties throw an error.
  • Scale conversions preserve format and return a new Time object; the original is unchanged.
  • Format computations return a time value (as opposed to a new Time struct).

Examples

using AstroEpochs

t = Time("2024-02-29T12:34:56.123", TAI(), ISOT())

# Raw fields
t.jd1; t.jd2; t.scale; t.format

# Format accessors
t.jd        # numeric JD
t.mjd       # numeric MJD
t.isot      # ISO 8601 string

# Scale conversion (preserves current format :isot)
tt_time = t.tt
tai_time = tt_time.tai
source
Base.showMethod
Base.show(io::IO, ::MIME"text/plain", t::Time)

Pretty, stable text/plain rendering for Time.

source
Base.showMethod
Base.show(io::IO, t::Time)

Delegate to the text/plain renderer so print/println/sprint(show, t) use the same output.

source
AstroEpochs.OFFSET_TABLEConstant
const OFFSET_TABLE = Dict{Tuple{Symbol, Symbol}, Function}

Maps adjacent pairs time scales to conversion functions.

source