Skip to content

Time Systems

This example works with the epoch classes (UTC, TAI, TT, TDB, GPS), converts between time systems, computes Julian dates and Greenwich Mean Sidereal Time, and demonstrates epoch arithmetic. You need these conversions whenever you mix data sources that use different time scales, such as GPS receivers, ephemerides, and TLEs.

ts
import { calcGmst, EpochUTC, jday, Seconds } from 'ootk';

Run it

bash
npm run build
npx tsx ./examples/time-systems.ts

Creating epochs

EpochUTC is the primary time class; construct it with fromDateTime, fromDateTimeString, or now. The other time systems (EpochTAI, EpochTT, EpochTDB, EpochGPS) are derived from a UTC epoch through toTAI(), toTT(), toTDB(), and toGPS(); they have no fromDateTime constructors of their own.

ts
console.log('=== Example 1: Different Time Systems ===\n');

const date = new Date('2024-01-28T12:00:00.000Z');

// EpochUTC is the primary time class. All other time systems are derived
// from it via the to*() conversion methods.
const utcEpoch = EpochUTC.fromDateTime(date);
const taiEpoch = utcEpoch.toTAI();
const ttEpoch = utcEpoch.toTT();
const tdbEpoch = utcEpoch.toTDB();
const gpsEpoch = utcEpoch.toGPS();

console.log(`UTC (Coordinated Universal Time): ${utcEpoch.toDateTime().toISOString()}`);
console.log(`TAI (International Atomic Time):  ${taiEpoch.toDateTime().toISOString()}`);
console.log(`TT  (Terrestrial Time):           ${ttEpoch.toDateTime().toISOString()}`);
console.log(`TDB (Barycentric Dynamical Time): ${tdbEpoch.toDateTime().toISOString()}`);
console.log(`GPS (week:seconds of week):       ${gpsEpoch.toString()}`);

GPS time

EpochGPS is not posix-based: it stores a week count since the 1980-01-06 reference epoch plus seconds into the week. The week10Bit and week13Bit getters model receiver week-number rollover, and toUTC() converts back, accounting for the 19-second GPS-TAI offset and accumulated leap seconds.

ts
console.log('\n=== Example 2: GPS Time ===\n');

// GPS time is expressed as weeks since 1980-01-06 plus seconds into the week
console.log(`GPS reference epoch: ${EpochUTC.fromDateTimeString('1980-01-06T00:00:00.000Z').toDateTime().toISOString()}`);
console.log(`GPS week:            ${gpsEpoch.week}`);
console.log(`GPS week (10-bit):   ${gpsEpoch.week10Bit}`);
console.log(`GPS week (13-bit):   ${gpsEpoch.week13Bit}`);
console.log(`Seconds of week:     ${gpsEpoch.seconds.toFixed(3)}`);

// GPS epochs can be converted back to UTC
const gpsRoundTrip = gpsEpoch.toUTC();

console.log(`\nRound trip GPS -> UTC: ${gpsRoundTrip.toDateTime().toISOString()}`);

Julian dates

The standalone jday function computes a Julian date from calendar components, while epoch instances expose toJulianDate(), toMjd(), and toJulianCenturies() (centuries since J2000, the argument most precession and nutation series expect).

ts
console.log('\n=== Example 3: Julian Dates ===\n');

// The jday function computes a Julian date from calendar components
const jd = jday(
  date.getUTCFullYear(),
  date.getUTCMonth() + 1,
  date.getUTCDate(),
  date.getUTCHours(),
  date.getUTCMinutes(),
  date.getUTCSeconds(),
);

console.log(`Date: ${date.toISOString()}`);
console.log(`Julian Date: ${jd.toFixed(6)}`);
console.log(`Modified Julian Date: ${(jd - 2400000.5).toFixed(6)}`);

// Epoch classes provide the same values directly
console.log(`\nFrom EpochUTC:`);
console.log(`  Julian Date: ${utcEpoch.toJulianDate().toFixed(6)}`);
console.log(`  Modified Julian Date: ${utcEpoch.toMjd().toFixed(6)}`);
console.log(`  Julian Centuries since J2000: ${utcEpoch.toJulianCenturies().toFixed(8)}`);

// J2000 epoch (January 1, 2000, 12:00:00)
const j2000Epoch = EpochUTC.fromDateTimeString('2000-01-01T12:00:00.000Z');

console.log(`\nJ2000 Epoch:`);
console.log(`  Date: ${j2000Epoch.toDateTime().toISOString()}`);
console.log(`  Julian Date: ${j2000Epoch.toJulianDate().toFixed(6)}`);

Greenwich Mean Sidereal Time

calcGmst returns GMST in radians for a JavaScript Date, and EpochUTC.gmstAngle() produces the same angle from an epoch. GMST is the rotation angle between the ECI and ECEF frames, so it appears in every inertial-to-Earth-fixed transform.

ts
console.log('\n=== Example 4: Greenwich Mean Sidereal Time ===\n');

const gmstResult = calcGmst(date);

console.log(`Date: ${date.toISOString()}`);
console.log(`GMST: ${gmstResult.gmst.toFixed(6)} radians`);
console.log(`      ${(gmstResult.gmst * (180 / Math.PI)).toFixed(6)} deg`);
console.log(`      ${((gmstResult.gmst * (180 / Math.PI)) / 15).toFixed(6)} hours`);

// Convert to hour:minute:second format
const gmstHours = (gmstResult.gmst * (180 / Math.PI)) / 15;
const hours = Math.floor(gmstHours);
const minutes = Math.floor((gmstHours - hours) * 60);
const seconds = ((gmstHours - hours) * 60 - minutes) * 60;

console.log(`      ${hours}h ${minutes}m ${seconds.toFixed(2)}s`);

// EpochUTC can also compute GMST directly
console.log(`\nFrom EpochUTC: ${utcEpoch.gmstAngle().toFixed(6)} radians`);

Time system offsets

Every Epoch stores posix seconds in its own time scale, so subtracting posix values between two derived epochs exposes the offsets: TAI leads UTC by the accumulated leap seconds (37 at this date), TT leads TAI by a constant 32.184 s, and TDB differs from TT by a sub-2 ms periodic relativistic term.

ts
console.log('\n=== Example 5: Time System Offsets ===\n');

// Each epoch stores posix seconds in its own time scale, so subtracting
// posix values reveals the offset between systems

// TAI-UTC offset (leap seconds)
const taiUtcDiff = taiEpoch.posix - utcEpoch.posix;

console.log(`TAI - UTC = ${taiUtcDiff} seconds (leap seconds)`);

// TT-TAI offset (constant 32.184 seconds)
const ttTaiDiff = ttEpoch.posix - taiEpoch.posix;

console.log(`TT - TAI  = ${ttTaiDiff.toFixed(3)} seconds (constant)`);

// TDB-TT offset (periodic relativistic correction, under 2 ms)
const tdbTtDiff = (tdbEpoch.posix - ttEpoch.posix) * 1000;

console.log(`TDB - TT  = ${tdbTtDiff.toFixed(3)} milliseconds (periodic)`);

Epoch arithmetic

roll(seconds) returns a new epoch shifted by a Seconds amount (it does not mutate), and difference(other) returns the separation in seconds.

ts
console.log('\n=== Example 6: Epoch Arithmetic ===\n');

const startEpoch = EpochUTC.fromDateTimeString('2024-01-01T00:00:00.000Z');

console.log(`Start: ${startEpoch.toDateTime().toISOString()}`);

// roll shifts an epoch by a number of seconds
const secondsPerDay = 86400 as Seconds;
const oneDayLater = startEpoch.roll(secondsPerDay);

console.log(`+1 day: ${oneDayLater.toDateTime().toISOString()}`);

// Add 7 days
const oneWeekLater = startEpoch.roll((7 * 86400) as Seconds);

console.log(`+7 days: ${oneWeekLater.toDateTime().toISOString()}`);

// difference returns the seconds between two epochs
console.log(`Difference: ${oneWeekLater.difference(startEpoch)} seconds`);

Reference epochs and precision

The same APIs applied to well-known reference epochs (GPS start, J2000.0, Unix epoch), plus a sub-second example showing that posix preserves millisecond input precision through conversions.

ts
console.log('\n=== Example 7: Common Date/Time Scenarios ===\n');

const scenarios = [
  { name: 'GPS Epoch Start', date: new Date('1980-01-06T00:00:00.000Z') },
  { name: 'J2000.0', date: new Date('2000-01-01T12:00:00.000Z') },
  { name: 'Unix Epoch', date: new Date('1970-01-01T00:00:00.000Z') },
  { name: 'Current Example', date: new Date('2024-01-28T12:00:00.000Z') },
];

scenarios.forEach((scenario) => {
  const epoch = EpochUTC.fromDateTime(scenario.date);
  const gmstValue = calcGmst(scenario.date);

  console.log(`${scenario.name}:`);
  console.log(`  Date: ${scenario.date.toISOString()}`);
  console.log(`  JD: ${epoch.toJulianDate().toFixed(6)}`);
  console.log(`  GMST: ${(gmstValue.gmst * (180 / Math.PI) / 15).toFixed(4)} hours`);
  console.log('');
});

console.log('=== Example 8: High-Precision Time Comparison ===\n');

const preciseDate = new Date('2024-01-28T12:34:56.789Z');
const utcPrecise = EpochUTC.fromDateTime(preciseDate);

console.log(`Input: ${preciseDate.toISOString()}`);
console.log(`UTC epoch posix: ${utcPrecise.posix.toFixed(9)} seconds since Unix epoch`);

// Show difference between time systems in milliseconds
const taiPrecise = utcPrecise.toTAI();
const ttPrecise = utcPrecise.toTT();

console.log('\nTime differences (from UTC):');
console.log(`  TAI: +${((taiPrecise.posix - utcPrecise.posix) * 1000).toFixed(0)} ms`);
console.log(`  TT:  +${((ttPrecise.posix - utcPrecise.posix) * 1000).toFixed(0)} ms`);

Output

txt
=== Example 1: Different Time Systems ===

UTC (Coordinated Universal Time): 2024-01-28T12:00:00.000Z
TAI (International Atomic Time):  2024-01-28T12:00:37.000Z
TT  (Terrestrial Time):           2024-01-28T12:01:09.184Z
TDB (Barycentric Dynamical Time): 2024-01-28T12:01:09.184Z
GPS (week:seconds of week):       2299:43218.000

=== Example 2: GPS Time ===

GPS reference epoch: 1980-01-06T00:00:00.000Z
GPS week:            2299
GPS week (10-bit):   251
GPS week (13-bit):   2299
Seconds of week:     43218.000

Round trip GPS -> UTC: 2024-01-28T12:00:00.000Z

=== Example 3: Julian Dates ===

Date: 2024-01-28T12:00:00.000Z
Julian Date: 2460338.000000
Modified Julian Date: 60337.500000

From EpochUTC:
  Julian Date: 2460338.000000
  Modified Julian Date: 60337.500000
  Julian Centuries since J2000: 0.24073922

J2000 Epoch:
  Date: 2000-01-01T12:00:00.000Z
  Julian Date: 2451545.000000

=== Example 4: Greenwich Mean Sidereal Time ===

Date: 2024-01-28T12:00:00.000Z
GMST: 5.362663 radians
      307.257933 deg
      20.483862 hours
      20h 29m 1.90s

From EpochUTC: 5.362663 radians

=== Example 5: Time System Offsets ===

TAI - UTC = 37 seconds (leap seconds)
TT - TAI  = 32.184 seconds (constant)
TDB - TT  = 0.682 milliseconds (periodic)

=== Example 6: Epoch Arithmetic ===

Start: 2024-01-01T00:00:00.000Z
+1 day: 2024-01-02T00:00:00.000Z
+7 days: 2024-01-08T00:00:00.000Z
Difference: 604800 seconds

=== Example 7: Common Date/Time Scenarios ===

GPS Epoch Start:
  Date: 1980-01-06T00:00:00.000Z
  JD: 2444244.500000
  GMST: 6.9828 hours

... (truncated)

=== Example 8: High-Precision Time Comparison ===

Input: 2024-01-28T12:34:56.789Z
UTC epoch posix: 1706445296.789000034 seconds since Unix epoch

Time differences (from UTC):
  TAI: +37000 ms
  TT:  +69184 ms

Released under the AGPL-3.0 License.