File: C:/Users/fred/anaconda3/Lib/site-packages/astropy/utils/iers/tests/test_leap_second.py
# Licensed under a 3-clause BSD style license - see LICENSE.rst
import locale
import os
import platform
import urllib.request
import erfa
import numpy as np
import pytest
from numpy.testing import assert_array_equal
from astropy.tests.tests.test_imports import test_imports
from astropy.time import Time, TimeDelta
from astropy.utils.data import get_pkg_data_filename
from astropy.utils.iers import iers
# Import every top-level astropy module as a test that the ERFA leap second
# table is not updated for normal imports.
test_imports()
# Now test that the erfa leap_seconds table has not been updated. This must be
# done at the module level, which unfortunately will abort the entire test run
# if if fails. Running within a normal pytest test will not work because the
# other tests will end up updating this attribute by virtue of doing Time UTC
# transformations.
assert erfa.leap_seconds._expires is None
# Tests in this module assume that the erfa.leap_seconds attribute has been
# updated from the `erfa` package built-in table to the astropy built-in
# leap-second table. That has the effect of ensuring that the
# `erfa.leap_seconds.expires` property is sufficiently in the future.
iers_table = iers.LeapSeconds.auto_open()
erfa.leap_seconds.update(iers_table)
assert erfa.leap_seconds._expires is not None
SYSTEM_FILE = "/usr/share/zoneinfo/leap-seconds.list"
# Test leap_seconds.list in test/data.
LEAP_SECOND_LIST = get_pkg_data_filename("data/leap-seconds.list")
def test_configuration():
# This test just ensures things stay consistent.
# Adjust if changes are made.
assert iers.conf.iers_leap_second_auto_url == iers.IERS_LEAP_SECOND_URL
assert iers.conf.ietf_leap_second_auto_url == iers.IETF_LEAP_SECOND_URL
class TestReading:
"""Basic tests that leap seconds can be read."""
def verify_day_month_year(self, ls):
assert np.all(ls["day"] == 1)
assert np.all((ls["month"] == 1) | (ls["month"] == 7) | (ls["year"] < 1970))
assert np.all(ls["year"] >= 1960)
t = Time(
{"year": ls["year"], "month": ls["month"], "day": ls["day"]},
format="ymdhms",
)
assert np.all(t == Time(ls["mjd"], format="mjd"))
def test_read_leap_second_dat(self):
ls = iers.LeapSeconds.from_iers_leap_seconds(iers.IERS_LEAP_SECOND_FILE)
# Below, >= to take into account we might ship and updated file.
assert ls.expires >= Time("2020-06-28", scale="tai")
assert ls["mjd"][0] == 41317
assert ls["tai_utc"][0] == 10
assert ls["mjd"][-1] >= 57754
assert ls["tai_utc"][-1] >= 37
self.verify_day_month_year(ls)
def test_read_leap_second_dat_locale(self):
current = locale.setlocale(locale.LC_ALL)
try:
if platform.system() == "Darwin":
locale.setlocale(locale.LC_ALL, "fr_FR")
else:
locale.setlocale(locale.LC_ALL, "fr_FR.utf8")
ls = iers.LeapSeconds.from_iers_leap_seconds(iers.IERS_LEAP_SECOND_FILE)
except locale.Error as e:
pytest.skip(f"Locale error: {e}")
finally:
locale.setlocale(locale.LC_ALL, current)
# Below, >= to take into account we might ship and updated file.
assert ls.expires >= Time("2020-06-28", scale="tai")
def test_open_leap_second_dat(self):
ls = iers.LeapSeconds.from_iers_leap_seconds(iers.IERS_LEAP_SECOND_FILE)
ls2 = iers.LeapSeconds.open(iers.IERS_LEAP_SECOND_FILE)
assert np.all(ls == ls2)
@pytest.mark.parametrize(
"file",
(LEAP_SECOND_LIST, "file:" + urllib.request.pathname2url(LEAP_SECOND_LIST)),
)
def test_read_leap_seconds_list(self, file):
ls = iers.LeapSeconds.from_leap_seconds_list(file)
assert ls.expires == Time("2020-06-28", scale="tai")
assert ls["mjd"][0] == 41317
assert ls["tai_utc"][0] == 10
assert ls["mjd"][-1] == 57754
assert ls["tai_utc"][-1] == 37
self.verify_day_month_year(ls)
@pytest.mark.parametrize(
"file",
(LEAP_SECOND_LIST, "file:" + urllib.request.pathname2url(LEAP_SECOND_LIST)),
)
def test_open_leap_seconds_list(self, file):
ls = iers.LeapSeconds.from_leap_seconds_list(file)
ls2 = iers.LeapSeconds.open(file)
assert np.all(ls == ls2)
@pytest.mark.skipif(
not os.path.isfile(SYSTEM_FILE), reason=f"system does not have {SYSTEM_FILE}"
)
def test_open_system_file(self):
ls = iers.LeapSeconds.open(SYSTEM_FILE)
expired = ls.expires < Time.now()
if expired:
pytest.skip("System leap second file is expired.")
assert not expired
def make_fake_file(expiration, tmp_path):
"""copy the built-in IERS file but set a different expiration date."""
ls = iers.LeapSeconds.from_iers_leap_seconds()
fake_file = str(tmp_path / "fake_leap_seconds.dat")
with open(fake_file, "w") as fh:
fh.write(
"\n".join([f"# File expires on {expiration}"] + str(ls).split("\n")[2:-1])
)
return fake_file
def test_fake_file(tmp_path):
fake_file = make_fake_file("28 June 2345", tmp_path)
fake = iers.LeapSeconds.from_iers_leap_seconds(fake_file)
assert fake.expires == Time("2345-06-28", scale="tai")
class TestAutoOpenExplicitLists:
# For this set of tests, leap-seconds are allowed to be expired
# except as explicitly tested.
@pytest.mark.filterwarnings(iers.IERSStaleWarning)
def test_auto_open_simple(self):
ls = iers.LeapSeconds.auto_open([iers.IERS_LEAP_SECOND_FILE])
assert ls.meta["data_url"] == iers.IERS_LEAP_SECOND_FILE
@pytest.mark.filterwarnings(iers.IERSStaleWarning)
def test_auto_open_erfa(self):
ls = iers.LeapSeconds.auto_open(["erfa", iers.IERS_LEAP_SECOND_FILE])
assert ls.meta["data_url"] in ["erfa", iers.IERS_LEAP_SECOND_FILE]
@pytest.mark.filterwarnings(iers.IERSStaleWarning)
def test_fake_future_file(self, tmp_path):
fake_file = make_fake_file("28 June 2345", tmp_path)
# Try as system file for auto_open, setting auto_max_age such
# that any ERFA or system files are guaranteed to be expired,
# while the fake file is guaranteed to be OK.
with iers.conf.set_temp("auto_max_age", -100000):
ls = iers.LeapSeconds.auto_open(
["erfa", iers.IERS_LEAP_SECOND_FILE, fake_file]
)
assert ls.expires == Time("2345-06-28", scale="tai")
assert ls.meta["data_url"] == str(fake_file)
# And as URL
fake_url = "file:" + urllib.request.pathname2url(fake_file)
ls2 = iers.LeapSeconds.auto_open(
["erfa", iers.IERS_LEAP_SECOND_FILE, fake_url]
)
assert ls2.expires == Time("2345-06-28", scale="tai")
assert ls2.meta["data_url"] == str(fake_url)
def test_fake_expired_file(self, tmp_path):
fake_file1 = make_fake_file("28 June 2010", tmp_path)
fake_file2 = make_fake_file("27 June 2012", tmp_path)
# Between these and the built-in one, the built-in file is best.
ls = iers.LeapSeconds.auto_open(
[fake_file1, fake_file2, iers.IERS_LEAP_SECOND_FILE]
)
assert ls.meta["data_url"] == iers.IERS_LEAP_SECOND_FILE
# But if we remove the built-in one, the least expired one will be
# used and we get a warning that it is stale.
with pytest.warns(iers.IERSStaleWarning):
ls2 = iers.LeapSeconds.auto_open([fake_file1, fake_file2])
assert ls2.meta["data_url"] == fake_file2
assert ls2.expires == Time("2012-06-27", scale="tai")
# Use the fake files to make sure auto_max_age is safe.
# Should have no warning in either example.
with iers.conf.set_temp("auto_max_age", None):
ls3 = iers.LeapSeconds.auto_open([fake_file1, iers.IERS_LEAP_SECOND_FILE])
assert ls3.meta["data_url"] == iers.IERS_LEAP_SECOND_FILE
with iers.conf.set_temp("auto_max_age", None):
ls4 = iers.LeapSeconds.auto_open([fake_file1, fake_file2])
assert ls4.meta["data_url"] == fake_file2
@pytest.mark.remote_data
class TestRemoteURLs:
def setup_class(cls):
# Need auto_download so that IERS_B won't be loaded and cause tests to
# fail.
iers.conf.auto_download = True
def teardown_class(cls):
# This setting is to be consistent with astropy/conftest.py
iers.conf.auto_download = False
# In these tests, the results may be cached.
# This is fine - no need to download again.
def test_iers_url(self):
ls = iers.LeapSeconds.auto_open([iers.IERS_LEAP_SECOND_URL])
assert ls.expires > Time.now()
def test_ietf_url(self):
ls = iers.LeapSeconds.auto_open([iers.IETF_LEAP_SECOND_URL])
assert ls.expires > Time.now()
class TestDefaultAutoOpen:
"""Test auto_open with different _auto_open_files."""
def setup_method(self):
# Identical to what is used in LeapSeconds.auto_open().
self.good_enough = iers.LeapSeconds._today() + TimeDelta(
180 - iers._none_to_float(iers.conf.auto_max_age), format="jd"
)
self._auto_open_files = iers.LeapSeconds._auto_open_files.copy()
def teardown_method(self):
iers.LeapSeconds._auto_open_files = self._auto_open_files
def remove_auto_open_files(self, *files):
"""Remove some files from the auto-opener.
The default set is restored in teardown.
"""
for f in files:
iers.LeapSeconds._auto_open_files.remove(f)
def test_erfa_found(self):
# Set huge maximum age such that whatever ERFA has is OK.
# Since it is checked first, it should thus be found.
with iers.conf.set_temp("auto_max_age", 100000):
ls = iers.LeapSeconds.open()
assert ls.meta["data_url"] == "erfa"
def test_builtin_found(self):
# Set huge maximum age such that built-in file is always OK.
# If we remove 'erfa', it should thus be found.
self.remove_auto_open_files("erfa")
with iers.conf.set_temp("auto_max_age", 100000):
ls = iers.LeapSeconds.open()
assert ls.meta["data_url"] == iers.IERS_LEAP_SECOND_FILE
# The test below is marked remote_data only to ensure it runs
# as an allowed-fail job on CI: i.e., we will notice it (eventually)
# but will not be misled in thinking that a PR is bad.
@pytest.mark.remote_data
def test_builtin_not_expired(self):
# TODO: would be nice to have automatic PRs for this!
ls = iers.LeapSeconds.open(iers.IERS_LEAP_SECOND_FILE)
assert ls.expires > self.good_enough, (
"The leap second file built in to astropy is expired. Fix with:\n"
"cd astropy/utils/iers/data/; . update_builtin_iers.sh\n"
"and commit as a PR (for details, see release procedure)."
)
def test_fake_future_file(self, tmp_path):
fake_file = make_fake_file("28 June 2345", tmp_path)
# Try as system file for auto_open, setting auto_max_age such
# that any ERFA or system files are guaranteed to be expired.
with (
iers.conf.set_temp("auto_max_age", -100000),
iers.conf.set_temp("system_leap_second_file", fake_file),
):
ls = iers.LeapSeconds.open()
assert ls.expires == Time("2345-06-28", scale="tai")
assert ls.meta["data_url"] == str(fake_file)
# And as URL
fake_url = "file:" + urllib.request.pathname2url(fake_file)
with (
iers.conf.set_temp("auto_max_age", -100000),
iers.conf.set_temp("iers_leap_second_auto_url", fake_url),
):
ls2 = iers.LeapSeconds.open()
assert ls2.expires == Time("2345-06-28", scale="tai")
assert ls2.meta["data_url"] == str(fake_url)
def test_fake_expired_file(self, tmp_path):
self.remove_auto_open_files(
"erfa", "iers_leap_second_auto_url", "ietf_leap_second_auto_url"
)
fake_file = make_fake_file("28 June 2010", tmp_path)
with iers.conf.set_temp("system_leap_second_file", fake_file):
# If we try this directly, the built-in file will be found.
ls = iers.LeapSeconds.open()
assert ls.meta["data_url"] == iers.IERS_LEAP_SECOND_FILE
# But if we remove the built-in one, the expired one will be
# used and we get a warning that it is stale.
self.remove_auto_open_files(iers.IERS_LEAP_SECOND_FILE)
with pytest.warns(iers.IERSStaleWarning):
ls2 = iers.LeapSeconds.open()
assert ls2.meta["data_url"] == fake_file
assert ls2.expires == Time("2010-06-28", scale="tai")
@pytest.mark.skipif(
not os.path.isfile(SYSTEM_FILE), reason=f"system does not have {SYSTEM_FILE}"
)
def test_system_file_used_if_not_expired(self, tmp_path):
# We skip the test if the system file is on a CI and is expired -
# we should not depend on CI keeping it up to date, but if it is,
# we should check that it is used if possible.
if iers.LeapSeconds.open(SYSTEM_FILE).expires <= self.good_enough:
pytest.skip("System leap second file is expired.")
self.remove_auto_open_files("erfa")
with iers.conf.set_temp("system_leap_second_file", SYSTEM_FILE):
ls = iers.LeapSeconds.open()
assert ls.expires > self.good_enough
assert ls.meta["data_url"] in (iers.IERS_LEAP_SECOND_FILE, SYSTEM_FILE)
# Also check with a "built-in" file that is expired
fake_file = make_fake_file("28 June 2017", tmp_path)
iers.LeapSeconds._auto_open_files[0] = fake_file
ls2 = iers.LeapSeconds.open()
assert ls2.expires > Time.now()
assert ls2.meta["data_url"] == SYSTEM_FILE
@pytest.mark.remote_data
def test_auto_open_urls_always_good_enough(self):
# Avoid using the erfa, built-in and system files, as they might
# be good enough already.
try:
# Need auto_download so that IERS_B won't be loaded and
# cause tests to fail.
iers.conf.auto_download = True
self.remove_auto_open_files(
"erfa", iers.IERS_LEAP_SECOND_FILE, "system_leap_second_file"
)
ls = iers.LeapSeconds.open()
assert ls.expires > self.good_enough
assert ls.meta["data_url"].startswith("http")
finally:
# This setting is to be consistent with astropy/conftest.py
iers.conf.auto_download = False
class ERFALeapSecondsSafe:
"""Base class for tests that change the ERFA leap-second tables.
It ensures the original state is restored.
"""
def setup_method(self):
# Keep current leap-second table and expiration.
self.erfa_ls = self._erfa_ls = erfa.leap_seconds.get()
self.erfa_expires = self._expires = erfa.leap_seconds._expires
def teardown_method(self):
# Restore leap-second table and expiration.
erfa.leap_seconds.set(self.erfa_ls)
erfa.leap_seconds._expires = self._expires
class TestFromERFA(ERFALeapSecondsSafe):
def test_get_erfa_ls(self):
ls = iers.LeapSeconds.from_erfa()
assert ls.colnames == ["year", "month", "tai_utc"]
assert isinstance(ls.expires, Time)
assert ls.expires == self.erfa_expires
ls_array = np.array(ls["year", "month", "tai_utc"])
assert np.all(ls_array == self.erfa_ls)
def test_get_built_in_erfa_ls(self):
ls = iers.LeapSeconds.from_erfa(built_in=True)
assert ls.colnames == ["year", "month", "tai_utc"]
assert isinstance(ls.expires, Time)
ls_array = np.array(ls["year", "month", "tai_utc"])
assert np.all(ls_array == self.erfa_ls[: len(ls_array)])
def test_get_modified_erfa_ls(self):
erfa.leap_seconds.set(self.erfa_ls[:-10])
ls = iers.LeapSeconds.from_erfa()
assert len(ls) == len(self.erfa_ls) - 10
ls_array = np.array(ls["year", "month", "tai_utc"])
assert np.all(ls_array == self.erfa_ls[:-10])
ls2 = iers.LeapSeconds.from_erfa(built_in=True)
assert len(ls2) > len(ls)
erfa.leap_seconds.set(None)
erfa_built_in = erfa.leap_seconds.get()
assert len(ls2) == len(erfa_built_in)
ls2_array = np.array(ls2["year", "month", "tai_utc"])
assert np.all(ls2_array == erfa_built_in)
def test_open(self):
ls = iers.LeapSeconds.open("erfa")
assert isinstance(ls.expires, Time)
assert ls.expires == self.erfa_expires
ls_array = np.array(ls["year", "month", "tai_utc"])
assert np.all(ls_array == self.erfa_ls)
class TestUpdateLeapSeconds(ERFALeapSecondsSafe):
def setup_method(self):
super().setup_method()
# Read default leap second table.
self.ls = iers.LeapSeconds.from_iers_leap_seconds()
# For tests, reset ERFA table to built-in default.
erfa.leap_seconds.set()
self.erfa_ls = erfa.leap_seconds.get()
def test_built_in_up_to_date(self):
"""Leap second should match between built-in and ERFA."""
erfa_since_1970 = self.erfa_ls[self.erfa_ls["year"] > 1970]
assert len(self.ls) >= len(erfa_since_1970), "built-in leap seconds out of date"
assert len(self.ls) <= len(erfa_since_1970), "ERFA leap seconds out of date"
overlap = np.array(self.ls["year", "month", "tai_utc"])
assert np.all(overlap == erfa_since_1970.astype(overlap.dtype))
def test_update_with_built_in(self):
"""An update with built-in should not do anything."""
n_update = self.ls.update_erfa_leap_seconds()
assert n_update == 0
new_erfa_ls = erfa.leap_seconds.get()
assert np.all(new_erfa_ls == self.erfa_ls)
@pytest.mark.parametrize("n_short", (1, 3))
def test_update(self, n_short):
"""Check whether we can recover removed leap seconds."""
erfa.leap_seconds.set(self.erfa_ls[:-n_short])
n_update = self.ls.update_erfa_leap_seconds()
assert n_update == n_short
new_erfa_ls = erfa.leap_seconds.get()
assert_array_equal(new_erfa_ls, self.erfa_ls)
# Check that a second update does not do anything.
n_update2 = self.ls.update_erfa_leap_seconds()
assert n_update2 == 0
new_erfa_ls2 = erfa.leap_seconds.get()
assert_array_equal(new_erfa_ls2, self.erfa_ls)
def test_update_initialize_erfa(self):
# With pre-initialization, update does nothing.
erfa.leap_seconds.set(self.erfa_ls[:-2])
n_update = self.ls.update_erfa_leap_seconds(initialize_erfa=True)
assert n_update == 0
new_erfa_ls = erfa.leap_seconds.get()
assert_array_equal(new_erfa_ls, self.erfa_ls)
def test_update_overwrite(self):
n_update = self.ls.update_erfa_leap_seconds(initialize_erfa="empty")
assert n_update == len(self.ls)
new_erfa_ls = erfa.leap_seconds.get()
assert new_erfa_ls["year"].min() > 1970
n_update2 = self.ls.update_erfa_leap_seconds()
assert n_update2 == 0
new_erfa_ls2 = erfa.leap_seconds.get()
assert_array_equal(new_erfa_ls2, new_erfa_ls)
n_update3 = self.ls.update_erfa_leap_seconds(initialize_erfa=True)
assert n_update3 == 0
new_erfa_ls3 = erfa.leap_seconds.get()
assert_array_equal(new_erfa_ls3, self.erfa_ls)
def test_bad_jump(self):
erfa.leap_seconds.set(self.erfa_ls[:-2])
bad = self.ls.copy()
bad["tai_utc"][-1] = 5
with pytest.raises(ValueError, match="jump"):
bad.update_erfa_leap_seconds()
# With an error the ERFA table should not change.
assert_array_equal(erfa.leap_seconds.get(), self.erfa_ls[:-2])
# Unless we initialized it beforehand.
with pytest.raises(ValueError, match="jump"):
bad.update_erfa_leap_seconds(initialize_erfa=True)
assert_array_equal(erfa.leap_seconds.get(), self.erfa_ls)
# Of course, we get no errors if we initialize only.
erfa.leap_seconds.set(self.erfa_ls[:-2])
n_update = bad.update_erfa_leap_seconds(initialize_erfa="only")
assert n_update == 0
new_erfa_ls = erfa.leap_seconds.get()
assert_array_equal(new_erfa_ls, self.erfa_ls)
def test_bad_day(self):
erfa.leap_seconds.set(self.erfa_ls[:-2])
bad = self.ls.copy()
bad["day"][-1] = 5
with pytest.raises(ValueError, match="not on 1st"):
bad.update_erfa_leap_seconds()
def test_bad_month(self):
erfa.leap_seconds.set(self.erfa_ls[:-2])
bad = self.ls.copy()
bad["month"][-1] = 5
with pytest.raises(ValueError, match="January"):
bad.update_erfa_leap_seconds()
assert_array_equal(erfa.leap_seconds.get(), self.erfa_ls[:-2])