Source code for pytcl.astronomical.time_systems

"""
Time system conversions for astronomical and tracking applications.

This module provides conversions between various time systems:
- UTC (Coordinated Universal Time)
- TAI (International Atomic Time)
- TT (Terrestrial Time)
- GPS (GPS Time)
- Julian Date (JD) and Modified Julian Date (MJD)
- Unix time
- Sidereal time (GMST, GAST)
"""

from typing import List, Tuple

import numpy as np

# Constants
JD_UNIX_EPOCH = 2440587.5  # Julian date of Unix epoch (1970-01-01 00:00:00 UTC)
JD_GPS_EPOCH = 2444244.5  # Julian date of GPS epoch (1980-01-06 00:00:00 UTC)
JD_J2000 = 2451545.0  # Julian date of J2000.0 epoch (2000-01-01 12:00:00 TT)
MJD_OFFSET = 2400000.5  # JD - MJD offset

# TAI-UTC offset at GPS epoch (1980-01-06)
TAI_UTC_AT_GPS_EPOCH = 19  # seconds

# TT-TAI offset (constant)
TT_TAI_OFFSET = 32.184  # seconds


[docs] class LeapSecondTable: """ Table of leap seconds for UTC-TAI conversion. The leap second table contains the dates when leap seconds were added and the cumulative TAI-UTC offset. Attributes ---------- entries : list of tuple List of (year, month, day, tai_utc_offset) tuples. Notes ----- This table must be updated when new leap seconds are announced. Last update: 2017-01-01 (37 seconds). As of 2024, no new leap seconds have been added since 2017. """
[docs] def __init__(self) -> None: # Leap second table: (year, month, day, TAI-UTC offset in seconds) # From IERS Bulletin C self.entries: List[Tuple[int, int, int, int]] = [ (1972, 1, 1, 10), (1972, 7, 1, 11), (1973, 1, 1, 12), (1974, 1, 1, 13), (1975, 1, 1, 14), (1976, 1, 1, 15), (1977, 1, 1, 16), (1978, 1, 1, 17), (1979, 1, 1, 18), (1980, 1, 1, 19), (1981, 7, 1, 20), (1982, 7, 1, 21), (1983, 7, 1, 22), (1985, 7, 1, 23), (1988, 1, 1, 24), (1990, 1, 1, 25), (1991, 1, 1, 26), (1992, 7, 1, 27), (1993, 7, 1, 28), (1994, 7, 1, 29), (1996, 1, 1, 30), (1997, 7, 1, 31), (1999, 1, 1, 32), (2006, 1, 1, 33), (2009, 1, 1, 34), (2012, 7, 1, 35), (2015, 7, 1, 36), (2017, 1, 1, 37), ]
[docs] def get_offset(self, year: int, month: int, day: int) -> int: """ Get TAI-UTC offset for a given date. Parameters ---------- year : int Year. month : int Month (1-12). day : int Day of month. Returns ------- int TAI-UTC offset in seconds. """ offset = 0 for y, m, d, o in self.entries: if (year, month, day) >= (y, m, d): offset = o else: break return offset
# Global leap second table instance _LEAP_SECOND_TABLE = LeapSecondTable()
[docs] def get_leap_seconds(year: int, month: int, day: int) -> int: """ Get the number of leap seconds (TAI-UTC offset) for a given date. Parameters ---------- year : int Year. month : int Month (1-12). day : int Day of month. Returns ------- int TAI-UTC offset in seconds. Examples -------- >>> get_leap_seconds(2020, 1, 1) 37 >>> get_leap_seconds(1980, 1, 6) 19 """ return _LEAP_SECOND_TABLE.get_offset(year, month, day)
[docs] def cal_to_jd( year: int, month: int, day: int, hour: int = 0, minute: int = 0, second: float = 0.0, ) -> float: """ Convert calendar date to Julian Date. Parameters ---------- year : int Year (negative for BCE). month : int Month (1-12). day : int Day of month (1-31). hour : int, optional Hour (0-23). minute : int, optional Minute (0-59). second : float, optional Second (0-59.999...). Returns ------- float Julian Date. Examples -------- >>> cal_to_jd(2000, 1, 1, 12, 0, 0) # J2000.0 2451545.0 >>> cal_to_jd(1970, 1, 1, 0, 0, 0) # Unix epoch 2440587.5 Notes ----- Uses the algorithm from Meeus, "Astronomical Algorithms", 2nd ed. Valid for dates after October 15, 1582 (Gregorian calendar). """ if month <= 2: year -= 1 month += 12 A = int(year / 100) B = 2 - A + int(A / 4) jd = int(365.25 * (year + 4716)) + int(30.6001 * (month + 1)) + day + B - 1524.5 # Add time of day jd += (hour + minute / 60.0 + second / 3600.0) / 24.0 return jd
[docs] def jd_to_cal(jd: float) -> Tuple[int, int, int, int, int, float]: """ Convert Julian Date to calendar date. Parameters ---------- jd : float Julian Date. Returns ------- tuple (year, month, day, hour, minute, second). Examples -------- >>> jd_to_cal(2451545.0) # J2000.0 (2000, 1, 1, 12, 0, 0.0) """ jd = jd + 0.5 Z = int(jd) F = jd - Z if Z < 2299161: A = Z else: alpha = int((Z - 1867216.25) / 36524.25) A = Z + 1 + alpha - int(alpha / 4) B = A + 1524 C = int((B - 122.1) / 365.25) D = int(365.25 * C) E = int((B - D) / 30.6001) day = B - D - int(30.6001 * E) if E < 14: month = E - 1 else: month = E - 13 if month > 2: year = C - 4716 else: year = C - 4715 # Convert fractional day to time hours_frac = F * 24.0 hour = int(hours_frac) minutes_frac = (hours_frac - hour) * 60.0 minute = int(minutes_frac) second = (minutes_frac - minute) * 60.0 return year, month, day, hour, minute, second
[docs] def mjd_to_jd(mjd: float) -> float: """ Convert Modified Julian Date to Julian Date. Parameters ---------- mjd : float Modified Julian Date. Returns ------- float Julian Date. Examples -------- >>> mjd = 44239.0 # 1980-01-01 >>> jd = mjd_to_jd(mjd) >>> jd 2444239.5 Notes ----- MJD = JD - 2400000.5 """ return mjd + MJD_OFFSET
[docs] def jd_to_mjd(jd: float) -> float: """ Convert Julian Date to Modified Julian Date. Parameters ---------- jd : float Julian Date. Returns ------- float Modified Julian Date. Examples -------- >>> jd = 2444239.5 # 1980-01-01 >>> mjd = jd_to_mjd(jd) >>> mjd 44239.0 """ return jd - MJD_OFFSET
[docs] def unix_to_jd(unix_time: float) -> float: """ Convert Unix timestamp to Julian Date. Parameters ---------- unix_time : float Unix timestamp (seconds since 1970-01-01 00:00:00 UTC). Returns ------- float Julian Date. Examples -------- >>> unix_to_jd(0.0) # Unix epoch 2440587.5 """ return JD_UNIX_EPOCH + unix_time / 86400.0
[docs] def jd_to_unix(jd: float) -> float: """ Convert Julian Date to Unix timestamp. Parameters ---------- jd : float Julian Date. Returns ------- float Unix timestamp. Examples -------- >>> jd = 2440587.5 # 1970-01-01 00:00:00 UTC >>> unix_to_jd = jd_to_unix(jd) >>> unix_to_jd 0.0 """ return (jd - JD_UNIX_EPOCH) * 86400.0
[docs] def utc_to_tai( year: int, month: int, day: int, hour: int = 0, minute: int = 0, second: float = 0.0, ) -> float: """ Convert UTC to TAI (Julian Date). Parameters ---------- year, month, day : int Calendar date. hour, minute : int, optional Time of day. second : float, optional Seconds (including fractional). Returns ------- float TAI as Julian Date. Notes ----- TAI = UTC + leap_seconds """ leap_seconds = get_leap_seconds(year, month, day) jd_utc = cal_to_jd(year, month, day, hour, minute, second) return jd_utc + leap_seconds / 86400.0
[docs] def tai_to_utc(jd_tai: float) -> Tuple[float, int]: """ Convert TAI (Julian Date) to UTC. Parameters ---------- jd_tai : float TAI as Julian Date. Returns ------- jd_utc : float UTC as Julian Date. leap_seconds : int Number of leap seconds applied. Notes ----- This is an approximate conversion that may have small errors near leap second boundaries. """ # First approximation jd_utc_approx = jd_tai year, month, day, _, _, _ = jd_to_cal(jd_utc_approx) leap_seconds = get_leap_seconds(year, month, day) jd_utc = jd_tai - leap_seconds / 86400.0 return jd_utc, leap_seconds
[docs] def tai_to_tt(jd_tai: float) -> float: """ Convert TAI (Julian Date) to TT (Terrestrial Time). Parameters ---------- jd_tai : float TAI as Julian Date. Returns ------- float TT as Julian Date. Notes ----- TT = TAI + 32.184 seconds (constant offset) """ return jd_tai + TT_TAI_OFFSET / 86400.0
[docs] def tt_to_tai(jd_tt: float) -> float: """ Convert TT (Terrestrial Time) to TAI (Julian Date). Parameters ---------- jd_tt : float TT as Julian Date. Returns ------- float TAI as Julian Date. """ return jd_tt - TT_TAI_OFFSET / 86400.0
[docs] def utc_to_tt( year: int, month: int, day: int, hour: int = 0, minute: int = 0, second: float = 0.0, ) -> float: """ Convert UTC to TT (Terrestrial Time). Parameters ---------- year, month, day : int Calendar date. hour, minute : int, optional Time of day. second : float, optional Seconds. Returns ------- float TT as Julian Date. Notes ----- TT = UTC + leap_seconds + 32.184 """ jd_tai = utc_to_tai(year, month, day, hour, minute, second) return tai_to_tt(jd_tai)
[docs] def tt_to_utc(jd_tt: float) -> Tuple[float, int]: """ Convert TT to UTC. Parameters ---------- jd_tt : float TT as Julian Date. Returns ------- jd_utc : float UTC as Julian Date. leap_seconds : int Number of leap seconds applied. """ jd_tai = tt_to_tai(jd_tt) return tai_to_utc(jd_tai)
[docs] def tai_to_gps(jd_tai: float) -> float: """ Convert TAI to GPS time. Parameters ---------- jd_tai : float TAI as Julian Date. Returns ------- float GPS time as Julian Date. Notes ----- GPS time = TAI - 19 seconds (offset at GPS epoch) GPS time does not include leap seconds after 1980. """ return jd_tai - TAI_UTC_AT_GPS_EPOCH / 86400.0
[docs] def gps_to_tai(jd_gps: float) -> float: """ Convert GPS time to TAI. Parameters ---------- jd_gps : float GPS time as Julian Date. Returns ------- float TAI as Julian Date. """ return jd_gps + TAI_UTC_AT_GPS_EPOCH / 86400.0
[docs] def utc_to_gps( year: int, month: int, day: int, hour: int = 0, minute: int = 0, second: float = 0.0, ) -> float: """ Convert UTC to GPS time. Parameters ---------- year, month, day : int Calendar date. hour, minute : int, optional Time of day. second : float, optional Seconds. Returns ------- float GPS time as Julian Date. """ jd_tai = utc_to_tai(year, month, day, hour, minute, second) return tai_to_gps(jd_tai)
[docs] def gps_to_utc(jd_gps: float) -> Tuple[float, int]: """ Convert GPS time to UTC. Parameters ---------- jd_gps : float GPS time as Julian Date. Returns ------- jd_utc : float UTC as Julian Date. leap_seconds : int Number of leap seconds applied. """ jd_tai = gps_to_tai(jd_gps) return tai_to_utc(jd_tai)
[docs] def gps_week_seconds(jd_gps: float) -> Tuple[int, float]: """ Convert GPS Julian Date to GPS week and seconds of week. Parameters ---------- jd_gps : float GPS time as Julian Date. Returns ------- week : int GPS week number. seconds : float Seconds into the week. Examples -------- >>> gps_week_seconds(JD_GPS_EPOCH) (0, 0.0) """ days_since_epoch = jd_gps - JD_GPS_EPOCH week = int(days_since_epoch / 7) day_of_week = days_since_epoch - week * 7 seconds = day_of_week * 86400.0 return week, seconds
[docs] def gps_week_to_utc(week: int, seconds: float) -> Tuple[float, int]: """ Convert GPS week and seconds to UTC. Parameters ---------- week : int GPS week number. seconds : float Seconds into the week. Returns ------- jd_utc : float UTC as Julian Date. leap_seconds : int Number of leap seconds applied. """ jd_gps = JD_GPS_EPOCH + week * 7 + seconds / 86400.0 return gps_to_utc(jd_gps)
[docs] def gmst(jd_ut1: float) -> float: """ Compute Greenwich Mean Sidereal Time. Parameters ---------- jd_ut1 : float UT1 as Julian Date. Returns ------- float GMST in radians. Notes ----- Uses the IAU 1982 expression for GMST. References ---------- .. [1] Explanatory Supplement to the Astronomical Almanac, 3rd ed. """ # Julian centuries from J2000.0 T = (jd_ut1 - JD_J2000) / 36525.0 # GMST in seconds at 0h UT1 gmst_sec = 24110.54841 + 8640184.812866 * T + 0.093104 * T**2 - 6.2e-6 * T**3 # Add rotation for time of day jd_frac = jd_ut1 - int(jd_ut1) - 0.5 if jd_frac < 0: jd_frac += 1.0 gmst_sec += jd_frac * 86400.0 * 1.00273790935 # Normalize to [0, 86400) gmst_sec = gmst_sec % 86400.0 # Convert to radians return gmst_sec * 2 * np.pi / 86400.0
[docs] def gast(jd_ut1: float, dpsi: float = 0.0, eps: float = 0.0) -> float: """ Compute Greenwich Apparent Sidereal Time. Parameters ---------- jd_ut1 : float UT1 as Julian Date. dpsi : float, optional Nutation in longitude (radians). Default 0. eps : float, optional Mean obliquity of ecliptic (radians). Default uses approximate value. Returns ------- float GAST in radians. Notes ----- GAST = GMST + equation of equinoxes The equation of equinoxes = dpsi * cos(eps) For high precision applications, nutation parameters should be computed from the IAU 2006/2000A precession-nutation model. """ gmst_val = gmst(jd_ut1) if eps == 0.0: # Approximate mean obliquity T = (jd_ut1 - JD_J2000) / 36525.0 eps = np.radians(23.439291 - 0.0130042 * T) # Equation of equinoxes eq_eq = dpsi * np.cos(eps) return gmst_val + eq_eq
__all__ = [ # Julian dates "cal_to_jd", "jd_to_cal", "mjd_to_jd", "jd_to_mjd", # Time scales "utc_to_tai", "tai_to_utc", "tai_to_tt", "tt_to_tai", "utc_to_tt", "tt_to_utc", "tai_to_gps", "gps_to_tai", "utc_to_gps", "gps_to_utc", # Unix time "unix_to_jd", "jd_to_unix", # GPS week "gps_week_seconds", "gps_week_to_utc", # Sidereal time "gmst", "gast", # Leap seconds "get_leap_seconds", "LeapSecondTable", # Constants "JD_UNIX_EPOCH", "JD_GPS_EPOCH", "JD_J2000", "MJD_OFFSET", "TT_TAI_OFFSET", ]