Source code for julian.leap_seconds

##########################################################################################
# julian/leap_seconds.py
##########################################################################################
"""
====================
Leap Seconds Support
====================
"""

import numpy as np
import pathlib
import os
import re
import sys
from filecache import FCPath

from julian.calendar import day_from_ymd, days_in_year, ymd_from_day
from julian._deltat  import FuncDeltaT, LeapDeltaT, MergedDeltaT, SplineDeltaT
from julian._utils   import _int, _number

##########################################################################################
# LEAPS_DELTA_T and SPICE_DELTA_T: baseline models for leap seconds and TAI-UTC.
# Each call to insert_leap_second modifies these globals.
# They are always initialized at startup or by calling _initialize_leap_seconds().
##########################################################################################

_LEAPS_DELTA_T = None
_SPICE_DELTA_T = None

# At load time, this file looks for an environment variable SPICE_LSK_FILEPATH. If found,
# this file is used to initialize the module. Otherwise, the local copy of the latest
# LSK is read.
_LATEST_LSK_NAME = 'naif0012.tls'       # possibly overridden by _default_lsk_path()

# Global variables needed for TAI-UTC conversions, from a SPICE leap seconds kernel
_DELTET_DELTA_T_A = 0.
_DELTET_K  = 0.
_DELTET_EB = 0.
_DELTET_M0 = 0.
_DELTET_M1 = 0.
_DELTET_DELTA_AT = []


def _default_lsk_path():
    """The default LSK path as a Path object."""

    global _LATEST_LSK_NAME

    try:
        lsk_path = os.environ['SPICE_LSK_FILEPATH']

    # If the environment variable is not defined, identify the latest local copy
    except KeyError:
        julian_root_dir = pathlib.Path(sys.modules['julian'].__file__).parent
        julian_docs_dir = julian_root_dir / 'assets'
        lsk_paths = list(julian_docs_dir.glob('naif00*.tls'))
        lsk_paths.sort()
        lsk_path = lsk_paths[-1]
        _LATEST_LSK_NAME = lsk_path.name

    return lsk_path


def _leaps_from_lsk(lsk_path):
    """The list of leap seconds from the specified SPICE leap seconds kernel file,
    represented by a string or Path.
    """

    global _DELTET_DELTA_T_A, _DELTET_K, _DELTET_EB, _DELTET_M0, _DELTET_M1
    global _DELTET_DELTA_AT

    _MONTHNO = {'JAN':1, 'FEB':2, 'MAR':3, 'APR':4, 'MAY':5, 'JUN':6, 'JUL':7, 'AUG':8,
                'SEP':9, 'OCT':10, 'NOV':11, 'DEC':12}

    # This procedure works correctly for naif0012.tls and all previous versions.
    # Unless something changes radically in the future, it should work for future
    # releases as well.

    def get_float(value):
        return float(value.lower().replace('d', 'e'))

    # Define a regex to match lines like:
    #   "DELTET/DELTA_T_A =   32.184"
    #   "DELTET/K         =    1.657D-3"
    #   "DELTET/EB        =    1.671D-2"
    #   "DELTET/M         = (  6.239996D0   1.99096871D-7 )"
    # DELTET/DELTA_AT     = ( 10,   @1972-JAN-1
    # The first capture is the name after the slash; the second is everything after "=".
    lsk_regex1 = re.compile(r' *DELTET/(\w+) *= *(.*)')

    # Define a regex to match lines like:
    #   "                   11,   @1972-JUL-1     "
    #   "                   12,   @1973-JAN-1     "
    #   "                   37,   @2017-JAN-1 )"
    # The match pattern captures (number of leap seconds, year, month, day)
    lsk_regex2 = re.compile(r' *(\d+), *@(\d\d\d\d)-([A-Z]{3})-(\d+) *\)? *')

    # Read the LSK
    deltet_dict = {}            # a dictionary name -> value from lsk_regex1
    deltet_delta_at = []        # a list of (leap seconds, year, month, day)
    lsk_path = FCPath(lsk_path)
    lsk_path.retrieve()
    with lsk_path.get_local_path().open(mode='r', encoding='latin-1') as f:
        for rec in f:           # pragma: no branch
            if rec.startswith('\\begindata'):
                break

        for rec in f:
            if match := lsk_regex1.match(rec):
                name = match.group(1)
                value = match.group(2).rstrip()
                deltet_dict[name] = value
            elif match := lsk_regex2.match(rec):    # groups are (secs, year, mon, day)
                value = match.groups(0)
                deltet_delta_at.append(value)

    # Extract the global values
    _DELTET_DELTA_T_A = get_float(deltet_dict['DELTA_T_A'])
    _DELTET_K         = get_float(deltet_dict['K'])
    _DELTET_EB        = get_float(deltet_dict['EB'])

    # Extract the two DELTET/M values
    parts = deltet_dict['M'].split()                # split by spaces; ignore parentheses
    _DELTET_M0 = get_float(parts[1])
    _DELTET_M1 = get_float(parts[2])

    # Put the first DELTET/DELTA_AT value back at the top of the list
    delta_at = deltet_dict['DELTA_AT'].strip()[1:]  # skip left parenthesis
    _DELTET_DELTA_AT = [lsk_regex2.match(delta_at).groups(0)] + deltet_delta_at

    # Convert the list of DELTET/DELTA_AT values
    leaps = []
    for (count, year, month, day) in _DELTET_DELTA_AT:
        if day != '1':
            raise ValueError('leap second day is not the first '    # pragma: no cover
                             f' of a month: {year}-{month:02d}-{day:02d}')

        leaps.append((int(year), _MONTHNO[month.upper()], int(count)))

    return leaps


def _initialize_leap_seconds(lsk_path=None):
    """Initialize the LEAPS and SPICE models for TAI-UT."""

    global _LEAPS_DELTA_T, _SPICE_DELTA_T

    lsk_path = lsk_path or _default_lsk_path()
    info = _leaps_from_lsk(lsk_path)

    _LEAPS_DELTA_T = LeapDeltaT(info)
    _SPICE_DELTA_T = LeapDeltaT(list(_LEAPS_DELTA_T.info), before=9)


# Initialize at startup
_initialize_leap_seconds()

##########################################################################################
# DELTA_T_1958_1972: UTC "rubber seconds" model, 1958-1972.
# It is initialized at startup or by calling _initialize_utc_1958_1972().
##########################################################################################

# Based on the International Earth Rotation Service (IERS)
# See https://www.ucolick.org/~sla/leapsecs/amsci.html
# Also https://hpiers.obspm.fr/eop-pc/index.php?index=TAI-UTC_tab

_DELTA_T_1958_1972 = None

# Table from https://hpiers.obspm.fr/eop-pc/index.php?index=TAI-UTC_tab
_TAI_MINUS_UTC_1958_1972_TEXT = """\
1961  Jan.  1 - 1961  Aug.  1     1.422 818 0s + (MJD - 37 300) x 0.001 296s
      Aug.  1 - 1962  Jan.  1     1.372 818 0s +        ""
1962  Jan.  1 - 1963  Nov.  1     1.845 858 0s + (MJD - 37 665) x 0.001 123 2s
1963  Nov.  1 - 1964  Jan.  1     1.945 858 0s +        ""
1964  Jan.  1 -       April 1     3.240 130 0s + (MJD - 38 761) x 0.001 296s
      April 1 -       Sept. 1     3.340 130 0s +        ""
      Sept. 1 - 1965  Jan.  1     3.440 130 0s +        ""
1965  Jan.  1 -       March 1     3.540 130 0s +        ""
      March 1 -       Jul.  1     3.640 130 0s +        ""
      Jul.  1 -       Sept. 1     3.740 130 0s +        ""
      Sept. 1 - 1966  Jan.  1     3.840 130 0s +        ""
1966  Jan.  1 - 1968  Feb.  1     4.313 170 0s + (MJD - 39 126) x 0.002 592s
1968  Feb.  1 - 1972  Jan.  1     4.213 170 0s +        ""
1972  Jan.  1 -       Jul.  1    10s
      Jul.  1 - 1973  Jan.  1    11s
...
"""

# (start_day, start_month, offset, dref, factor)
# Between each pair of dates, (TAI-UTC) = offset + (day - dref) * factor
_MJD_OF_JAN_1_2000 = 51544
_TAI_MINUS_UTC_1958_1972_INFO = [
    (1958,  1, 0.      , 0.       , 37300 - _MJD_OF_JAN_1_2000),
    (1961,  1, 1.422818, 0.001296 , 37300 - _MJD_OF_JAN_1_2000),
    (1961,  8, 1.372818, 0.001296 , 37300 - _MJD_OF_JAN_1_2000),
    (1962,  1, 1.845858, 0.0011232, 37665 - _MJD_OF_JAN_1_2000),
    (1963, 11, 1.945858, 0.0011232, 37665 - _MJD_OF_JAN_1_2000),
    (1964,  1, 3.240130, 0.001296 , 38761 - _MJD_OF_JAN_1_2000),
    (1964,  4, 3.340130, 0.001296 , 38761 - _MJD_OF_JAN_1_2000),
    (1964,  9, 3.440130, 0.001296 , 38761 - _MJD_OF_JAN_1_2000),
    (1965,  1, 3.540130, 0.001296 , 38761 - _MJD_OF_JAN_1_2000),
    (1965,  3, 3.640130, 0.001296 , 38761 - _MJD_OF_JAN_1_2000),
    (1965,  7, 3.740130, 0.001296 , 38761 - _MJD_OF_JAN_1_2000),
    (1965,  9, 3.840130, 0.001296 , 38761 - _MJD_OF_JAN_1_2000),
    (1966,  1, 4.313170, 0.002592 , 39126 - _MJD_OF_JAN_1_2000),
    (1968,  2, 4.213170, 0.002592 , 39126 - _MJD_OF_JAN_1_2000),
    (1972,  1, 10      , 0.       , 0                         ),
]


def _initialize_utc_1958_1972():
    """Initialize the TAI-UTC models 1958-1972."""

    global _DELTA_T_1958_1972

    _DELTA_T_1958_1972 = SplineDeltaT(_TAI_MINUS_UTC_1958_1972_INFO, last=1972)


# Initialize at startup
_initialize_utc_1958_1972()

##########################################################################################
# Functional model for -1999 to 3000.
# This is the model used for the Five Millennium Canon of Solar Eclipses: -1999 to +3000.
# See https://eclipse.gsfc.nasa.gov/SEpubs/5MCSE.html.
# The numerical details are here: https://eclipse.gsfc.nasa.gov/SEcat5/deltatpoly.html.
# DeltaT object is DELTA_T_NEG1999_3000.
# It is initialized at startup or by calling _initialize_ut1_neg1999_3000().
##########################################################################################

def _delta_t_neg0500_0500(y):
    u = y / 100.
    return (10583.6 + u * (-1014.41 + u * (33.78311 + u * (-5.952053 + u * (-0.1798452
            + u * (0.022174192 + u * 0.0090316521))))))

def _delta_t_0500_1600(y):
    u = (y - 1000.) / 100.
    return (1574.2 + u * (-556.01 + u * (71.23472 + u * (0.319781 + u * (-0.8503463
            + u * (-0.005050998 + u * 0.0083572073))))))

def _delta_t_1600_1700(y):
    t = y - 1600
    return 120 + t * (-0.9808 + t * (-0.01532 + t/7129.))

def _delta_t_1700_1800(y):
    t = y - 1700
    return 8.83 + t * (0.1603 + t * (-0.0059285 + t * (0.00013336 - t/1174000.)))

def _delta_t_1800_1860(y):
    t = y - 1800
    return (13.72 + t * (-0.332447 + t * (0.0068612 + t * (0.0041116 + t * (-0.00037436
            + t * (0.0000121272 + t * (-0.0000001699 + t * 0.000000000875)))))))

def _delta_t_1860_1900(y):
    t = y - 1860
    return (7.62 + t * (0.5737 + t * (-0.251754 + t * (0.01680668 + t * (-0.0004473624
            + t/233174.)))))

def _delta_t_1900_1920(y):
    t = y - 1900
    return -2.79 + t * (1.494119 + t * (-0.0598939 + t * (0.0061966 + t * -0.000197)))

def _delta_t_1920_1941(y):
    t = y - 1920
    return 21.20 + t * (0.84493 + t * (-0.076100 + t * 0.0020936))

def _delta_t_1941_1961(y):
    t = y - 1950
    return 29.07 + t * (0.407 + t * (-1./233. + t/2547.))

def _delta_t_1961_1986(y):
    t = y - 1975
    return 45.45 + t * (1.067 + t * (-1./260. - t/718.))

def _delta_t_1986_2005(y):
    t = y - 2000
    return (63.86 + t * (0.3345 + t * (-0.060374 + t * (0.0017275 + t * (0.000651814
            + t * 0.00002373599)))))

def _delta_t_2005_2050(y):
    t = y - 2000
    return 62.92 + t * (0.32217 + 0.005589 * t)

def _delta_t_2050_2150(y):
    return -20 + 32 * ((y - 1820) / 100.)**2 - 0.5628 * (2150 - y)

def _delta_t_long_term(y):
    u = (y - 1820.) / 100.
    return -20 + 32 * u**2

# Set it all up for quick indexing of nonnegative years 0-2149
_DELTA_T_INDEX = np.empty(2150, dtype='int')
_DELTA_T_INDEX[    : 500] =  0
_DELTA_T_INDEX[ 500:1600] =  1
_DELTA_T_INDEX[1600:1700] =  2
_DELTA_T_INDEX[1700:1800] =  3
_DELTA_T_INDEX[1800:1860] =  4
_DELTA_T_INDEX[1860:1900] =  5
_DELTA_T_INDEX[1900:1920] =  6
_DELTA_T_INDEX[1920:1941] =  7
_DELTA_T_INDEX[1941:1961] =  8
_DELTA_T_INDEX[1961:1986] =  9
_DELTA_T_INDEX[1986:2005] = 10
_DELTA_T_INDEX[2005:2050] = 11
_DELTA_T_INDEX[2050:    ] = 12

_DELTA_T_FUNCTIONS = np.empty(13, dtype='object')
_DELTA_T_FUNCTIONS[ 0] = _delta_t_neg0500_0500
_DELTA_T_FUNCTIONS[ 1] = _delta_t_0500_1600
_DELTA_T_FUNCTIONS[ 2] = _delta_t_1600_1700
_DELTA_T_FUNCTIONS[ 3] = _delta_t_1700_1800
_DELTA_T_FUNCTIONS[ 4] = _delta_t_1800_1860
_DELTA_T_FUNCTIONS[ 5] = _delta_t_1860_1900
_DELTA_T_FUNCTIONS[ 6] = _delta_t_1900_1920
_DELTA_T_FUNCTIONS[ 7] = _delta_t_1920_1941
_DELTA_T_FUNCTIONS[ 8] = _delta_t_1941_1961
_DELTA_T_FUNCTIONS[ 9] = _delta_t_1961_1986
_DELTA_T_FUNCTIONS[10] = _delta_t_1986_2005
_DELTA_T_FUNCTIONS[11] = _delta_t_2005_2050
_DELTA_T_FUNCTIONS[12] = _delta_t_2050_2150


def _delta_t_neg1999_3000(y, m, d):
    """Delta T model from Five Millennium Canon of Solar Eclipses: -1999 to +3000."""

    y_int = _int(y)
    day = day_from_ymd(y_int, m, d)
    y = y_int + (day - day_from_ymd(y_int, 1, 1)) / days_in_year(y_int)

    # Determine values of TT - UT based on the defined functions
    if np.isscalar(y):
        if y < 0:
            if y < -500:
                tt_minus_ut = _delta_t_long_term(y)
            else:
                tt_minus_ut = _delta_t_neg0500_0500(y)
        elif y < 2150:
            tt_minus_ut = _DELTA_T_FUNCTIONS[_DELTA_T_INDEX[y_int]](y)
        else:
            tt_minus_ut = _delta_t_long_term(y)

    else:
        tt_minus_ut = np.empty(y.shape)
        below_or_above = False

        mask_below_0000 = y < 0
        if np.any(mask_below_0000):
            below_or_above = True
            y_below_0000 = y[mask_below_0000]
            tt_minus_ut_below_0000 = _delta_t_neg0500_0500(y_below_0000)
            mask = y_below_0000 < -500
            tt_minus_ut_below_0000[mask] = _delta_t_long_term(y_below_0000[mask])
            tt_minus_ut[mask_below_0000] = tt_minus_ut_below_0000

        mask_above_2150 = y >= 2150
        if np.any(mask_above_2150):
            below_or_above = True
            tt_minus_ut[mask_above_2150] = _delta_t_long_term(y[mask_above_2150])

        # Create indices, an array with the same shape as y, containing the function index
        # applicable to each value of y. The index is -1 for years < -500 or >= 2150.
        if below_or_above:
            indices = -np.ones(y.shape, dtype='int')
            mask = np.logical_not(mask_below_0000 | mask_above_2150)
            indices[mask] = _DELTA_T_INDEX[y_int[mask]]
        else:
            indices = _DELTA_T_INDEX[y_int]

        # Fill in values for each unique function index
        for indx in set(indices.ravel()):
            if indx < 0:
                continue
            mask = (indx == indices)
            tt_minus_ut[mask] = _DELTA_T_FUNCTIONS[indx](y[mask])

    # Apply the "Canon correction" and the offset to TAI - UT
    # see https://eclipse.gsfc.nasa.gov/SEcat5/deltatpoly.html
    tai_minus_ut = tt_minus_ut - 0.000012932 * (y - 1955)**2 - 32.184
    return tai_minus_ut


_DELTA_T_NEG1999_3000 = None

def _initialize_ut1_neg1999_3000():
    """Initialize the TAI-UTC models for years -1999 to 3000 (and beyond)."""

    global _DELTA_T_NEG1999_3000

    _DELTA_T_NEG1999_3000 = FuncDeltaT(_delta_t_neg1999_3000, first=None, last=None)


# Initialize at startup
_initialize_ut1_neg1999_3000()

##########################################################################################
# Model and kernel selectors
##########################################################################################

_SELECTED_DELTA_T = None                     # Filled in below
_SELECTED_UT_MODEL = 'LEAPS'
_SELECTED_FUTURE_YEAR = None
_RUBBER = False

_DELTA_T_DICT = {       # (DeltaT object,
    'LEAPS'   : _LEAPS_DELTA_T,
    'SPICE'   : _SPICE_DELTA_T,
    'PRE-1972': MergedDeltaT(_LEAPS_DELTA_T, _DELTA_T_1958_1972),
    'CANON'   : MergedDeltaT(_LEAPS_DELTA_T, _DELTA_T_1958_1972, _DELTA_T_NEG1999_3000),
}


[docs] def set_ut_model(model='LEAPS', future=None): """Define how to handle the differences between TAI and UT for years prior to 1972 and for years into the future. This is a global setting of the Julian Library, although it can be changed at will. Parameters: model (str, optional): One of "LEAPS", "SPICE", "PRE-1972", or "CANON". See Notes. future (int, optional): The future year at which to use the "CANON" model, if it is selected. Use None or np.inf to suppress the CANON model for all future dates. Ignored if the "CANON" model is not in use. Notes: The following models are supported: * "LEAPS": This is the default model including leap seconds. It assumes that TAI-UTC equals 10 prior to 1972 and will hold its current fixed value into the future. * "SPICE": Replicate the behavior of the SPICE Toolkit, in which TAI-UTC=9 before 1972. In this system, December 31, 1971 incorrectly contained a leap second. * "PRE-1972": In addition to the "LEAPS" model, include the full model for UTC widely used during the period 1958-1972. In these years, the UTC time system was defined in terms of a "rubber second", which could expand or shrink as necessary to ensure that every UTC day had exactly 86,400 seconds. In addition, several fractional leap seconds were added during the 1960s. See https://hpiers.obspm.fr/eop-pc/index.php?index=TAI-UTC_tab. * "CANON": In addition to the "PRE-1972" model, include the model for UT1 "rubber seconds" based on the Five Millennium Canon of Solar Eclipses for the years -1999 to 3000. See https://eclipse.gsfc.nasa.gov/SEpubs/5MCSE.html. When selected, this model will apply for all years before 1958. Use the input parameter "future" to specify the future year in which this model overrides the UTC leap second model; otherwise, by default, the leap second model will apply to all future years. """ global _SELECTED_DELTA_T, _SELECTED_UT_MODEL, _SELECTED_FUTURE_YEAR, _RUBBER _SELECTED_DELTA_T = _DELTA_T_DICT[model] _SELECTED_UT_MODEL = model if model != 'CANON': future = None if future != _SELECTED_FUTURE_YEAR: last_year = None if future is None else future - 1 _LEAPS_DELTA_T.set_last_year(last_year) _SPICE_DELTA_T.set_last_year(last_year) _SELECTED_FUTURE_YEAR = future _RUBBER = model not in ('LEAPS', 'SPICE')
# Initialize... set_ut_model()
[docs] def load_lsk(lsk_path=''): """Load a specified SPICE leap seconds kernel. Any previously defined leap seconds are replaced by the new list. If additional leap seconds were previously inserted via :meth:`insert_leap_second`, they must be inserted again. Parameters: lsk_path (str, pathlib, of filecache.FCPath, optional): The path to an LSK kernel file. If this is blank or None, the default LSK kernel is re-loaded. Notes: The currently selected UTC model is preserved. The list of leap seconds is a global setting of the Julian Library. """ global _DELTA_T_DICT _initialize_leap_seconds(lsk_path) _initialize_utc_1958_1972() _initialize_ut1_neg1999_3000() _DELTA_T_DICT = { 'LEAPS' : _LEAPS_DELTA_T, 'SPICE' : _SPICE_DELTA_T, 'PRE-1972': MergedDeltaT(_LEAPS_DELTA_T, _DELTA_T_1958_1972), 'CANON' : MergedDeltaT(_LEAPS_DELTA_T, _DELTA_T_1958_1972, _DELTA_T_NEG1999_3000), } set_ut_model(_SELECTED_UT_MODEL, future=_SELECTED_FUTURE_YEAR)
[docs] def insert_leap_second(y, m, offset=1): """Insert a new (positive or negative) leap second. Parameters: y (int): Year of the new leap second. m (int): Month of the new leap second. offset (int): The change in TAI - UT. The default is 1; use -1 for a negative leap second. Notes: The new leap second must occur after any previously defined leap seconds. The list of leap seconds is a global setting of the Julian Library. """ _LEAPS_DELTA_T.insert_leap_second(y, m, offset) _SPICE_DELTA_T.insert_leap_second(y, m, offset) _DELTA_T_DICT['LEAPS'] = _LEAPS_DELTA_T # otherwise dict values would be stale _DELTA_T_DICT['SPICE'] = _SPICE_DELTA_T set_ut_model(_SELECTED_UT_MODEL, future=_SELECTED_FUTURE_YEAR)
########################################################################################## # Standard API ##########################################################################################
[docs] def delta_t_from_ymd(y, m, d=1): """The difference between TAI seconds and UT seconds for the given date, expressed as a calendar year, month, and optional day. Parameters: y (int or array-like): Year. m (int or array-like): Month, 1-12. d (int, float, or array-like): Day of month, 1-31. Returns: int, float or array: TAI-UT in seconds. If values are exclusively defined as integral values of leap seconds, returned values are integral; if any "rubber seconds" are involved, they are floats. """ return _SELECTED_DELTA_T.delta_t_from_ymd(y, m, d)
[docs] def delta_t_from_day(day): """The difference between TAI seconds and UT seconds for the given day number. Parameters: day (int, float, or array-like): Day number relative to January 1, 2000. Returns: int, float or array: TAI-UT in seconds. If values are exclusively defined as integral values of leap seconds, returned values are integral; if any "rubber seconds" are involved, they are floats. """ day = _number(day) (y, m, d) = ymd_from_day(day) return _SELECTED_DELTA_T.delta_t_from_ymd(y, m, d)
[docs] def leapsecs_from_ymd(y, m, d=1): """The number of leap seconds on the given date, where the date is expressed as a calendar year, month, and optional day. Parameters: y (int or array-like): Year. m (int or array-like): Month, 1-12. d (int, float, or array-like): Day of month, 1-31. Returns: int or array[int]: TAI-UT as integer seconds. """ return _SELECTED_DELTA_T.leapsecs_from_ymd(y, m, d)
[docs] def leapsecs_from_ym(y, m, d=1): """The number of leap seconds on the given date, where the date is expressed as a calendar year, month, and optional day. Alternative name for :meth:`leapsecs_from_ymd`. Parameters: y (int or array-like): Year. m (int or array-like): Month, 1-12. d (int, float, or array-like): Day of month, 1-31. Returns: int or array: TAI-UT as integer seconds. """ return _SELECTED_DELTA_T.leapsecs_from_ymd(y, m, d)
[docs] def leapsecs_on_day(day): """The cumulative difference between TAI and UT for the given day number. This differs from the function `tai_minus_utc_from_day()` in that it ignores UT "rubber seconds" and therefore always returns integers, typically 86400 or 86401. Parameters: day (int, float, or array-like): Day number of relative to January 1, 2000. Returns: int or array: TAI-UT as integer seconds. """ day = _number(day) (y, m, d) = ymd_from_day(day) return _SELECTED_DELTA_T.leapsecs_from_ymd(y, m, d)
[docs] def leapsecs_from_day(day): """The cumulative difference between TAI and UT for the given day number. This differs from the function `tai_minus_utc_from_day()` in that it ignores UT "rubber seconds" and therefore always returns integers, typically 86400 or 86401. Alternative name for :meth:`leapsecs_on_day`. Parameters: day (int, float, or array-like): Day number of relative to January 1, 2000. Returns: int or array: TAI-UT as integer seconds. """ return leapsecs_on_day(day)
[docs] def seconds_on_day(day, leapsecs=True, timesys='UTC'): """Number of seconds on a given day number. Parameters: day (int, float, or array-like): Day number of relative to January 1, 2000. leapsecs (bool, optional): If False, values of 86400 are returned regardless of the input. timesys (str, optional): The time system, "UTC" or "TAI". Returns: int or array: Number of seconds on the day, 86400 unless there is a leap second. Notes: If timesys equals "UTC", the values returned are in units of UTC "rubber seconds", which are adjusted relative to TAI seconds in order to ensure that every day prior to 1972 contains exactly 86400 seconds. If timesys equals "TAI", then the values returned are in fixed units of TAI seconds, meaning that days prior to 1972 may not have integral durations. """ day = _int(day) if not leapsecs: if np.isscalar(day): return 86400 result = np.empty(day.shape, dtype='int') result.fill(86400) return result if timesys == 'UTC': return 86400 + leapsecs_on_day(day+1) - leapsecs_on_day(day) if timesys == 'TAI': return 86400 + delta_t_from_day(day+1) - delta_t_from_day(day) raise ValueError('timesys must be either "UTC" or "TAI"') # pragma: no cover
##########################################################################################