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.tdbTime 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
jd2magnitude small (< 1.0 day) to preserve floating-point precision - Use
jd1for large epoch offsets (e.g., setjd1to the integer Julian Date) - Avoid fractional parts in
jd1that have more than a few decimal significant figures - The internal
_rebalance()function automatically maintains these constraints
Time Scales
AstroEpochs supports various astronomical time scales.
| Scale | Description |
|---|---|
| TAI | International Atomic Time - Uniform atomic time scale based on cesium atomic clocks |
| TT | Terrestrial Time - Theoretical uniform time scale for Earth-based observations (TT = TAI + 32.184s) |
| TDB | Barycentric Dynamical Time - Time scale for solar system dynamics, corrected for relativistic effects |
| UTC | Coordinated Universal Time - Civil time standard with leap seconds to maintain alignment with Earth rotation |
| TCB | Barycentric Coordinate Time - Coordinate time in the barycentric reference system |
| TCG | Geocentric 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.tdbTime Formats
AstroEpochs supports multiple time formats for input and output:
| Format | Description | Example |
|---|---|---|
| JD | Julian Date - Days since January 1, 4713 BCE at noon UTC | 2451545.0 |
| JD (precision) | Julian Date with split representation for high precision | jd1=2451545.0, jd2=0.378264 |
| MJD | Modified Julian Date - JD minus 2400000.5 | 51544.5 |
| ISOT | ISO 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
AstroEpochs.OFFSET_TABLEAstroEpochs.ISOTAstroEpochs.JDAstroEpochs.MJDAstroEpochs.TAIAstroEpochs.TCBAstroEpochs.TCGAstroEpochs.TDBAstroEpochs.TTAstroEpochs.TimeAstroEpochs.TimeAstroEpochs.TimeAstroEpochs.TimeAstroEpochs.TimeAstroEpochs.TimeAstroEpochs.TimeAstroEpochs.TimeAstroEpochs.UTCAstroEpochs._format_tag_strAstroEpochs._from_formatAstroEpochs._isot_to_dateAstroEpochs._rebalanceAstroEpochs._rebalanceAstroEpochs._scale_tag_strAstroEpochs._to_isotAstroEpochs._to_mjdAstroEpochs._typename_parenAstroEpochs._validate_formatAstroEpochs._validate_inputcouplingAstroEpochs._validate_scaleAstroEpochs.apply_offsetsAstroEpochs.calhms2jd_precAstroEpochs.get_conversion_pathBase.:+Base.:+Base.:-Base.:-Base.:==Base.getpropertyBase.showBase.show
API Reference
AstroEpochs.ISOT — Type
ISOT <: AbstractTimeFormatISO 8601 Time format.
AstroEpochs.JD — Type
JD <: AbstractTimeFormatJulian Date format.
AstroEpochs.MJD — Type
MJD <: AbstractTimeFormatModified Julian Date format.
AstroEpochs.TAI — Type
TAI <: AbstractTimeScaleInternational Atomic Time scale.
AstroEpochs.TCB — Type
TCB <: AbstractTimeScaleBarycentric Coordinate Time scale.
AstroEpochs.TCG — Type
TCG <: AbstractTimeScaleGeocentric Coordinate Time scale.
AstroEpochs.TDB — Type
TDB <: AbstractTimeScaleBarycentric Dynamical Time scale.
AstroEpochs.TT — Type
TT <: AbstractTimeScaleTerrestrial Time scale.
AstroEpochs.Time — Type
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 + jd2equals the epoch’s Julian Date. Internally,_rebalancekeeps_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.jdandt.mjdreturn numeric date values;t.isotreturns 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.0advances by one day;t2 - t1returns 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)AstroEpochs.Time — Method
Time(jd1, jd2, scale, format)Construct time given single-value Julian Date with validation.
AstroEpochs.Time — Method
Time(value::Real, s::AbstractTimeScale, f::AbstractTimeFormat) -> TimeConstruct time given single-value JD and typed scale/format tags.
AstroEpochs.Time — Method
Time(jd1::Real, jd2::Real, s::AbstractTimeScale, f::AbstractTimeFormat) -> TimeConstruct time given JD parts and typed scale/format tags.
AstroEpochs.Time — Method
Time(jd1::Real, jd2::Real, scale::Symbol, format::Symbol)Promotes to T = promote_type(typeof(jd1), typeof(jd2)) and constructs Time{T}.
AstroEpochs.Time — Method
Time(value::Real, scale::Symbol, format::Symbol)Construct time given single-value Julian Date.
AstroEpochs.Time — Method
Time(isostr::String, s::AbstractTimeScale, f::AbstractTimeFormat) -> TimeConstruct time given ISOT string and typed scale/format tags.
AstroEpochs.Time — Method
Time(isostr::String, scale::Symbol, format::Symbol) → TimeConstruct time given time in ISOT format.
AstroEpochs.UTC — Type
UTC <: AbstractTimeScaleCoordinated Universal Time scale.
AstroEpochs._format_tag_str — Method
_typename_paren(x) = string(nameof(typeof(x)), "()")Helper function to print type name with parentheses in show().
AstroEpochs._from_format — Method
_from_format(value::Real, scale::Symbol, format::Symbol) -> Time{T}Construct a Time{T} from a JD/MJD numeric value, preserving T = typeof(value).
AstroEpochs._isot_to_date — Method
_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).
AstroEpochs._rebalance — Method
function _rebalance(a::Real, b::Real)Promote to a common T and reuse the typed method
AstroEpochs._rebalance — Method
function _rebalance(jd1::Real, jd2::Real)Rebalance jd1 and jd2 so that -0.5 ≤ jd2 ≤ 0.5, with the remainder placed in jd1.
AstroEpochs._scale_tag_str — Method
_typename_paren(x) = string(nameof(typeof(x)), "()")Helper function to print type name with parentheses in show().
AstroEpochs._to_isot — Method
_to_isot(t::Time)Return time value in ISOT format rounding to millisecond.
AstroEpochs._to_mjd — Method
_to_mjd(t::Time)Return time value in Modified Julian Date (MJD) format.
AstroEpochs._typename_paren — Method
_typename_paren(x) = string(nameof(typeof(x)), "()")Print a type name with parentheses for display.
AstroEpochs._validate_format — Method
_validate_format(format::Symbol)Validate time format against supported formats
AstroEpochs._validate_inputcoupling — Method
_validate_inputcoupling(value::Any, format::Symbol)Validate that value is compatible with the specified time format.
AstroEpochs._validate_scale — Method
function _validate_scale(scale::Symbol)Validate time scale against supported scales
AstroEpochs.apply_offsets — Method
apply_offsets(jd1, jd2, from::Symbol, to::Symbol)Applies sequence of scale conversions from from to to.
AstroEpochs.calhms2jd_prec — Method
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
AstroEpochs.get_conversion_path — Method
get_conversion_path(from::Symbol, to::Symbol) → Vector{Symbol}Returns conversion path from from to to time scales.
Base.getproperty — Method
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, :tcbto 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/jd2are 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.taiAstroEpochs.OFFSET_TABLE — Constant
const OFFSET_TABLE = Dict{Tuple{Symbol, Symbol}, Function}Maps adjacent pairs time scales to conversion functions.