Epoch conversions shared by the GNSS correction wrappers.
These helpers turn an Elixir NaiveDateTime or a
{{year, month, day}, {hour, minute, second}} tuple into the two
representations the sidereon-core crate consumes:
- a split Julian date
{jd_whole, fraction}wherejd_wholeis the*.5midnight boundary of the civil day andfractionis the within-day part (the same convention the SP3 reader uses); - integer or continuous seconds since the J2000 epoch (JD 2451545.0), used by NIF calls that consume either exact product epoch axes or fractional receive times.
No leap-second shifting is applied: the epoch stays in the time scale the caller supplied it in (typically GPS time for these models).
Summary
Types
A time scale, named either by atom (:gpst, :utc, :glonasst, ...) or by
its uppercase abbreviation string ("GPST", "UTC", ...).
Functions
Fractional day-of-year of the epoch, as the float the Niell troposphere
seasonal term consumes.
Convert an epoch to integer seconds since the J2000 epoch (JD 2451545.0).
Convert an epoch to continuous floating-point seconds since J2000.
Convert an epoch to the split Julian date {jd_whole, fraction}.
GPS minus UTC, in seconds, in effect at a UTC calendar date.
Provenance and coverage of the embedded leap-second table.
TAI minus UTC, in seconds, in effect at a UTC calendar date.
TAI minus UTC for a list of UTC calendar dates.
Seconds-of-day in [0, 86400), formed from the epoch's clock fields.
TAI minus UTC, in seconds, in effect at a UTC calendar date.
The supported time-scale atoms.
Fixed inter-system time offset to - from, in seconds.
Leap-aware inter-system time offset to - from, in seconds, at utc_jd.
Provenance and coverage of the embedded UT1/EOP table.
Validated UTC instant for an epoch, as the split Julian date {jd_whole, fraction}.
Types
@type leap_second_table() :: %{ source: String.t(), first_mjd: integer(), last_mjd: integer(), entries: non_neg_integer() }
A time scale, named either by atom (:gpst, :utc, :glonasst, ...) or by
its uppercase abbreviation string ("GPST", "UTC", ...).
Functions
@spec day_of_year(NaiveDateTime.t() | tuple()) :: float()
Fractional day-of-year of the epoch, as the float the Niell troposphere
seasonal term consumes.
January 1 00:00 is 1.0. The continuous day-of-year comes from
sidereon_core::astro::time::civil::day_of_year, matching the crate's
fractional SolveInputs.day_of_year convention, so the SPP troposphere and
Sidereon.GNSS.Troposphere agree for the same epoch.
@spec epoch_to_j2000_seconds(NaiveDateTime.t() | tuple()) :: {:ok, integer()} | {:error, term()}
Convert an epoch to integer seconds since the J2000 epoch (JD 2451545.0).
A whole-second epoch yields an exact integer (the core returns the exact
whole-second value, which is converted back to an integer here). Returns
{:ok, seconds} or {:error, :non_integer_second_epoch} if the epoch carries
a sub-second part. The continuous seconds come from
sidereon_core::astro::time::civil::j2000_seconds.
@spec epoch_to_j2000_seconds_fractional(NaiveDateTime.t() | tuple()) :: {:ok, float()} | {:error, term()}
Convert an epoch to continuous floating-point seconds since J2000.
Unlike epoch_to_j2000_seconds/1, this accepts sub-second NaiveDateTime
values and tuple epochs with a floating-point seconds field. Delegates to
sidereon_core::astro::time::civil::j2000_seconds.
@spec epoch_to_split_jd(NaiveDateTime.t() | tuple()) :: {float(), float()}
Convert an epoch to the split Julian date {jd_whole, fraction}.
The calendar arithmetic lives in sidereon-core
(sidereon_core::astro::time::civil::split_julian_date); this module only
marshals the epoch into civil (year, month, day, hour, minute, second)
fields.
GPS minus UTC, in seconds, in effect at a UTC calendar date.
This is the IS-GPS-200 quantity broadcast in the navigation message (the leap
seconds a GPS receiver applies): 18 s from 2017. It is leap_seconds/3 minus
the constant 19 s TAI - GPST, so it is the value to use whenever you mean
"GPS - UTC", not leap_seconds/3 (which is TAI - UTC).
@spec leap_second_table_info() :: leap_second_table()
Provenance and coverage of the embedded leap-second table.
TAI minus UTC, in seconds, in effect at a UTC calendar date.
Delegates to sidereon_core::astro::time::scales::julian_day_number and
sidereon_core::astro::time::scales::find_leap_seconds.
TAI minus UTC for a list of UTC calendar dates.
Each date is {year, month, day} and each result delegates to the same core
functions as leap_seconds/3.
@spec second_of_day(NaiveDateTime.t() | tuple()) :: float()
Seconds-of-day in [0, 86400), formed from the epoch's clock fields.
Used by the Klobuchar diurnal term, which takes the GPS second-of-day
directly. The arithmetic delegates to
sidereon_core::astro::time::civil::second_of_day.
TAI minus UTC, in seconds, in effect at a UTC calendar date.
The unambiguously named alias of leap_seconds/3 (the IERS Bulletin C
quantity, 37 s from 2017); it returns the identical value. Use
gps_utc_offset_s/3 for the GNSS "GPS - UTC" offset instead.
@spec time_scales() :: [atom()]
The supported time-scale atoms.
@spec timescale_offset(time_scale(), time_scale()) :: {:ok, float()} | {:error, term()}
Fixed inter-system time offset to - from, in seconds.
Returns the value that, added to a reading in the from scale, yields the
to-scale reading of the same instant. Defined only for the atomic scales
(TAI/TT/GPST/GST/QZSST/BDT) whose mutual offset is a constant.
Returns {:error, {:epoch_required, scale}} for the UTC-based scales (UTC and
GLONASST) whose offset carries the leap-second count (use
timescale_offset_at/3 with an epoch), and {:error, {:unsupported, "TDB"}}
for TDB (its offset from TT is an epoch-dependent periodic term).
iex> Sidereon.GNSS.Time.timescale_offset(:gpst, :tai)
{:ok, 19.0}
iex> Sidereon.GNSS.Time.timescale_offset(:gpst, :utc)
{:error, {:epoch_required, "UTC"}}
@spec timescale_offset_at(time_scale(), time_scale(), number()) :: {:ok, float()} | {:error, term()}
Leap-aware inter-system time offset to - from, in seconds, at utc_jd.
utc_jd is the UTC Julian date of the instant; it only affects the result
when from or to is UTC-based (UTC/GLONASST), resolving the leap-second
count. For purely atomic pairs it is ignored and the result matches
timescale_offset/2.
iex> {:ok, off} = Sidereon.GNSS.Time.timescale_offset_at(:glonasst, :utc, 2_451_545.0)
iex> Float.round(off, 1)
-10800.0
@spec ut1_coverage_info() :: ut1_coverage()
Provenance and coverage of the embedded UT1/EOP table.
@spec utc_instant_split(NaiveDateTime.t() | tuple()) :: {:ok, {float(), float()}} | {:error, term()}
Validated UTC instant for an epoch, as the split Julian date {jd_whole, fraction}.
Delegates to sidereon_core::astro::time::model::Instant::from_utc_civil, the
entry the ionosphere/troposphere delay dispatchers build their epoch argument
from. Unlike epoch_to_split_jd/1, this runs the core's JulianDateSplit
guard, so an out-of-day clock field is rejected as {:error, :invalid_instant}
rather than producing an out-of-range fraction.
iex> {:ok, {jd_whole, _fraction}} =
...> Sidereon.GNSS.Time.utc_instant_split({{2020, 6, 25}, {12, 0, 0}})
iex> jd_whole
2_459_025.5