module

Kozai::Timescale

Julian dates, calendar conversions and Greenwich sidereal time.

Everything here is UTC. Not "UTC by default" — there is no other option. Local time exists in this project only in the browser, where the user's machine knows its own offset. Two reasons, and both matter:

  • Orbital mechanics has no use for civil time. Every published algorithm, every element set epoch and every reference implementation works in universal time, so converting anywhere else is pure risk.
  • The standard library reads its timezone database from the operating system, and a scratch container has no such database. A daemon that needed one could not ship the way this one does.

NOTE: UT1 and UTC are treated as interchangeable throughout. They differ by at most 0.9 seconds, which displaces a satellite's ground track by a few hundred metres. That is far below the error already present in an element set more than a few hours old, and correcting it would require polar motion and earth orientation parameters — a network dependency and a file to keep current, for an offline station that gains nothing from either.

Constants

DAYS_PER_CENTURY = 36525.0

Days in a Julian century.

EARTH_ROTATION_RAD_PER_MIN = 4.37526908801129966e-3

Earth's rotation rate in radians per minute.

NOTE: this is the value used by SGP4's deep-space routines for the rotation of the resonance geopotential terms, from Spacetrack Report No. 3.

J2000 = 2451545.0

Julian date of the J2000.0 epoch, 2000 January 1 at 12:00 UTC.

SECONDS_PER_DAY = 86400.0

Seconds in a day.

SGP4_EPOCH_JD = 2433281.5

Julian date of 1949 December 31 at 00:00 UTC.

SGP4 measures its epoch in days from this instant, an inheritance from the original AFSPC implementation.

TWO_PI = Math::PI * 2.0

Two pi, used to reduce angles.

UNIX_EPOCH_JD = 2440587.5

Julian date of the Unix epoch, 1970 January 1 at 00:00 UTC.

Class methods

calendar_from_day_of_year(year : Int32, day_of_year : Float64) : Tuple(Int32, Int32, Int32, Int32, Float64)

Splits a day of year with fraction into calendar month, day and time of day.

day_of_year uses the element set convention where 1.0 is January 1st at 00:00 UTC. Returns {month, day, hour, minute, second} with the seconds carrying the fractional part.

Source
calendar_from_julian_date(jd : Float64) : Tuple(Int32, Int32, Int32, Int32, Int32, Float64)

Converts a Julian date back to a UTC calendar date and time.

Returns {year, month, day, hour, minute, second}. This is the exact inverse of .julian_date and keeps the fractional seconds that a Time would round away.

Source
day_of_year(year : Int32, month : Int32, day : Int32, hour : Int32 = 0, minute : Int32 = 0, second : Float64 = 0.0) : Float64

Combines a calendar date and time into a day of year with fraction.

The inverse of .calendar_from_day_of_year, using the same convention where 1.0 is January 1st at 00:00 UTC. This is how an OMM's ISO-8601 epoch becomes the day-of-year form that element sets and the propagator work in.

Source
days_in_month(year : Int32, month : Int32) : Int32

Number of days in month of year.

Source
gmst(jd_ut1 : Float64) : Float64

Greenwich Mean Sidereal Time in radians, in the range 0...2π.

NOTE: the polynomial is the IAU 1982 expression for GMST as a function of UT1, reproduced in Vallado algorithm 15. Its leading constant 67310.54841 is in seconds of time; the division by 240 converts seconds of time to degrees before the conversion to radians.

Source
gmst(time : Time) : Float64

Greenwich Mean Sidereal Time in radians for a Time.

Source
gmst_afspc(days_since_1949 : Float64) : Float64

Greenwich Mean Sidereal Time as computed by the original AFSPC software.

days_since_1949 is the element set epoch in days from 1949 December 31 at 00:00 UTC.

NOTE: this exists because SGP4's opsmode = 'a' reproduces the sidereal time the operational AFSPC code produced, not the IAU expression in .gmst. The two disagree by a few hundred microradians, which is negligible for pointing an antenna but not for reproducing the published verification vectors — and matching those is the entire point of this implementation. The constants are those of the original formulation: the sidereal rate relative to solar time, Greenwich sidereal time at the 1970 reference, and a small quadratic drift term.

See SGP4::OpsMode for which mode applies when.

Source
julian_date(year : Int32, month : Int32, day : Int32, hour : Int32 = 0, minute : Int32 = 0, second : Float64 = 0.0) : Float64

Computes the Julian date from a UTC calendar date and time.

Valid from 1900 March through 2100 February, which covers every element set this software will ever be handed.

NOTE: the formulation is the standard one given in the astrodynamics literature (Vallado, Fundamentals of Astrodynamics and Applications, algorithm 14). The time of day is folded in as a single fraction so that the result carries the full precision of the seconds argument.

Source
julian_date(time : Time) : Float64

Computes the Julian date of a Time.

The instant is interpreted in UTC regardless of the location attached to time.

Source
julian_date_from_epoch(epoch_year : Int32, epoch_day : Float64) : Float64

Computes the Julian date of an element set epoch.

epoch_day is a day of year with fraction, where 1.0 is January 1st at 00:00 UTC — the form stored in a TLE.

NOTE: this goes through a calendar decomposition rather than adding the day count to January 1st directly. The two differ in the last bits, and this is the path the reference implementations take when producing the numbers in spec/fixtures/sgp4-ver.expected.

Source
leap_year?(year : Int32) : Bool

Whether year is a leap year in the Gregorian calendar.

Source
normalize_radians(angle : Float64) : Float64

Reduces an angle in radians to the range 0...2π.

Source
time_from_julian_date(jd : Float64) : Time

Converts a Julian date to a UTC Time.

NOTE: a Julian date near the present is around 2.46e6, so one unit in the last place of a Float64 is roughly 4.7e-10 days — about 40 microseconds. That is the floor on the resolution of any single-Float64 Julian date, here and everywhere else in this library, and no amount of care in the conversion improves on it.

For propagation this costs at most a metre of along-track position, which is three orders of magnitude below the error already present in the element set, so it is left alone. A split representation would remove it, at the price of diverging from the formulation the reference vectors were produced with. Do not tighten a test past this bound expecting it to pass.

Source
to_degrees(radians : Float64) : Float64

Converts radians to degrees.

Source
to_radians(degrees : Float64) : Float64

Converts degrees to radians.

Source